본문 바로가기

언리얼 개발자

[Unreal] Smooth Path Following using Spline

 이전 포스팅에서는 이동 인풋에 이용할 곡선 데이터를 만드는 법을 알아보았습니다.

https://onecoke.tistory.com/entry/Unreal-Smooth-Dynamic-Spline-using-PathFinding-Points

 

[Unreal] Smooth Dynamic Spline using PathFinding Points

이전 시간까지는 비동기 길 찾기에 대해서 알아보았습니다. https://onecoke.tistory.com/entry/Unreal-%EB%B9%84%EB%8F%99%EA%B8%B0Async-PathFinding [Unreal] 비동기[Async] PathFinding https://onecoke.tistory.com/entry/Unreal-PathFinding

onecoke.tistory.com

 

 이번 포스팅에서는 실제로 부드럽게 이동하는 로직을 구현해볼 차례입니다.

곡선 데이터에서 뽑아와야 하는 데이터는 캐릭터로부터 가장 가까운 곡선 위 점의 이동 방향입니다.

따라서 아래의 과정이 필요합니다.

1. 현재 가장 가까운 Spline 지점 가져오기

2. 가져온 Spline 지점의 이동 방향 가져오기

 모두 언리얼 엔진에서 제공해주고 있으니 걱정 안해도 됩니다.

1번에 매칭되는 함수 : USplineComponent::FindInputKeyClosestToWorldLocation()

2번에 매칭되는 함수 : USplineComponent::GetDirectionAtSplineInputKey()

 

 이 함수들을 통해서 가장 가까운 곡선 위 점의 이동 방향을 가져올 수 있게 되었습니다. 그럼 이제 이동 인풋을 곡선으로만 주면 될까요? Before, After를 위해 직선 이동 영상을 먼저 보여드린 후 곡선 이동의 영상을 보겠습니다.

 

직선 이동 ( 언리얼 기본 )

 역시나 캐릭터가 딱딱하게 직선형으로 이동하는 것을 확인할 수 있습니다.

 

[ 곡선 Input 이동 ]

곡선 이동 ( 곡선 인풋 )

 첫 번째 영상과 비교했을 때 캐릭터의 이동 루트가 상대적으로 부드러워진 것을 확인할 수 있습니다. 이 GIF만 보면 여기서 구현을 끝내도 될 것 같지만 아쉽게도 치명적인 문제가 있습니다.

곡선 이동의 문제점

 보이시나요? 캐릭터가 이동 중 위치가 변경되었을 때 캐릭터는 벽에 부딪혀 이동이 막히게 됩니다. 원하는 기능은 길 찾기 결과로 나온 루트를 부드럽게 이동하는 것이지, 단순히 부드럽게 이동시키는 것이 전부가 아닙니다. 부드러움에 빠져 주객전도가 된 모습입니다.

 더군다나 위와 같이 길 찾기 경로가 고려되지 않으면 언리얼의 PathFollowingComponent를 이용할 것이기 때문에 경로 포인트 도착 로직이 제대로 동작하지 않을 것입니다. 저희는 경로 포인트를 향한 인풋을 반드시 고려해야 합니다.

바로 생각나는 방법은 곡선 인풋과 경로 포인트를 향한 인풋을 섞어주는 것입니다. 아래의 영상에서 결과를 보겠습니다.

 

[ 곡선 + 직선 Input 이동 ]

곡선 + 직선 이동의 효과

 곡선 인풋에 목적지를 향한 직선 인풋을 섞어주었을 뿐인데 캐릭터가 외부요인으로 인한 위치 변경에도 적절하게 길을 따라 가는 모습입니다. 물론 길 찾기를 다시 수행한 건 아니기 때문에 장애물이 대놓고 가로막으면 길 찾기 재수행이 필요할 것입니다. 일단 소프트한 위치 변경에는 대응이 되는 것을 확인할 수 있습니다. 하지만 전체적인 이동의 모습은 다시 어색해집니다. 

곡선 + 직선 이동

 직선보다는 낫지만 역시 중간에 끊기는 모습들은 어색합니다. 이 어색함을 덜어내려면 어떻게 해야 할까요?

 

[ 곡선 + 직선 Input 이동 + 캐릭터 전방 보간 ]

 어색한 원인은 캐릭터가 바라보는 방향이 순간적으로 바뀌기 때문입니다. 먼저 캐릭터의 MovementComponent의 설정을 살펴보면 캐릭터가 이동하는 방향을 전방으로 사용하는 Orient Rotation To Movement 가 true이고, Yaw 회전 속도는 720 이라는 빠른 속도로 설정되어 있는 것을 볼 수 있습니다.

캐릭터 무브먼트 설정

설정에 따르면 캐릭터가 바라보는 방향은 이동 인풋에 의해 계산된 Velocity의 방향을 따르게 됩니다. 이동 인풋을 부드럽게 보간하지 않았기 때문에 나오는 어색함인 것입니다. 즉 이동 인풋에 현재 캐릭터의 전방 방향을 고려해주면 보다 부드러운 결과가 나옵니다. 먼저 결과부터 보겠습니다.

곡선 + 직선 이동과 캐릭터 전방 보간

 '곡선 + 직선 이동' 영상과 비교했을 때 좀 더 부드러워진 것이 느껴지나요? 전 대부분의 이동 인풋들을 전방과 보간을 합니다. 곡선 데이터가 없어도 전방과 보간만 해주면 이동이 좀 더 부드러워지거든요.

 제가 위에서 여러 인풋들을 섞고 보간한다 라고만 말씀드렸는데 적용시킨 방법에 대해 말씀드리겠습니다.

 

