Unity 공부 (12)

도토코·2025년 3월 10일

Unity공부

목록 보기
12/22

Camere (2)


클릭하면 플레이어를 그곳으로 이동시키기

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를 수정해준다.

InputManager.cs

using System;
using UnityEngine;

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;
            }
        }
    }
}
  • Define.MouseEvent 타입의 인수만 받는 함수들을 등록할 수 있는 MouseAction 액션 추가.
  • 마우스 입력 처리
    • 마우스를 누르고 있을 땐 액션이 등록된 함수들에게 Define.MouseEvent.Press를 인수로 넘겨 실행
      • 아직 사용 X
    • 마우스를 누르다 뗏을 땐 액션에 등록된 함수들에게 Define.MouseEvent.Click을 인수로 넘겨 실행

PlayerController.cs

using Unity.Mathematics;
using Unity.VisualScripting;
using UnityEngine;
using UnityEngine.UIElements;

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

    float _yangle = 0.0f;
    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);
                // 1번 방법
                // if (moveDist > = dir.magnitude)
                //     moveDist = dir.magnitude

                transform.position += dir.normalized * moveDist;
                transform.LookAt(_destPos);
            }
        }

    }

    void OnKeyboard()
    {
        if (Input.GetKey(KeyCode.W))
        {
            // transform.rotation = Quaternion.LookRotation(Vector3.forward); // 월드 기준
            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.LookRotation(Vector3.back);
            transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(Vector3.back), 0.2f);
            transform.position += Vector3.back * Time.deltaTime * _speed;
        }

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

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

    void OnMouseClicked(Define.MouseEvent evt)
    {
        if (evt != Define.MouseEvent.Click)
            return;

        Debug.Log("onmouseclicked");


        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"))) //768;을 사용해서 2의 8승과 2의 9승을 더한 숫자를 입력해도 된ㅏ.
        {
            _destPos = hit.point;
            _moveToDest = true;
        }
        
    }
}
  • _moveToDest : 마우스 클릭 이벤트로 인하여 플레이어가 그 곳으로 이동 되어야 하는 상황인지를 bool형태로써 저장
  • _destPos : 목적지의 위치, 바닥에 마우스 클릭한 곳의 월드 좌표가 목적지가 된다.

Start()

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

OnMouseClicked()

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

  2. evt가 Define.MouseEvent.Click이 아니라면 함수를 종료(return).

    • 즉, 마우스 버튼을 누르고 있는 동안(Press)은 실행되지 않으며, 버튼을 뗐을 때(Click)만 실행됨.
  3. 마우스 클릭 위치에 따라 Ray를 생성하고 충돌 검사 수행

    • 카메라 위치에서 클릭한 화면상의 월드 좌표를 향해 Ray를 발사
    • Wall 레이어를 가진 오브젝트만 감지하도록 설정
    • Ray의 최대 길이는 100
  4. 클릭한 위치에 Wall 레이어가 있다면

    • _destPos를 충돌한 위치(hit.point)로 설정
    • _moveToDest를 true로 변경하여 이동 시작

Update()

  1. 클릭한 위치로 이동해야 하는지 확인

    • 즉, 마우스 버튼을 누르고 있는 동안(Press)은 실행되지 않으며, 버튼을 뗐을 때(Click)만 실행된다.
  2. 목적지가지의 방향 벡트(dir) 계산

    • 현재 위치에서 목표 위치까지의 방향 벡터를 구한다.
      Vector3 dir = _destPos - transform.position;
      	
  3. 목적지 도착 여부 확인

    • 방향 벡터(dir)의 크기가 거의 0이면 즉, 케릭터가 목적지에 도착했으면 이동 중지
    • _moveToDest = false로 설정
      if (dir.magnitude < 0.0001f)
  4. 아직 이동해야 한다면 이동 실행

    • _speed * Time.deltaTime 값을 현재 남은 거리(dir.magnitude)에서 제한

    • 한 번의 Update()에서 너무 많이 이동하지 않도록 조절

      float moveDist = Mathf.Clamp(_speed * Time.deltaTime, 0, dir.magnitude);
    • 이 과정없이 아래와 같인 코드를 작성한다면 목적지를 지나쳤다가 dir가 새롭게 업데이트 되어 다시 돌아오는 과정이 존재할 수 있기에 플레이어가 도착했는데도 버벅이며 움직이는 것처럼 된다.
      그렇기에 위와 같이 moveDist라는 float변수를 만들어 주어 그것만큼 이동하게 하는 것이다.

      transform.position = transform.position + dir.normalized * _speed * Time.deltaTime;
      
  5. 계산된 거리만큼 이동

    • 방향 벡터(dir)를 정규화하여 일정 거리만큼 이동
    transform.position += dir.normalized * moveDist;
    
  6. 이동 방향으로 케릭터 회전

    transform.LookAt(_destPos);

