공부/인프런 - Rookiss

Part 3-5-2. 카메라 : 클릭한 곳으로 플레이어 이동하기

셩잇님 2023. 8. 21. 15:52
반응형

 

 

카메라

롤이나 스타처럼 클릭한 곳으로 플레이어와 카메라가 따라가게 하기. 바닥에 마우스를 클릭하면 클릭한 곳으로 플레이어가 자동으로, 스스로 이동하게 한다.

 

방법 소개
1️⃣ 카메라를 플레이어의 자식으로 넣기
단점 👉 플레이어가 회전하면 카메라도 같이 회전한다. 그래서 휙휙 돌아서 너무 어지럽다.

 

2️⃣ 부모-자식 관계가 아닌 별개로 두되, 카메라가 플레이어와 적정 Vector3 값을 유지한체, 플레이어를 바라보며 따라다니게 하기

 

카메라의 위치는 (플레이어와의 위치 + 카메라와 플레이어의 적당한 Vector3 값)로 업데이트 하고, 카메라는 LookAt 함수를 이용하여 플레이어의 위치를 바라보게 회전한다.  플레이어가 바라보는 방향으로 회전하는 것이 아닌, 카메라가 그냥 플레이어의 위치를 바라보게 회전하는 것이다.

 

 

📜Define.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

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

    public enum CameraMode
    {
        QuaterView,
    }
}

📜Define 클래스에 MouseEvent 라는 enum을 정의하였다.
Press 👉 마우스를 클릭하는 동안 사용할
Click 👉 마우스를 클릭에서 손 뗐을 때 사용할

 

 

📜InputManager.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

using System;

public class InputManager
{
    public Action KeyAction = null;
    public Action<Define.MouseEvent> MouseAction = null;

    bool _pressed = false;

    public void OnUpdate()
    {
        /* Keyboard 입력 */
        if (Input.anyKey && KeyAction != null)
            KeyAction.Invoke();

        /* Mouse 입력 */
        if (MouseAction != null)
        {
            if (Input.GetMouseButton(0))  // 누르는 중
            {
                MouseAction.Invoke(Define.MouseEvent.Press);  // 마우스를 누르고 있을 땐 Define.MouseEvent.Press 을 인수로 넘겨 액션에 등록된 함수들 실행
                _pressed = true;  // 누르는 중이었음을 표시하는
            }
            else // 누르는 중 아님
            { 
                if (_pressed)    // 누르는 중이 아닌데 눌렀었다면 뗀 것.
                    MouseAction.Invoke(Define.MouseEvent.Click);  // 마우스를 누르다 뗏을 땐 Define.MouseEvent.Click 을 인수로 넘겨 액션에 등록된 함수들 실행
                _pressed = false;  // 초기화
            }
        }
    }
}

 

Define.MouseEvent 타입의 인수만 받는 함수들을 등록할 수 있는 MouseAction 액션 추가.

 

마우스 입력 처리
1. 마우스를 누르고 있을 땐 액션에 등록된 함수들에게 Define.MouseEvent.Press 을 인수로 넘겨 실행

👉 이번 포스트에선 아직 사용 X

 

2. 마우스를 누르다 뗏을 땐 액션에 등록된 함수들에게 Define.MouseEvent.Click 을 인수로 넘겨 실행

👉 클릭한 곳으로 자동 이동하게 할 것이라서 이번 포스트에선 이 것만 사용할 것

 

cf) 만약 마우스 드래그하는 상태를 캐치해내고 싶다면 0.2초 이상 누르고 있을 때는 드래그 상태로 정의한다던가 등등 이런 조건을 줄 수 있을 것이다.

 

 

📜PlayerController.cs

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class PlayerController : MonoBehaviour
{
    [SerializeField]
    float _speed = 10.0f;

    bool _moveToDest = false;  // 마우스 클릭한 곳으로 롤처럼 자동 이동 되야 하는 상황인지
    Vector3 _destPos;  // 목적지 위치

    void Start()
    {
        Managers.Input.KeyAction -= OnKeyboard;
        Managers.Input.KeyAction += OnKeyboard;

        Managers.Input.MouseAction -= OnMouseClicked;
        Managers.Input.MouseAction += OnMouseClicked;
    }

    void Update()
    {
        if (_moveToDest)
        {
            Vector3 dir = _destPos - transform.position;
            if (dir.magnitude < 0.0001f)  // 도착함. 더 이상 이동 X 
            {
                _moveToDest = false;
            }
            else   // 아직 도착 안함
            {
                float moveDist = Mathf.Clamp(_speed * Time.deltaTime, 0, dir.magnitude);
                transform.position = transform.position + dir.normalized * moveDist;

                transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(dir), 10 * Time.deltaTime);
                transform.LookAt(_destPos);
            }
        }
    }

    void OnKeyboard()
    {
        if (Input.GetKey(KeyCode.W))
        {
            transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(Vector3.forward), 0.2f);
            transform.position += Vector3.forward * Time.deltaTime * _speed;
        }

        if (Input.GetKey(KeyCode.S))
        {
            transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(Vector3.back), 0.2f);
            transform.position += Vector3.back * Time.deltaTime * _speed;
        }
        if (Input.GetKey(KeyCode.D))
        {
            transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(Vector3.right), 0.2f);
            transform.position += Vector3.right * Time.deltaTime * _speed;
        }
        if (Input.GetKey(KeyCode.A))
        {
            transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(Vector3.left), 0.2f);
            transform.position += Vector3.left * Time.deltaTime * _speed;
        }

        _moveToDest = false;  // 키보드 입력은 플레이어 의지로 움직이는 것이니 자동 이동 멈춤
    }

    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;
        }
    }
}

 

_moveToDest 👉 마우스 클릭 이벤트로 인하여 플레이어가 그 곳으로 이동 되야 하는 상황인지 True or False.
_destPos 👉 목적지 위치. 바닥에 마우스 클릭한 곳 월드 좌표가 목적지가 될 것이다.

 

Start()

📜Manager.cs 싱글톤에 접근하여 📜InputManager.cs 의 MouseAction에 📜PlayerController.cs의 OnMouseClicked 함수를 등록한다.

 

OnMouseClicked

Define.MouseEvent 타입의 인수를 evt에 받는다.

일단 실험삼아 evt가 Define.MouseEvent.Click 상태일 때만 처리될 수 있도록 Define.MouseEvent.Click 가 아니면 return 시켰다.

 

마우스 클릭하고 뗐을 때

1. 카메라 위치로부터, 그 클릭한 화면상의 위치의 월드 좌표로 향하는 Ray를 생성한 후,

2. “Wall” Layer 를 가진 대상들에게만 이 Ray를 100 길이로 쏜다. 그리고 충돌 정보를 hit에 저장.

2 - 1. “Wall” Layer 를 가진 대상에 충돌했다면

2 - 2 _destPos을 충돌한 위치로 업데이트. 즉, 클릭한 곳으로 업데이트.

2 - 3. _moveToDest의 값을 True로 업데이트.

 

OnKeyboard()

마우스 입력으로 인해 자동으로 이동 중이다가 키보드 입력이 들어오면 키보드 입력 처리를 다 하고난 후, _destPos로 더 이상 이동하지 않게 _moveToDest를 False로 업데이트.

 👉 롤 하다보면 마우스 클릭한 곳으로 이동 하다가도 플레이어가 WASD 이동키 입력하면 더 이상 클릭한 곳으로 이동하지 않듯이 그 작업을 해준 것.

키보드 입력 처리를 다 하고난 후엔 자동으로 _moveToDest를 False로 업데이트.

 

Update()

매 프레임 실행  👉 이게 다 실행되고 나서 📜CameraController.cs의 LateUpdate() 에서 플레이어를 따라가도록 카메라 위치가 업데이트 된다.

 

클릭한 곳으로 플레이어가 자동으로 이동 되야 하는 상황이라면 if (_moveToDest)

dir 업데이트 👉 플레이어로부터 목적지까지의 방향 (플레이어가 그 곳으로 도착해갈 수록 점점 dir 벡터의 크기가 작아진다)

도착했다고 판단하고 더 이상 이동 X _moveToDest를 False로 업데이트. if (dir.magnitude < 0.0001f)

아직 도착하지 않았으니 플레이어의 위치와 회전값을 클릭한 위치로 이동해야 한다.

 

위치 업데이트
 👉 한 프레임 마다 dir 방향으로 _speed * Time.deltaTime 크기만큼 이동하되, _speed * Time.deltaTime가 dir.magnitude를 넘지 않게 한다. 즉, 이동할만큼의 스칼라 크기가 목적지 크기를 넘지 않게

이 과정 없이 그냥 아래와 같이 해주면 목적지를 지나쳐버릴 수가 있다. 목적지 지나쳤다가 dir 새롭게 업뎃 한것에 의해 다시 목적지 위치로 돌아오고 이런 과정이 있을 수 있기 때문에 플레이어가 버벅대며 움직이는 것처럼 된다.

transform.position = transform.position + dir.normalized * _speed * Time.deltaTime;

 

회전 값 업데이트
dir 방향을 바라보도록 먼저 부드럽게 회전을 해준다.
destPos 위치를 바라보도록 회전한다.

      transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(dir), 10 * Time.deltaTime);
      transform.LookAt(_destPos);

 

 

 

반응형