유니티 엔진에 관한 실습을 다 진행했으니, 본격적으로 게임을 만드는 과정에 대해 들어간다.
https://assetstore.unity.com/packages/3d/environments/fantasy/medieval-town-exteriors-27026
무료 에셋인 medieval town exteriors를 임포트했다.
유니티에서 지형을 작성할 때 사용되는 컴포넌트이다.
지형의 높낮이 조절, 지형 평탄화, 텍스쳐 파일을 이용해서 지형 그리기 등이 가능하다.
환경 세팅을 하고, 미리 실습했던 코드와 프리팹들을 에셋에서 제공하는 데모 씬에 옮겨서 실행해 보았다.
하지만 이동이 되지 않는다. 즉 레이캐스팅이 실패하고 있다.
Terrain에 Collider가 없는지 확인한다.->Collider가 있다.
PlayerController의 코드를 확인한다.
if (Physics.Raycast(ray, out hit, 100.0f, LayerMask.GetMask("Wall")))
{
_destPos = hit.point;
_state = PlayerState.Moving;
}
이 부분이 문제였다.
GetMask를 통해 Wall이라는 객체에 Raycast가 반응하기 때문에 레이캐스팅이 실패하고 있는 것이다.
씬의 Terrain과 건물들의 Layer를 모두 Wall로 바꿔준다. 이동이 정상적으로 작동한다.
하지만 빌딩을 클릭했을 때 건물을 등반하는 문제가 생긴다.
Collider의 충돌을 인식하거나, 레이캐스팅을 이용해 해결하거나, Navigation Mesh를 이용해서 길찾기를 먼저 하고 갈 수 없는 곳은 가지 않게 하는 해결 방법들이 있다.
Navigation Mesh를 이용해 해결해 보도록 한다.
Window->AI->Navigation을 통해 Navigation Mesh를 추가 할 수 있다.
추가하면 컴포넌트의 Inspector 옆에 Navigation이 추가된다.현재 씬에 적용하고 싶다면 Bake를 하면 된다. Bake를 하면 현재 씬에서 갈 수 있는 공간을 표시해준다.원리는 생각보다 간단한데, 단순히 플레이어가 등반할 수 있는 각도를 설정한 뒤 그 각도보다 가파른 위치에 Static한 객체가 있다면 그 객체가 있는 곳은 갈 수 없다고 판단한다.
Static한 객체만 판단하는 점을 이용해 객체의 Static을 해제한다면 플레이어가 이동할 수 있는 지형으로 만들 수 있다. 이 상황에서는 버섯과 같은 식물들의 Static을 해제하여 플레이어가 이동할 수 있도록 하였다.
이후에는 Player의 객체에 Nav Mesh Agent라는 컴포넌트를 추가해줘야 한다. 추가한 뒤 PlayerController 코드를 수정한다.
if (dir.magnitude < 0.0001f)
{
_state = PlayerState.Idle;
} // 목적지까지의 거리가 매우 작다면(도착했다면) 이동중이라는 상태를 false로 만든다.
else
{
float moveDist = Mathf.Clamp(_speed * Time.deltaTime, 0, dir.magnitude);
// 목적지 좌표에 도착하고도 계속 rotation을 변경하며 목적지에 가려는 상황을 없애기 위해
// 목적지 까지의 거리보다 속도가 높아지면 속도를 0으로 만든다.
NavMeshAgent nma = gameObject.GetOrAddComponent<NavMeshAgent>();
nma.Move(dir.normalized * moveDist);
// transform.position += dir.normalized * moveDist; 이 부분을 nma.Move()로 대체한다.
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(dir), 10 * Time.deltaTime);
}
이러니 이번에는 건물을 등반하지는 않지만 클릭한 곳에 도착할 때와 못가는 지형을 클릭했을 때 계속 뛰는 애니메이션이 재생되는 오류가 발생한다.
먼저 클릭한 곳에 도착할 때 계속 뛰는 모션이 나오는 오류는 if (dir.magnitude < 0.0001f) 부분이 제대로 작동하지 않기 때문이라는 것을 알 수 있다.
기존에는 position이라는 변수에 값을 더해 정확한 값을 position에 저장할 수 있었지만 Move() 함수를 사용하면서 정확한 값을 계산할 수 없다는 것이 문제였다.
if (dir.magnitude < 0.1f)
{
_state = PlayerState.Idle;
} // 목적지까지의 거리가 매우 작다면(도착했다면) 이동중이라는 상태를 false로 만든다.
if 문 안의 값을 0.1f로 만들어서 값에 유예를 주니 해결되었다.
못가는 지형을 클릭했을 때 계속 뛰는 애니메이션이 재생되는 오류는 레이캐스팅과 관련된 오류였다.
레이캐스트가 빌딩에 Hit하기 때문에 플레이어는 계속 빌딩으로 이동하려 하지만, Navigation Mesh가 못가게 막는 서로의 간섭때문에 이러한 오류가 계속 발생한 것이다.
빌딩의 Layer를 Wall로 하면서 레이캐스트가 빌딩에 Hit 하도록 한 이유는 플레이어와 카메라 사이가 빌딩으로 막힐 때를 위해서 였지만, 그로 인해 계속 오류가 나기 때문에 빌딩의 Layer를 바꿔 레이캐스트가 Hit 하지 않도록 해준다.
플레이어와 카메라 사이가 빌딩으로 막힐 때의 경우는 후에 다시 생각하도록 한다.
못가는 지형을 클릭했을 때 처음보단 훨씬 자연스럽게 이동하게 되었다. 하지만 계속해서 뛰는 모션이 나오는 점은 변함이 없다.이는 건물 바닥에 있는 Terrain에 레이캐스트가 Hit 하고, 그 위치로 플레이어는 계속 이동하려 하지만 Navigation Mesh가 못가도록 막기 때문에 발생하는 오류였다.해결을 위해서는 플레이어 앞으로 레이캐스팅을 하여 Hit 하는 경우 멈추게 하는 방법이 있고, 길찾기 알고리즘을 통해 플레이어를 이동하는 방법이 있다. 이 게임에서는 앞으로 레이캐스팅을 하는 방식으로 해결해 보도록 한다.
먼저, 플레이어가 통과할 수 없을것 같은 객체들의 Layer를 모두 Block 레이어로 바꿔준다.그 후에 PlayerController 코드를 수정해 준다.
NavMeshAgent nma = gameObject.GetOrAddComponent<NavMeshAgent>();
nma.Move(dir.normalized * moveDist);
Debug.DrawRay(transform.position + Vector3.up * 0.5f, dir.normalized, Color.green);
if(Physics.Raycast(transform.position + Vector3.up * 0.5f, dir, 1.0f, LayerMask.GetMask("Block")))
{
_state = PlayerState.Idle;
return;
}
플레이어의 배꼽 위치 정도에서 1.0f 길이의 레이캐스팅을 해서 만약 Block 레이어에 Hit 하면 모션을 Idle로 바꾸고 멈추도록 하였다.
이후 모든 오류가 해결되었음을 확인했다.
(4/18 수정)
마우스를 누르고 있을 때는 계속 움직이는 모션이 나오게 하고 싶다. 따라서 코드를 수정하였다.
NavMeshAgent nma = gameObject.GetOrAddComponent<NavMeshAgent>();
nma.Move(dir.normalized * moveDist);
Debug.DrawRay(transform.position + Vector3.up * 0.5f, dir.normalized, Color.green);
if(Physics.Raycast(transform.position + Vector3.up * 0.5f, dir, 1.0f, LayerMask.GetMask("Block")))
{
if (Input.GetMouseButton(0) == false)
_state = PlayerState.Idle;
transform.rotation = Quaternion.Slerp(transform.rotation, Quaternion.LookRotation(dir), 10 * Time.deltaTime);
return;
}