Chapter 5-2. 롤이나 스타처럼 클릭한 곳으로 플레이어와 카메라가 따라가게 하기

Date:     Updated:

카테고리:

태그:

인프런에 있는 Rookiss님의 [C#과 유니티로 만드는 MMORPG 게임 개발 시리즈] Part3: 유니티 엔진 강의를 듣고 정리한 필기입니다. 😀
🌜 강의 들으러 가기 Click

Chapter 5. 카메라

🚖 롤이나 스타처럼 클릭한 곳으로 플레이어와 카메라가 따라가게 하기

바닥에 마우스를 클릭하면 클릭한 곳으로 플레이어가 자동으로, 스스로 이동하게 한다. 롤이나 스타크래프트처럼.

📜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 액션 추가.
  • 마우스 입력 처리
    • 마우스를 누르고 있을 땐 액션에 등록된 함수들에게 Define.MouseEvent.Press 을 인수로 넘겨 실행
      • 이번 포스트에선 아직 사용 X
    • 마우스를 누르다 뗏을 땐 액션에 등록된 함수들에게 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 시켰다.
      • 마우스 클릭하고 뗐을 때
        • 카메라 위치로부터, 그 클릭한 화면상의 위치의 월드 좌표로 향하는 Ray를 생성한 후
        • “Wall” Layer 를 가진 대상들에게만 이 Ray를 100 길이로 쏜다. 그리고 충돌 정보를 hit에 저장.
          • “Wall” Layer 를 가진 대상에 충돌했다면
            • _destPos을 충돌한 위치로 업데이트. 즉, 클릭한 곳으로 업데이트.
            • _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.deltaTimedir.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);
            


🌜 개인 공부 기록용 블로그입니다. 오류나 틀린 부분이 있을 경우 
언제든지 댓글 혹은 메일로 지적해주시면 감사하겠습니다! 😄

맨 위로 이동하기

댓글남기기