강의, 책/[Unity] C#과 유니티로 만드는 MMORPG 게임 개발 시리즈

Section 5. Camera

hye3193 2024. 1. 24. 18:30

다양한 카메라 모드를 구현하기 위해 새로운 스크립트를 정의한다

파일 경로: Assets > Scripts > Utils > Define.cs

public class Define
{
    public enum CameraMode
    {
        QuarterView,
    }
}

위와 같이 카메라모드를 정의해 준 다음,

 

Assets > Scripts > Controllers > CameraController.cs(main camera 오브젝트에 부착)에서

public class CameraController : MonoBehaviour
{
    Define.CameraMode _mode = Define.CameraMode.QuarterView;
    Vector3 _delta = new Vector3(0.0f, 6.0f, -5.0f);
    GameObject _player = null;
    
    void LateUpdate()
    {
        if (_mode = Define.CameraMode.QuarterView)
        {
            transform.position = _player.transform.position += _delta;
            transform.LookAt(_player.transform);
        }
    }
    
    public void SetQuarterView(Vector3 delta)
    {
        _mode = Define.CameraMode.QuarterView;
        _delta = delta;
    }
}

위와 같이 카메라 모드를 정의할 수 있다

* _delta: 유저랑 카메라 사이의 거리

* Update 함수에서 플레이어가 움직인 후에 카메라가 그 좌표로 따라가게 하기 위해 카메라에는 LateUpdate문을 사용

 

LookAt(target) 함수를 사용하면 target 좌표 방향을 바라보도록 rotate된다

 

 

화면에서 클릭한 위치로 플레이어가 이동하게 만들기 위한 스크립트

우선 Define 스크립트에서 새로운 enum(MouseEvent)을 만들어준다

public class Define
{
    public enum MouseEvent
    {
        Press,
        Click,
    }

    public enum CameraMode
    {
        QuarterView,
    }
}

 

그리고 InputManager에서 MouseAction 액션을 추가해준다

public class InputManager
{
    public Action KeyAction = null;
    public Action<Define.MouseEvent> MouseAction = null;
    
    bool _pressed = false;
    
    public void OnUpdate()
    {
        if (Input.anyKey && KeyAction != null)
            KeyAction.Invoke();
            
        if (MouseAction != null)
        {
            if (Input.GetMouseButton(0))
            {
                MouseAction.Invoke(Define.MouseEvent.Press);
                _pressed = true;
            }
            else
            {
                if (_pressed)
                {
                    MouseAction.Invoke(Define.MouseEvent.Click);
                }
                _pressed = false;
            }
    }
}

bool 타입의 _pressed 변수를 선언하여, 마우스 입력이 들어오면 이를 true로 바꿔주고, 마우스를 뗄 경우(Input.GetMouseButton(0) = false) press 여부를 체크하여 click 이벤트를 invoke 해준다

 

그 다음 PlayerController 스크립트 상에서 mouseevent를 추가해준다

bool _moveToDest = false;
Vector3 _destPos;

void Start()
{
    Managers.Input.MouseAction -= OnMouseClicked;
    Managers.Input.MouseAction += OnMouseClicked;
}

void OnMouseClicked(Define.MouseEvent evt)
{
    if (evt != Define.MouseEvent.Click)
        return;
    
    Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    Debug.DrawRay(Camera.main.transform.position, ray.direction * 100.0f, Color.red, 1.0f);
    
    RaycastHit hit;
    if (Physics.Raycast(ray, out hit, 100.0f, LayerMask.GetMask("Wall")))
    {
        _destPos = hit.point;
        _moveToDest = true;
    }
}

그리고 PlayerController 내에서 키보드로 움직이는 방식을 사용할 경우(키 입력을 받았을 경우) _moveToDest 변수를 false로 설정해준다

* Layer 이름이 Wall인데, 강의에서 이는 바닥을 가르키는 레이어에 쓰인 것

 

해당 위치로 움직이게 하기 위해서 Update 문에서 아래와 같이 입력해준다

void Update()
{
    if (_moveToDest)
    {
        Vector3 dir = _destPos - transform.position;
        if (dir.magnitude < 0.0001f)
        {
            _moveToDest = false;
        }
        else
        {
            float moveDist = Mathf.Clamp(_speed * Time.deltaTime, 0, dir.magnitude);
            transform.position += dir.normalized * moveDist;
            
            transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(dir), 10 * Time.deltaTime);
        }
    }
}

float moveDist를 선언해줌으로써 moveDist 값이 dir.magnitude(목적지까지 남은 거리)보다 커서 이를 지나쳤다 다시 뒤돌아오는 등의 현상을 해결해줄 수 있다

 

선택한 방향을 바라보게 회전하는 것은 transform.LookAt 함수를 이용할 수도 있지만, 부드럽게 회전시켜주기 위해서 Slerp를 사용하면 된다

* Quaternion.LookRotation은 해당 벡터 방향을 바라보도록 회전시키는 함수다

 

마지막으로 벽에 가려져 플레이어가 보이지 않을 경우, 플레이어 위치에서 카메라를 향해 광선을 쏘아, 충돌한 위치로 카메라를 이동시키는 코드를 CameraController 스크립트에 추가한다

    void LateUpdate()
    {
        if (_mode = Define.CameraMode.QuarterView)
        {
            RaycastHit hit;
            if (Physics.Raycast(_player.transform.position, _delta, out hit, _delta.magnitude, LayerMask.GetMask("Wall")))
            {
                float dist = (hit.point - _player.transform.position).magnitude * 0.8f;
                transfrom.position = _player.transform.position + _delta.normalized * dist;
            }
            else
            {
                transform.position = _player.transform.position += _delta;
                transform.LookAt(_player.transform);
            }

        }
    }

플레이어의 위치에서 카메라방향(_delta)으로 광선을 쏴서 Wall 레이어에 충돌하는지를 감지한 다음,

충돌했을 경우 충돌지점에서 플레이어까지의 벡터 크기의 0.8에 해당하는 위치로 이동시켜준다