
오늘은 유니티에서 효율적인 방법으로 컨베이어밸트 physics를 구현해보겠다.
충돌검사와 직선 이동 스크립트를 통해 눈속임으로 쉽게 구현할 수도 있겠지만,
물리적으로 자연스러운 컨베이어 밸트가 구현되면 직선 뿐 아니라, 곡선, 사선, 오르막길 내리막길 등 여러 경우에 쉽게 적용시킬 수 있다는 장점이 있다.
밸트로 사용될 오브젝트를 준비한다. 이 방법에서는 단순한 cube 오브젝트여도 상관없다.
유니티에는 기본적인 물리엔진이 friction(마찰력)을 제공하기 때문에 바닥에 콜라이더가 설정되어있고, 위에 올라와있는 물체에 중력과 콜라이더, rigidbody가 잘설정되어있다면 바닥이 움직일시 자연스럽게 위에 올려져있는 물체도 따라 움직일 것이다.

public float speed = 2.0f; // 이동 속도
void Update()
{
transform.Translate(Vector3.right * speed * Time.deltaTime);
}
예상과는 다르게 바닥의 이동이 바닥위의 오브젝트에 물리적 영향을 주지 못하였다.
Transform.Translate() 함수와, Time.deltaTime으로 프레임 단위로 자연스럽게 바닥을 이동시켰지만,
위 코드대로면
바닥 오브젝트의 위치 정보만 아주 세세하게 바꿔주었을 뿐이고
프레임 당 물리적 작용을 기대하기엔 부적절한 Update()함수를 사용하였다.
Update()함수는 불규칙한 호출로 인해 단순한 키입력, 타이머 등에 적합하다.
파악한 문제들을 해결하고 개선된 결과를 도출해본다.
public float speed = 2.0f; // 이동 속도
Rigidbody rb;
void Start()
{
rb = GetComponent<Rigidbody>();
}
void FixedUpdate()
{
rb.MovePosition(rb.position + Vector3.back * speed * Time.fixedDeltaTime);
}
우선 물리충돌검사에 적합한 FixedUpdate()함수를 활용한다.
컴퓨터 환경에 따라 불규칙하게 호출되는 Update()와 달리 Fixed Timestep에 설정된 값에 따라 일정한 간격(Default : 0.02sec)으로 호출되기 때문에 물리 효과가 적용된(Rigidbody) 오브젝트를 조정할 때 사용된다
단순 위치정보 수정이 아닌, 프레임 사이사이에 물리적 충돌이 있다면 그부분을 물리적으로 밀어내는 Rigidbody.MovePosition() 함수를 사용한다. 이를 위해서 코드에선 GetComponent()를 통해 따로 선언하였다.
Vector3.right -> Vector3.back 기존 Translate()함수는 오브젝트 자체의 모델좌표계 기준이지만, MovePosition()은 월드좌표계 기준이므로 그에 맞춰 방향이 수정된 것.
deltaTime -> Time.fixedDeltaTime 앞서 설명한 Fixed Timestep을 보정하기 위해 쓰이는 값.

여기까지 했으면 어떤 방식이로든 컨베이어밸트는 구현될것이다. 하지만 그 중 가장 부담이 적은 방법으로 구현해보겠다.
예를 들면 바닥부분에 해당하는 모든 요소에 디테일한 콜라이더를 적용시켜서 돌리거나,
윗 부분만 보이게 한 후 블럭하나하나 시작부분에서 생겨서 끝나는 지점에 Destroy시킨다던지 여러 방법이 물리적으로 자연스럽게 컨베이어밸트를 구현할 수 있을 것이다.

하지만 여기 문제가 있다. (나는 이미 잘 분할된 모델을 쓰고있긴하지만)
1. 밸트 오브젝트 구성요소 각각이 따로 선택되어야 하는 오브젝트가 필요.
2. 밸트를 구성하는 요소 각각 따로 collider, rigidbody 등의 컨포넌트를 적용해야하여 리소스를 많이 잡아먹음
3. 디테일한 애니메이션의 필요
핵심은 2번이다. 1,3번의 경우 디자이너, 애니메이터가 따로 있는 프로젝트라면 부탁하면되는데 그렇다 하더라도 리소스를 많이 잡아먹는 방식이라는 점은 최적화측면에서 해결해야할 문제이다.
Edit Colider를 통해 디테일한 콜라이더 설정할 필요없이 하나의 큰 콜라이더를 설정해준다.

- 코드 수정
void FixedUpdate()
{
Vector3 pos = rb.position;
rb.position += (-1*transform.right) * speed * Time.fixedDeltaTime;
rb.MovePosition(pos);
}
이전 상황에서 벨트가 움직였기 때문에 밸트의 위치는 고정되면서, 올려져있는 물체는 벨트가 움직이는 것처럼 움직이게 수정해야한다.
프레임마다 벨트의 현위치 정보를 저장해둔다.
단순위치정보수정을 통해 밸트의 위치정보를 밸트의 이동방향의 반대로 수정한다 (맨 처음 코드처럼 밸트만따로움직이는 로직)
MovePosition() 함수를 통해 저장해뒀던 원위치로 복귀시킨다. 이때 위에 올려진 오브젝트가 움직인다.
한 프레임 내에서 밸트의 위치를 옮겼다가 원위치시키는 것이기 때문에 실제 런타임에서 버벅거리거나 할 걱정은 없이 컨베이어 밸트 로직이 완성된다.
Physics를 구현해내긴 했지만, 밸트자체의 콜라이더를 따서 직접 돌린것이 아니라 일종의 눈속임 로직이기 때문에, 밸트가 눈에 보이게 돌아가게 하려면 애니메이션을 따로 적용시켜야 한다.

무한 루프를 설정해놓고, start, end 프레임을 조절하여 끊김이 없게 애니메이션을 준비해서 밸트 오브젝트에 적용시키고 프리펩화 시킨다.
float speed;
Rigidbody rb;
Animator animator;
void Start()
{
rb = GetComponent<Rigidbody>();
animator = GetComponent<Animator>();
//실행중인 애니메이션 특정해서 속도 가져옴
speed = animator.GetCurrentAnimatorClipInfo(0)[0].clip.length /
animator.GetCurrentAnimatorStateInfo(0).length * 1.1F;
}
void FixedUpdate()
{
Vector3 pos = rb.position;
rb.position += (-1*transform.right) * speed * Time.fixedDeltaTime;
rb.MovePosition(pos);
}
애니메이션의 속도와 밸트의 이동속도가 완전히 별개로 조정해야했기 때문에 밸트의 speed변수를 실행중인 애니메이션 속도와 연결지었다.
speed 설정하는 마지막 1.1F값을 조절해가며 싱크를 한번 맞추어두면, 앞으로 애니메이션 속도만 조절하면 자동으로 밸트의 속도까지 맞춰지는 유지보수를 할 수 있다.
