플레이어의 움직임을 제어하는 여러가지 방법 중, 마우스 클릭을 통해 플레이어를 움직이려고 한다
예: 리그 오브 레전드, 스타크래프트, 워크래프트 등
세팅을 해보자
해당 프로젝트는 쿼터뷰 (시점고정) 게임이다.
초록색 캡슐 ➡️ Player
빨간색 고글(?) ➡️ Player의 방향
파란색 기둥 ➡️ 장애물
클릭한 지점으로 플레이어가 바라 보는지 확인하기 위해 빨간색 고글을 생성함
Ray ray = _mainCamera.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, navLayerMask))
{
// hit가 충돌한 지점에 대한 정보를 가지고 있다.
// hit.point => 그 지점의 Vector3
hit.point
}
메인 카메라 위치 기준에서부터 마우스 클릭한 지점으로 Ray를 쏘고
충돌을 하면 충돌한 오브젝트 정보를 담은 구조체 hit을 통해 Position 도출
public void UpdateAgent(){
if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, navLayerMask))
{
agent.SetDestination(hit.point);
}
}
private void Awake()
{
***************************************
agent = GetComponent<NavMeshAgent>();
agent.updateRotation = false;
***************************************
rigidbody = GetComponent<Rigidbody>();
_mainCamera = Camera.main;
}
PlayerManager.cs
Angular Speed가 0인 부분은 왜냐면 PlayerManager.CS Awake에서 updateRotation을 꺼줌
끈 이유가 Angular Speed를 높은 수치로 해놔도 목표 지점으로 플레이어가 바라보는게 굉장히 부자연스럽고 이상함
이 기능을 꺼버린 대신 이동 시 해당 목표 지점을 바라보는 코드를 작성
public void UpdateRotation()
{
dir = agent.desiredVelocity.normalized;
targetRotation = Quaternion.LookRotation(dir);
rigidbody.rotation = Quaternion.Lerp(rigidbody.rotation, targetRotation,
rotationSpeed * Time.deltaTime)
}
desiredVelocity는 목표지로 향하는 속도를 나타내는데 여기서 방향을 구하는 것
하지만, 한 가지 사소한 문제가 생겼다
잘 보면 플레이어가 회전을 굳이 하지 않아도 되는 상황임에도 불구하고 회전을 하려하는 상황이 많아서 Jittering 발생
public void UpdateRotation()
{
dir = agent.desiredVelocity.normalized;
targetRotation = Quaternion.LookRotation(dir);
if(Quaternion.Angle(targetRotation, rigidbody.rotation) > 7.0f)
{
rigidbody.rotation = Quaternion.Lerp(rigidbody.rotation, targetRotation,
rotationSpeed * Time.deltaTime)
}
}
Quaternion Angle을 통해 굳이 방향을 돌려도 되지 않는 상황이라면 rotation을 하지 않도록 했음
7.0f 보다 어느정도 값이 크더라도 자연스러움
왜 transform rotation을 안했나 transform rotation을 하면 연산하는 단계가 rigidbody보다 훨씬 비용이 많이듦 ( 특히, 모델에 계층구조가 복잡할 경우 더욱 )
사실 모델 계층구조가 복잡한 경우, 해당 Model.FBX를 Optimize Hierarchy 체크 해주면 되긴 함
이제 PlayerManager.cs에서 해당 함수들을 호출함
해당 프로젝트에서 Update, FixedUpdate를 가진 Script는 PlayerManager 하나이다.
성능면에서나 디자인 패턴적으로 괜찮음
public class PlayerManager : MonoBehaviour
{
private PlayerMoveHandler moveHandler;
void Awake()
{
Application.targetFrameRate = 120;
moveHandler = GetComponent<PlayerMoveHandler>();
}
void Update()
{
if (Input.GetMouseButtonDown(1))
{
moveHandler.UpdateAgent();
}
}
void FixedUpdate()
{
if (moveHandler.IsRemain)
{
moveHandler.UpdateRotation();
}
}
}
Update에서 마우스 오른쪽 키를 눌렀을 시에만 길 목표를 갱신 하도록 함
FixedUpdate에서 moveHandler.IsRemain의 여부를 통해 Rotation 하도록 했는데
이는 PlayerMoveHandler에서 Property 기능을 통해 가져온 것
{ .... }
public bool IsRemain { get{ return agent.remainingDistance > agent.stoppingDistance;} }
private void Awake()
{
{ .... }
}
public void UpdateAgent()
{
{ .... }
}
PlayerMoveHandler.cs
Player가 아직 목표 지점까지 남은 거리가 있다면, UpdateRotation을 진행하고 그 외에는 진행하지 않게끔했다
이렇게 마우스를 통해 움직이는 부분을 구현했다