2D 도형 충돌

제대로 알기 전에는 도형을 잘 추상화해서 어떤 형태든 추상 형태로 계산할 수 있을 거라고 생각했다. 하지만 아래 강의를 보니 아닌 거 같다. 원이면 반지름, 선이면 가까운 지점 등 가지는 요소가 다른만큼 각자 다르게 계산되어야 한다.

unity3d의 경우 게임 엔진 차원에서 제공하기 때문에 더 자세하게 구현되어 있다. [2D Physics Engine from Scratch (JS)](#2D Physics Engine from Scratch (JS))의 구현과 비교해보면 재밌다.

2D Physics Engine from Scratch (JS)

https://www.youtube.com/playlist?list=PLo6lBZn6hgca1T7cNZXpiq4q395ljbEI_

이 유튜브 재생목록이 step by step으로 잘 설명한다. javascript 및 canvas로 구현한다. 나는 8번 Collision Response에서 10번 Adding the Walls까지 봤는데, 앞부분이 쉽다면 중간부터 봐도 무방해 보인다.

GitHub에 소스코드도 공개되어 있다.

8번부터만 봐도 핵심요소는 다 포함하는 듯하다. 속도 velocity, 질량 mass, 탄성 elasticity, 마찰 friction, 가속도 acceleration

매 프레임마다 다음 순서로 계산한다:

  1. intersection 충돌 여부 확인
  2. penetration depth resolution 겹침 해결
  3. collision resolution 충돌 계산

1에서 작용하지 않은 것으로 판단하면 2, 3을 진행하지 않는다. 2에서 다음 프레임까지의 Delta Time 및 속도에 따라 겹치는 정도가 다르다. 팅겨내기 전에 표면으로 돌려보내기 위한 계산이다. 3에서 물체 충돌 후 반작용을 위한 계산을 한다.

Movement

Unity 기준.

What's the best way to move to a target?

간단하게 구현하면 다음과 같이 할 수 있다:

transform.position += (target.position - transform.position).normalized * Time.deltaTime;

normalized로 vector 정규화하면 방향만 남고 크기는 1인 단위 벡터가 된다. 여기에 deltaTime을 곱해주면 게임 엔진의 프레임을 고려한 속도가 된다.

이 방법은 두 오브젝트가 서로를 향해 이동할 때 문제가 있다. 서로를 넘어가는 시점부터 둘 다 같은 방향으로 이동하게 된다.

부드러운 이동을 위해서는 다음과 같이 할 수 있다:

transform.position = Vector3.Lerp(transform.position, target.position, Time.deltaTime);

Lerp는 선형보간(Linear Interpolation)으로, 두 지점 사이의 중간 지점을 계산한다. 마찬가지로 deltaTime을 곱해주므로 프레임을 고려하며, 가속도가 적용되기 때문에 부드러운 이동이 가능하다.

게임 케릭터를 구현을 위해 위 로직을 사용하면 어색하다. 가속도가 없고 목표 지점을 넘어가지 않는 MoveTowards를 사용하는 것이 좋다.

transform.position = Vector3.MoveTowards(transform.position, target.position, Time.deltaTime * speed);

케릭터의 속도 speed를 고려하는 방식이다.

references: