정말 긴 시리즈물이 되고있어
사실 번호를 붙이라면 5번도 거뜬히 넘을 수 있을 것 같지만..
아마 다음엔 메쉬 영역보다는 다른 쪽에 관계된 얘기일 것 같아서 3번이 마지막이지 않을까?
아래는 2번 글.
이번에 쓸 문제는 1번 글에서 얘기했던 것 중 2번 문제에 대한 이야기입니다.
가야 할 곳을 가지 않는 캐릭터
사실 이 문제는 순전히 레테이니까 발생하는 문제이긴 한데요
캐릭터가 이동할 수 있는 메쉬 위에 있고, 그 메쉬가 자체적으로 이동이 가능하다면 이런 문제가 있을겁니다.
예를 들면.. 주기적으로 왔다갔다 하는 발판이라든가?
여기에 캐릭터가 타고 있으면 캐릭터도 발판과 함께 움직여야 하잖아요.
그런데 네비메쉬를 사용하면, 에이전트는 기본적으로 네비메쉬 위만 걸을 수 있기 때문에
A지점에서 B지점으로 가는 발판이 있는데, 그 사이가 베이크가 안 돼있다? 그러면
캐릭터는 평온하게 A지점에 서있고 발판만 움직이는 사태가 발생합니다 ㅋㅋㅋ
이는 바로 2번 글에서 썼던 "가지 말아야 할 곳을 간다"는 문제와 충돌해서
말하자면 두 배의 문제가 되어버립니다 (끔찍하죠)
1. 먼저 해야할 것: 캐릭터를 같이 이동시켜주기
우선 캐릭터를 움직이는 메쉬와 같은 속도로 이동시켜줘야 합니다.
그러기 위해선 현재 이동하는 메쉬 위에 캐릭터가 있는지 없는지부터 확인을 해야하고요.
1-1. Raycast로 바닥 종류 판정
그래서 저는 .. 캐릭터의 발밑에 있는 오브젝트가 뭔지 구별하는 코드를 썼습니다.
이 코드는 아래 글에 나오는 코드의 확장판입니다.
이 글에서는 "현재 캐릭터가 서 있는 오브젝트"를 받아오기 위해 레이캐스트를 썼었는데,
여기엔 그 레이캐스트에서 한 걸음 더 나아가서 "오브젝트 종류"를 구별하는 코드를 추가했습니다.
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입니다.
다음에 이거 관해서 글 쓸 때는 와 님들아 저 이런거 개발했어용 이런거면 좋겠네요
댓글