본문 바로가기
프로그래밍/유니티

[Unity NavMesh] 확장: 움직이는 길을 네비게이션 메쉬 영역으로 설정하기 (3 - 메쉬와 함께 이동)

by 김시루시루르 2021. 11. 24.

정말 긴 시리즈물이 되고있어

사실 번호를 붙이라면 5번도 거뜬히 넘을 수 있을 것 같지만..

아마 다음엔 메쉬 영역보다는 다른 쪽에 관계된 얘기일 것 같아서 3번이 마지막이지 않을까?

 

아래는 2번 글.

 

[Unity NavMesh] 확장: 움직이는 길을 네비게이션 메쉬 영역으로 설정하기 (2 - 이동 제한)

바로 전 글이었던 "움직이는 길을 네비게이션 메쉬 영역으로 설정하기 (1)"에서 이어지는것입니다. 거기서 언급했던 문제들이 있는데요... 2번에선 예정대로 그걸 써볼까 하고 [Unity NavMesh] 움직이

drybone-developer.tistory.com

 

 

이번에 쓸 문제는 1번 글에서 얘기했던 것 중 2번 문제에 대한 이야기입니다.


가야 할 곳을 가지 않는 캐릭터

사실 이 문제는 순전히 레테이니까 발생하는 문제이긴 한데요

캐릭터가 이동할 수 있는 메쉬 위에 있고, 그 메쉬가 자체적으로 이동이 가능하다면 이런 문제가 있을겁니다.

예를 들면.. 주기적으로 왔다갔다 하는 발판이라든가?

여기에 캐릭터가 타고 있으면 캐릭터도 발판과 함께 움직여야 하잖아요.

 

그런데 네비메쉬를 사용하면, 에이전트는 기본적으로 네비메쉬 위만 걸을 수 있기 때문에

A지점에서 B지점으로 가는 발판이 있는데, 그 사이가 베이크가 안 돼있다? 그러면

캐릭터는 평온하게 A지점에 서있고 발판만 움직이는 사태가 발생합니다 ㅋㅋㅋ

이는 바로 2번 글에서 썼던 "가지 말아야 할 곳을 간다"는 문제와 충돌해서

말하자면 두 배의 문제가 되어버립니다 (끔찍하죠)


1. 먼저 해야할 것: 캐릭터를 같이 이동시켜주기

우선 캐릭터를 움직이는 메쉬와 같은 속도로 이동시켜줘야 합니다.

그러기 위해선 현재 이동하는 메쉬 위에 캐릭터가 있는지 없는지부터 확인을 해야하고요.

 

1-1. Raycast로 바닥 종류 판정

그래서 저는 .. 캐릭터의 발밑에 있는 오브젝트가 뭔지 구별하는 코드를 썼습니다.

이 코드는 아래 글에 나오는 코드의 확장판입니다.

 

[Unity NavMesh] 목적지 설정 + α

이 글은 네비메쉬의 목적지를 지정하는 코드와.. 직전까지 썼었던 '움직이는 길을 네비메쉬로 베이크하기' 에서 "결국 막다른길에서 어떻게 움직임을 멈추는가?" 에 대한 이야기입니다. 제목 뭐

drybone-developer.tistory.com

이 글에서는 "현재 캐릭터가 서 있는 오브젝트"를 받아오기 위해 레이캐스트를 썼었는데,

여기엔 그 레이캐스트에서 한 걸음 더 나아가서 "오브젝트 종류"를 구별하는 코드를 추가했습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
RaycastHit hitCurCube;
Ray rayCurCube = new Ray(transform.position + new Vector3(0.1f), -transform.up);
Debug.DrawLine(rayCurCube.origin,
        rayCurCube.origin + rayCurCube.direction * .5f, Color.blue);
 
if (Physics.Raycast(rayCurCube, out hitCurCube, .5f, layerMask))
{
    curOnCube = hitCurCube.transform.gameObject;
    GameManager.instance.PlayerOnCube = curOnCube;
 
    //움직이는 큐브 위에 서 있을 경우
    if (hitCurCube.transform.tag.Equals("MoveLand"))
        GameManager.instance.isOnMoveCube = true;
    else
        GameManager.instance.isOnMoveCube = false;
}
cs

위 글의 코드와 비교해보시면 Land 태그로 구별하던 부분이 없어지고, 12번줄 이후가 추가되었습니다.

태그 구별은 6번 줄 if문에서 레이어마스크(네비메쉬 레이어)로 구별을 해 주고,

그 중에서 "움직이는 땅" 태그를 가진 오브젝트 위에 있을 때만 계산을 하도록 했습니다.

GameManager에 값을 저장하고, 이 값을 토대로 큐브가 이동할 때 캐릭터의 이동 여부를 정합니다.

 

만약 이처럼 움직이는 땅에서만 같이 움직인다! 는 조건을 넣지 않으면

모든 큐브 이동에서 캐릭터가 같이 움직이는 사고가 발생하므로(...)

이것은 말하자면 제가 겪은 그 시행착오의 결과라고 할 수 있겠군요. (그땐 정말 깜짝 놀랐다)


1-2. 캐릭터를 동시에 움직이기

이제 캐릭터를 움직여줍니다.

저는 움직이는 땅이 움직이는 부분 코드에 아래의 코드를 추가했습니다.