[ 인풋 섞고 보간하기 ]

 [ 인풋 섞고 보간하는 거 설명하기 전에, 그 로직들은 어디에 구현되었는가? ]

 제가 인풋에 대한 설명만 하다보니 정확히 어디에 구현되었는지는 말씀을 안드렸네요.

인풋을 섞고 보간하는 로직은 PathFollowingComponent 를 상속한 클래스를 만들고 FollowPathSegment() 함수를 override 하여 안에 구현했습니다. Super는 부르지 않았습니다.

 

 [ 곡선 Input, 직선 Input 섞기 ]

먼저 두 인풋을 섞는 방식은 기호에 따라 달라집니다. 정확한 경로 이동을 바라느냐, 더 부드러운 경로 이동을 바라느냐에 따라 어떻게 섞을 지가 정해집니다.

 

곡선 인풋 + 직선 인풋 코드

 빨간색 박스 내에 있는 코드가 인풋을 섞는 방식입니다. 그리고 노란색 박스 안에 있는 코드가 직선 이동을 얼마나 사용할 지 비율이라고 보시면 됩니다. 현재 0.5로 값이 설정되어있는데 이 뜻은 곡선 인풋을 1만큼, 직선 인풋을 0.5만큼 사용하겠다는 의도였습니다. 만약 더 정확한 경로 이동을 바란다면 0.5보다 더 큰 값을 적용시키면 됩니다. 경로가 덜 부드러워지는 것이지, 캐릭터 이동 자체의 부드러움이 손상되는 것은 아니므로 기호에 따라 설정하시면 될 것 같습니다.

 

 [ 캐릭터 전방과 보간하기 ]

캐릭터 전방과 보간

 계산된 이동 인풋과 캐릭터의 전방을 보간하는 코드입니다. 저는 언리얼에서 제공하는 FMathVInterpTo를 이용했습니다. 첫 번째 인자로 들어가는 것이 현재 Vector, 두 번째 인자로 들어가는 것이 목표 Vector 입니다. 코드 상 현재 Vector를 캐릭터의 전방으로, 목표 Vector를 계산된 이동 인풋으로 두었습니다. 보간 속도는 InterpSpeed 를 따릅니다. VInterpTo  함수 코드를 보시면 DeltaTime과 InterpSpeed를 가지고 어떻게 보간하는지 나와있고 복잡하지 않아 설명은 생략하도록 하겠습니다.

 

 보간 속도(InterpSpeed) 와 직선 인풋을 얼마나 사용할 지에 대한 비율 값(UsingDestInputRatio)이 각각 매직넘버로 박혀있습니다. 이 값을 에디터 상에서 설정할 수 있게 해두면 부드러운 이동을 원하는 사람이 다시 컴파일할 필요 없이 값을 수정하면서 변경하면서 적절한 값을 확인할 수 있습니다. 또한 저렇게 하나의 값이 아니라 특정 상황마다 보간 값을 다르게 하도록 공식을 짜 놓을 수 있겠지요. 중요한 것은 여러 인풋들을 어떻게 조합하느냐입니다. 지금까지 말씀드렸던 건 하나의 방법일 뿐, 정답이 아님을 꼭 아셨으면 좋겠습니다.

 

[ 마치며 ]

 단순했지만 여기까지 길 찾기와 연계한 곡선형 이동을 구현해보았습니다. 마지막 단계는 비동기 길 찾기와 연동하는 것입니다. 이전 포스팅에서 많은 길 찾기로 인해 게임이 끊기지 않으려면 비동기로 처리해야 한다고 했으면서 곡선형 이동의 비동기 처리를 포스팅하지 않으면 모순이니 말이죠. 특히 곡선형 길 찾기는 언리얼에서 제공하는 기능이 아니므로, 비동기 처리 방식이 좀 더 복잡해집니다.

 비동기 곡선형 길 찾기는 적용은 해봤지만 아직 이해가 부족해 블로그에 올리는 것은 이르다는 생각과 특히 다른 스레드를 할당해야 하니 정확한 이해 없이 올리는 건 더욱더 무책임하다는 생각이 들었습니다. 앞으로 정리할 것도 공부할 것도 많아서 순번이 언제 올 진 모르겠지만, 언젠간 비동기 곡선형 길 찾기에 대해서 포스팅하도록 하겠습니다.

 

 곡선형 이동을 하면서 경험했던 예외들을 적으면서 포스팅을 마치겠습니다.

1. 정확한 경로를 따라 이동하지 않기 때문에 너비가 타이트하고 굴곡이 많은 지형에서는 벽에 박을 수 있다.

    -> 레벨 디자인이 좀 더 넓게 이루어지거나, 직선 인풋을 보다 크게 이용하는 방향으로 해결했습니다.

 

2. 전방과 보간하게 되면서 캐릭터가 뒤 돈 상태에서 출발하거나, 마지막 목적지로 향할 때 꼬이는 경우가 있다.

    -> 이 문제를 해결하기 위해 첫 번째 경로 이동과 마지막 경로 이동에서는 보간을 하지 않는 형태로 구현했었습니다.

    -> PathFollowingComponent 코드를 보시면 현재 첫 번째 경로인지, 마지막 경로인지 아실 수 있을 겁니다.

 

[ Git 주소 ]

https://github.com/skyjpower/FunctionTestProject