유니티에서 오브젝트를 곡선 및 직선 경로를 따라 움직이게 만드는 방법은 무엇일까?

아마 가장 쉽고 직관적인 방법은 애니메이션을 사용하는 것일것이다.

하지만 애니메이션을 통한 구현 방법은 아래와 같은 문제점이 있다.

문제점

1 . 키프레임 사이를 원하는 방법대로 조절하기 어렵다.

image

빨간 네모에서 파란 네모로 움직이는 애니메이션,

즉 Position의 변화를 애니메이션으로 만든다고 하면

경로는 위 그림처럼 무수히 많이 존재 할 수 있는데,

애니메이션에서는 항상 가장 빨리 변할 수 있는 방향 (주황색 패스) 으로만 정해진다.

2 . 일정한 속도로 움직이기 어렵다

image

만약 위 그림처럼 빨간 네모에서 초록색 네모로, 그 후 파란색 네모로 움직이는 패쓰가 있다고 하자.

이 때 각 지점사이의 거리가 같지 않다고 할 때,

애니메이션 키프레임이 다음과 같이 분포한다면 (1:1 로)

image

두 부분에서 물체가 움직이는 속도는 다를것이다.

따라서 위 상황처럼 s1과 s2가 같지 않을 때 일정한 속도의 애니메이션을 만들고 싶다면,

S1 : S2 = ( t1 - t0 ) : ( t2 - t1 ) 이 되도록 키프레임을 제어해야 한다.

물론 시간을 많이 투자하면 가능하겠지만,

경로가 구부러지거나 그 수가 많아 비율을 계산하기 힘든 상황에선 불편 할 수 밖에 없다.



베지어 곡선

위에서 알 수 있는 문제점 중 공통된 사항은 ‘곡선’이다.

어떻게 하면 곡선을 나타 낼 수 있을까 찾아보다가, 베지어 곡선(Bézier curve)이라는 것을 찾아내었다.

image

이론

일단 직선을 움직이는 점을 생각해본다.

image

하나의 직선이 있고 그 위를 점 M이 일정 속도로 이동하고 있다.

이 점 M의 궤적은 당연하지만 단순한 직선으로 그려진다.

이때 t는 선분 위를 비율적으로 얼마나 나아갔는지를 나타내는 수치다.

여기에 선을 하나 더 추가하고 그 위에 M처럼 이동하는 점을 놓아본다.

그리고 원래의 점 M을 M0로, 새로운 점을 M1으로 한다.

M0와 M1이 움직이는 규칙은 이전과 같다.

M1이라는 점이 하나 더 늘었다 하더라도 특별히 복잡해질 것은 없다.

image

여기에서 M0와 M1을 잇는 선을 하나 더 그을 수 있다.

그 선은 M0와 M1이 이동하면 자연스럽게 함께 움직이게 된다.

그 선 위에 M0나 M1처럼 일정 속도로 이동하는 점을 놓을 수 있다. 그 점을 B라고 하자.

그리고 점 B가 그리는 궤적을 살펴보면, 곡선이 되는 모습을 볼 수 있다.

점 B가 그리는 궤적을 2차 베지어 곡선(Quadratic Bezier Curve)이라고 한다.

아래는 시간 t에 대한 베지어 곡선 식이다.

image

조절점을 늘릴수록 3차, 4차, 5차 .. 베지어 곡선을 만들 수 있다.

차수가 늘어날 수록 정교하거나 복잡한 곡선을 만들 수 있지만,

이번 프로젝트에서는 2차 베지어 곡선만으로도 충분하다고 생각했다.

아래는 3차 베지어 곡선이다.

image



스크립트

그래서…

이제는 애니메이션이 아닌 스크립트로 베지어 곡선을 만들어서

자동차가 달릴 트랙을 만들기로 하였다.

  • PathGenerator.cs : 자동차를 지정된 Path대로 움직이는 스크립트
  • PathGeneratorGUI.cs Play버튼을 누르기 전, 에디터 상에서 Path를 그리는 스크립트

쓸 수 있는 스크립트는 PathGenerator인데, Inspector Window에서 조정 가능한 파라미터는

아래와 같다.

image

이름 타입 설명
Speed float 자동차의 스피드. 추천 : 1800f
Flag GameObject 사용자가 패스를 만들때 쓰는, 자동차가 지나가게 되는 트랙의 연결부분 프리팹
Angle GameObject 베지어 곡선을 만들 때 쓰이는 조절점 프리팹
Target GameObject 자동차 오브젝트. Scene에서 드래그해서 넣으면 된다.
Guide GameObject 디버그 시 자동차가 진행할 방향을 표시해줄 오브젝트의 프리팹
FlagRoot GameObject 트랙을 만들 때 사용되는 플래그들의 부모 오브젝트
AngleRoot GameObject 곡선을 만들 때 사용되는 조절점들의 부모 오브젝트
IsClosed bool 트랙이 닫힌회로 (무한루프)인지 체크
IsDebug bool 디버그를 하는건지 체크
FlagList List<GameObject> 실제로 트랙을 구성하는 플래그들의 집합
AngleList List<GameObject> 실제로 트랙을 구성하는 조절점들의 집합
PathDensity int 곡선의 밀한 정도를 나타내는 수치. 추천 50
(많아지면 계산량 증가)

파라미터를 알맞게 조정하고,

Scene에서 오브젝트들을 잘 조절하면 주어진 트랙에 맞는 패스를 쉽게 그릴 수 있었다.


곡선 제작

아래는 에디터에서의 화면이다.

01

이처럼 Path를 쉽게 그릴 수 있다.

02

PathDensity를 조절하면 곡선이 부드러울지 뚝뚝 끊길지 정할 수 있다.

너무 적을 경우 곡선이 아니라 직선처럼움직이게 된다.

등속력 운동

image

아래처럼 만들어진 곡선을 따라서 같은 속력 ( offset * speed * Time.deltaTime )으로 움직이기 때문에

곡선이던 직선이던 상관없이 부드럽게 움직인다.


주행 테스트

ezgif-4-bcd2baab135c

ezgif-4-976a33e36dfc

확인해 본 결과 아주 잘 달리는 모습이다.

GIF로 업로드하던 도중 차가 살짝 중앙에서 치우쳐서 달리는 모습이 보이긴 하는데

애니메이션과 다르게 Path의 해당 앵글을 조금 손 보면 되기 때문에 금방 수정 가능하다.


남은 것

이제 핸들과 바퀴의 회전이 남았다.

해당 이슈에 대해서도 어느정도 생각이 있는다.

Quaternion의 Slerp를 이용하면 될 것 같은데,

자세한 사항은 다음에 업로드 하도록 하겠다.