이 스크립트는 캐릭터 스크립트와는 다른 스크립트로, 캐릭터 오브젝트를 찾아서 참고하는 식.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//플레이어가 이동하는 큐브 위에 있을때 같이 움직이기
if (GameManager.instance.isOnMoveCube == true
    && GameManager.instance.curSelectCube == GameManager.instance.PlayerOnCube)
{
    Vector3 destinationCubePos = 목적지 큐브의 좌표;
 
    //y값이 같은 이동
    if (destinationCubePos.y
        == GameManager.instance.PlayerOnCube.transform.position.y)
    {
        player.transform.position =
            Vector3.MoveTowards(player.transform.position,
                new Vector3(destinationCubePos.x, playerY, destinationCubePos.z),
                    step);
    }
    //y값이 다른 이동
    else
    {
        player.transform.position =
            Vector3.MoveTowards(player.transform.position,
                new Vector3(destinationCubePos.x,
                    destinationCubePos.y + .5f, destinationCubePos.z),
                        step);
    }
}
cs

전후좌우로 움직일 경우는 y값이 똑같으니 플레이어의 현재 y값을 계속 유지해주면 되지만,

상하로 움직일 경우는 y값이 달라서 큐브 피봇~캐릭터 피봇만큼의 거리를 계속 더해줘야 합니다.


2. 그러나

코드를 이렇게 추가해도 실제로 작동해보면 잘 되지 않습니다.

왼쪽처럼 베이크해서 위의 코드를 추가해도 오른쪽과 같은 결과가 나옵니다.

왜 그럴까요!

 

네비메쉬 에이전트는 기본적으로 베이크된 길만을 움직일 수 있게 설정되었기 때문에,

코드에서 transform을 통해 position을 직접 조절해줘도 베이크 영역 밖으로 나가지 못하는 것입니다.

 

여기서 또 머리를 굴렸죠 (?)

그래서 대충 두 가지 방법을 또 생각해냈습니다.


2-1. 두 지점 사이를 전부 베이크하기

극단적인 해결방법이지만 이런 방법이 있습니다.

물론 저는 이 방법을 채용하지 않았습니다.

이동가능한 지점 사이를 다 메우려면 메쉬 배치 난이도가 점점 극악으로 치닫거든요

 

어쨌든 이렇게 베이크하면 잘 되긴 합니다.


2-2. 리지드바디 겸용

이것도 제법 극단적인 방법인듯 .. 근데 저는 이 방법을 써서 했습니다.

네비메쉬가 문제라면 그동안 네비메쉬를 꺼주면 되는거 아닌가? 하고...

근데 평소엔 네비메쉬가 리지드바디와 콜라이더 역할을 모두 해 주는 탓인지

리지드바디 추가 없이 그저 에이전트 컴포넌트를 꺼버리면 똑같은 코드를 써도 결국 잘 되지 않더라고요

그래서 .. 에이전트를 끌 동안 그걸 대체할 수 있는 리지드바디를 추가하게 된 것입니다.

 

단 평소엔 네비메쉬로 캐릭터를 조작하기 때문에,

이동 상황이 아니면 리지드바디는 비활성화될 필요가 있습니다.

그런데 리지드바디는 임의로 enabled를 켜고 끌 수가 없어요!

 

그래서 enabled = false 대신.. Is Kinematic을 켜주면 됩니다.

즉 평소엔 Is Kinematic = true 상태를 유지하다가, 리지드바디가 필요해지면 (네비메쉬 에이전트를 끌 때)

그 때 Is Kinematic = false로 리지드바디 연산을 해주는 것이죠.

1
2
3
4
5
6
7
8
9
10
11
12
13
public void ChangeState()
{
    if (/* 이동중일 때*/)
    {
        GetComponent<Rigidbody>().isKinematic = false;
        charagent.enabled = false;
    }
    else
    {
        GetComponent<Rigidbody>().isKinematic = true;
        charagent.enabled = true;
    }
}
cs

이런 코드를 캐릭터 스크립트에 쳐두고 필요할때 (이동 때)마다 호출해주면 편리합니다.

혹은 이동하는 코드에서 캐릭터의 NavMesh 컴포넌트를 참고해서 바꿔주셔도 되고요.

단 네비메쉬 에이전트를 참고하려면 using문에 UnityEngine.AI를 포함해야 합니다.

 

그래서, 이 코드를 호출하는 것까지 포함하면 최종적으로 이런 코드가 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
player.GetComponent<CharacterMovement>().ChangeState();
 
//플레이어가 이동하는 큐브 위에 있을때 같이 움직이기
if (GameManager.instance.isOnMoveCube == true
    && GameManager.instance.curSelectCube == GameManager.instance.PlayerOnCube)
{
    Vector3 destinationCubePos = 목적지 큐브의 좌표;
 
    //y값이 같은 이동
    if (destinationCubePos.y
        == GameManager.instance.PlayerOnCube.transform.position.y)
    {
        player.transform.position =
            Vector3.MoveTowards(player.transform.position,
                new Vector3(destinationCubePos.x, playerY, destinationCubePos.z),
                    step);
    }
    //y값이 다른 이동
    else
    {
        player.transform.position =
            Vector3.MoveTowards(player.transform.position,
                new Vector3(destinationCubePos.x,
                    destinationCubePos.y + .5f, destinationCubePos.z),
                        step);
    }
}
 
player.GetComponent<CharacterMovement>().ChangeState();
cs

이렇게 하면 메쉬가 이동할 때 에이전트의 네비메쉬 컴포넌트가 리지드바디로 전환되므로

문제 없이 캐릭터를 도착지점까지 움직일 수 있습니다.


휴..

이렇게 제가 프로젝트에서 어떤 꼼수를 썼는지 낱낱이 드러내니 굉장히 미묘한 기분이군요

하지만 여러분의 개발이 편안하다면 그것으로 OK입니다.

 

다음에 이거 관해서 글 쓸 때는 와 님들아 저 이런거 개발했어용 이런거면 좋겠네요

댓글