게임을 만들다 보면 누구나 한 번은 이 질문과 마주칩니다. 유니티나 언리얼의 기본 물리 엔진을 쓰면 되는데, 왜 어떤 스튜디오는 굳이 게임 물리 엔진을 처음부터 직접 만드는 걸까? 이번 포스팅에서는 실제로 간단한 2D 물리 프로토타입을 직접 짜보고, 상용 물리 엔진과 무엇이 다른 것인지 코드 수준에서 비교해보도록 하겠습니다.
범용 물리 엔진이 해결하지 못하는 문제
유니티의 PhysX처럼 범용 물리 엔진은 “그럴듯하게 보이는 시뮬레이션“을 목표로 만들어졌지, “매번 정확히 똑같은 결과“를 목표로 설계되지 않았습니다. 이 둘은 비슷해 보일지 몰라도 실제로는 완전히 다른 문제라고 할 수 있습니다.
격투 및 온라인 게임이 물리를 직접 만드는 이유

격투 게임을 예시로 들어보도록 하겠습니다. 캐릭터의 점프 궤적과 히트박스 판정은 프레임 단위로 항상 일치해야 합니다.
하지만 범용 물리 엔진은 부동소수점 연산 순서, 물리 스텝의 서브스테핑 방식 때문에 같은 입력을 하더라도 실행 환경에 따라 아주 미세하게 다른 결과가 나올 수도 있습니다.
해당 오차는 GGPO와 같은 롤백 넷코드 방법을 사용하는 온라인 대전 게임에서는 치명적이라고 할 수 있습니다. 두 플레이어의 고객이 같은 입력으로 다른 결과를 계산하면 그 순간 화면이 어긋날 수 있습니다. 그렇기 때문에 격투 게임과 결정론적 동기화가 필요한 온라인 게임 대부분은 물리를 직접 구현하는 것입니다.
직접 만든 사례: 셀레스트
조작감도 중요한 이유 중 하나라고 할 수 있습니다. 플랫포머 셀레스트와 타워폴을 만든 매디 손(Maddy Thorson)은 개발 블로그에서 범용 물리로는 이용자가 원하는 점프 곡선 또는 반응성을 만들기가 어렵기 때문에 중력과 가속도, 최고 속도를 프레임 단위로 직접 계산해내는 커스텀 이동 코드를 사용했다고 공개한 사실이 있습니다.
직접 만든 최소한의 2D 물리 코드
실제로 이것이 얼마나 간단한지 확인해보기 위해서 60줄 정도의 프로토타입을 만들어봤습니다. 핵심은 3단계 뿐입니다.
- 중력을 속도에 더해준다: velocity.y += gravity * dt
- 속도만큼 위치를 옮겨준다: position += velocity * dt
- 충돌 시 위치를 되돌리고 속도를 0으로 만들어준다: if (collide) { position = resolve(); velocity.y = 0; }
여기에 델타타임을 고정값으로 두는 “픽스드 타임스텝” 방식을 적용하니, 60fps든 144fps든 게임에 등장하는 캐릭터의 낙하 거리와 점프 높이가 오차 없이 동일하게 나왔습니다. 반면에 유니티 기본 물리로 같은 테스트를 해보면은 프레임레이트에 따라서 미세한 편차가 생기는 걸 체크할 수 있었습니다.
상용 엔진이 필요한 순간
물론 Box2D나 PhysX와 같은 상용 엔진이 압도적으로 유리한 영역도 분명히 있습니다. 복잡한 충돌 형태와 관절 연결 그리고 다중 물체 상호작용은 지겁 구현하기에 있어서 상당한 시간이 듭니다.
정교함과 예측 가능성은 종종 트레이드오프 관계에 있어서는 계산이 정교해질 수록 오차가 생길 여지도 함께 커집니다. 위 코드도 오일러 적분 대신 세미 임플리시트 오일러 방식을 사용하여 오차를 줄였을 뿐이지, 마찰, 회전, 반발력 같은 요소는 아예 다루지 않은 단순한 버전이라고 할 수 있습니다.
결론: 목적에 따라 선택은 달라진다
선택 기준은 명확하다고 말할 수 있습니다. 사실적인 시뮬레이션이 목적이라면 상용 엔진이 더 낫다고 할 수 있고 정확하게 반복이 되는 조작감과 온라인 동기화가 목적이라면 직접 짠 물리가 더 나은 선택입니다. 실제로 많은 게임이 두 방식을 섞어서 사용한다고 합니다. 배경 오브젝트나 파괴 효과처럼 정밀도가 크게 중요하지 않은 부분은 상용 엔진에게 맡기도록 하고, 플레이어 캐릭터의 이동같이 조작감이 핵심적인 부분만 직접 구현하는 것입니다.
유니티나 언리얼로 게임을 제작하다가 조작감이 애매하게 느껴진다고 하면 엔진의 한계가 아니라 애초에 목적이 다르게 설계된 도구를 캐릭터 이동에 그대로 사용하고 있기 때문에 그 가능성을 한 번 의심해봐야 합니다.