AI Navigation을 이용해 마우스로 플레이어 움직이기

devKyoun·2024년 10월 15일
0

Unity

목록 보기
6/27
post-thumbnail

플레이어의 움직임을 제어하는 여러가지 방법 중, 마우스 클릭을 통해 플레이어를 움직이려고 한다

예: 리그 오브 레전드, 스타크래프트, 워크래프트 등

세팅을 해보자

해당 프로젝트는 쿼터뷰 (시점고정) 게임이다.

초록색 캡슐 ➡️ Player
빨간색 고글(?) ➡️ Player의 방향
파란색 기둥 ➡️ 장애물

클릭한 지점으로 플레이어가 바라 보는지 확인하기 위해 빨간색 고글을 생성함

해당 클릭 지점에 대한 Vector3 Position 확보

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 도출

해당 지점으로의 이동은 AI Navigation 이용

PlayerMoveHandler.CS의 UpdateAgent() 함수

public void UpdateAgent(){

		if (Physics.Raycast(ray, out RaycastHit hit, Mathf.Infinity, navLayerMask))
		{
			agent.SetDestination(hit.point);
		}
}

플레이어에 Nav Mesh Agent 컴포넌트 설정

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를 높은 수치로 해놔도 목표 지점으로 플레이어가 바라보는게 굉장히 부자연스럽고 이상함

이 기능을 꺼버린 대신 이동 시 해당 목표 지점을 바라보는 코드를 작성

해당 지점을 바라보기

PlayerMoveHandler.CS의 UpdateRotation() 함수

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에서 호출

이제 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을 진행하고 그 외에는 진행하지 않게끔했다

이렇게 마우스를 통해 움직이는 부분을 구현했다

profile
Game Developer

0개의 댓글