동작원리

1. InputManager.cs에서 이벤트를 정리

  • InputManager 클래스에서 Action 타입의 변수를 만들어 이벤트 시스템 설계
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;
            }
        }
    }
}
  • Action KeyAction : 키 입력 이벤트르 저장하는 변수.
  • Action<Define.MouseEvent> MouseAction : 마우스 이벤트를 저장하는 변수
  • OnUpdate()에서 마우스를 클릭하면 MouseAction.Invoke(Define.MouseAction.Click)이 실행된다.

2. PlayerController에서 MouseAction을 구독
PlayerController에서 InputManager가 감지한 마우스 이벤트를 받으려면 MouseAction에 함수를 등록해야함

void Start()
{
    Managers.input.MouseAction -= OnMouseClicked;  // 기존에 등록된 이벤트 제거 (중복 방지)
    Managers.input.MouseAction += OnMouseClicked;  // 새로운 이벤트 추가
}
  • MouseAction += OnMouseClick;을 실행하면 MouseAction.Invoke(Define.MouseEvent.Click);이 호출될 때, OnMouseClicked()가 자동으로 실행된다.

3. InputManager에서 마우스 입력 발생 -> PlayerController의 OnMouseClicked()실행

마우스를 클릭하면 다음과 같이 동작

  • InputManager의 Onupdate()실행
    • 마우스를 클릭하면 MouseAction.Invoke(Define.MouseEvent.Click);이 호출된
  • 이벤트 발생 -> OnMouseClicked() 자동 실행된다.
    • MouseAction에 OnMouseClicked()가 등록되어 있어서, Invoke()가 실행될 때 자동으로 OnMouseClicked(Define.MouseEvent.Click);가 호출된다.
void OnMouseClicked(Define.MouseEvent evt)
{
    if (evt != Define.MouseEvent.Click)
        return;

    Debug.Log("onmouseclicked");

    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;
    }
}
  • 마우스를 클릭하면 OnMouseClicked()가 실행된다.
  • Raycast를 이용해 클릭한 위치를 찾고 _destPos에 저장한다.
  • _moveToDest = true; 설정하여 Update()에서 이동을 시작한다.

정리하자면
(1) InputManager가 마우스 클릭 감지

  • OnUpdate()에서 MouseAction.Invoke(Define.MouseEvent.Click);이 실행된다.

(2) MouseAction을 구독한 PlayerController의 OnMouseClicked() 실행

  • MouseAction에 OnMouseClicked()가 등록되어 있어서 자동으로 실행된다.

(3) 클릭한 위치를 Raycast로 찾고 _destPos에 저장

  • _moveToDest = true;가 되어 Update()에서 이동 로직이 실행된다.

(4) Update()에서 _moveToDest가 true이므로 캐릭터가 클릭한 곳으로 이동

  • 케릭터가 _destToPos로 이동 후 transform.LookAt(_destPos);로 클릭한 방향을 바라본다.

Clamp()

float moveDist = Mathf.Clamp(_speed * Time.deltaTime, 0, dir.magnitude);

이 코드에서 사용된 Clamp()함수는

public static int Clamp (int value, int min, int max);

와 같이 생겼으며, 이것이 의미하는 것이 value값이 min보다 작으면 min으로 설정하고 max보다 크면 max로 설정한다는 것이다.


참조
https://ansohxxn.github.io/unity%20lesson%202/ch5-2/

profile
코(딩)(꿈)나무

0개의 댓글