카메라
롤이나 스타처럼 클릭한 곳으로 플레이어와 카메라가 따라가게 하기. 바닥에 마우스를 클릭하면 클릭한 곳으로 플레이어가 자동으로, 스스로 이동하게 한다.
방법 소개
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);
'공부 > 인프런 - Rookiss' 카테고리의 다른 글
Part 3-6-1. 애니메이션 : 기초, 블렌딩 (0) | 2023.08.22 |
---|---|
Part 3-5-3. 카메라 : 카메라가 벽에 막힐시, 벽 앞으로 카메라 이동시키기 (1) | 2023.08.21 |
Part 3-5-1. 카메라 : 카메라가 플레이어를 따라다니게 만들기 (0) | 2023.08.21 |
Part 3-4-2. Raycast, LayerMask, Tag, 투영의 개념 (0) | 2023.08.21 |
Part 3-4-1. Collider, Collision, Trigger (0) | 2023.08.17 |