현실에서 움직이는 발판 위에 서 있으면 그냥 같이 갑니다. 발바닥과 발판 사이의 마찰이 공짜로 해 주는 일입니다. 게임에서는 다릅니다. 엘리베이터가 올라가는데 캐릭터만 제자리에 남아 바닥을 뚫고 내려가거나, 움직이는 발판에서 점프했더니 뒤로 떨어져 죽는 게임을 한 번쯤 겪어 봤을 겁니다.
이유는 한 줄입니다. 게임 캐릭터의 몸은 물리가 아니라 코드가 옮기는 캡슐이라, 발판이 움직여도 따라갈 이유가 없습니다. 그래서 게임은 발판을 타는 세 줄을 따로 짭니다. 영상과 그림에서 하늘색 캐릭터는 세 줄을 다 넣은 쪽, 주황 캐릭터는 줄을 뺀 쪽입니다(점프 장면의 주황은 셋째 줄만 뺐습니다).
영상판 — 영상의 발판과 하늘은 언리얼 엔진에 직접 짠 촬영 리그이고, 캐릭터 이동은 엔진의 캐릭터 무브먼트 없이 이 글의 규칙으로 짠 캡슐 컨트롤러입니다. 수치는 전부 우리 랩의 값입니다
왜 이런 게 생겼나
게임 캐릭터의 몸은 대개 캡슐 하나이고, 그 캡슐은 물리 엔진이 밀지 않습니다. 이동 코드가 매 프레임 이번에 갈 자리를 정해 스윕(캡슐 모양을 가려는 방향으로 미리 쓸어 보며 부딪히는지 검사하는 것)으로 옮기고, 벽에 닿으면 미끄러뜨리고, 낮은 턱은 올라탑니다. 이런 몸을 운동학적 컨트롤러라고 부릅니다. 힘과 마찰이 아니라 위치를 직접 씁니다.
캐릭터를 이렇게 만드는 이유는 조작감입니다. 물리 몸으로 만들면 발판은 공짜로 타지만 점프가 물렁해지고 계단에서 튀고 벽에 끼입니다. 그래서 캐릭터는 코드가 옮기고, 그 대가로 마찰이 없습니다.
우리 리그에서 발판이 195 cm/s 로 출발하면, 세 줄이 없는 캐릭터는 발판 중심에 서 있다가 월드 좌표 그 자리에 남습니다. 0.51초 뒤 발판 뒤끝이 발밑을 지나가면 80 cm 아래 바닥으로 떨어집니다.

첫째, 발밑을 쏴서 바닥이 누구인지 알아낸다
발판을 타려면 먼저 내가 무엇 위에 서 있는지 알아야 합니다. 캡슐 컨트롤러는 어차피 매 프레임 발밑으로 작은 상자를 쓸어내려 바닥을 찾습니다. 그 결과에서 닿은 물체를 적어 두고, 그게 움직일 수 있는 물체이면 베이스로 삼습니다.
쏘는 자리는 발밑보다 조금 위여야 합니다. 발판 윗면에 딱 붙은 채로 쏘면 시작부터 겹친 것으로 나와 바닥이 없다고 판정하기 때문입니다. 우리 리그는 발밑 4 cm 위에서 쏘고, 언리얼의 바닥 찾기도 같은 이유로 조금 위에서 쏩니다.
둘째, 발판이 간 만큼 몸도 옮긴다
베이스를 알면 그 다음은 단순합니다. 이번 프레임에 발판이 얼마나 움직였는지 재서, 내 이동을 계산하기 전에 그 델타를 캡슐에 먼저 더합니다. 순서가 중요합니다. 내 이동 뒤에 더하면 발이 한 프레임 늦게 따라와서 매 프레임 3.25 cm 씩 뒤로 갔다 앞으로 튀는 떨림이 생깁니다.
회전 발판이면 델타가 회전입니다. 발판 중심(피벗)을 축으로 캡슐의 자리를 돌리고, 몸의 방향도 같이 돌립니다. 안 돌리면 발판만 돌고 몸은 그대로라 발판 위를 미끄러집니다. 우리 회전 발판이면 한 바퀴에 377 cm 를 미끄러지는 셈입니다.
발 IK 로 발을 바닥에 맞추는 캐릭터라면 디딘 발도 같은 델타로 옮겨야 합니다. 98번 글(캐릭터 발이 계단에 딱 붙는 이유)처럼 발을 월드 좌표에 꽂아 두는 방식에서는 몸만 옮기면 발이 발판 위에 남아 다리가 늘어나기 때문입니다.


셋째, 떠날 때 발판 속도를 물려준다
달리는 발판 위에서 점프하면 현실에서는 발판과 같은 속도로 날아가 같은 자리에 내립니다. 관성입니다. 게임 캐릭터는 관성이 없습니다. 점프하는 순간 베이스가 사라지니 델타를 더할 대상이 없고, 수평 속도는 0 입니다.
우리 리그에서는 체공 0.82초 동안 발판이 159 cm 앞서가는데, 중심에서 뛰면 뒤로 여유가 100 cm 뿐이라 발판 뒤끝 밖에 떨어집니다. 고치는 법은 점프하는 순간 발판 속도를 몸 속도에 더하는 것입니다. 그러면 같은 자리에 내립니다.

실무 방식
엔진마다 세 줄 중 어디까지 자동인지가 다릅니다. 이 글의 세 줄은 언리얼과 같은 뼈대이고, 고도는 엔진이 다 해 주고, 유니티 기본 컨트롤러는 직접 짜야 합니다.
언리얼 엔진 5 의 캐릭터 무브먼트 컴포넌트는 바닥 스윕 결과에서 베이스를 지정하고, 움직일 수 있는 컴포넌트면 상대 좌표로 추적합니다. 자기 이동을 계산하기 전에 베이스 델타를 먼저 적용하는데, 발판이 움직이고 돈 만큼 옮길 캐릭터의 점을 캡슐 중심이 아니라 발바닥으로 잡는다는 주석이 소스에 그대로 있습니다. 떠날 때 베이스 속도를 물려주는 것은 축마다 옵션입니다.
고도 4 의 캐릭터 바디는 이동 함수가 발판 속도를 자동으로 더해 주고, 떠날 때 처리를 세 가지 중 하나로 고릅니다. 발판 속도를 더한다, 더하되 아래로 가는 성분은 버린다, 아무것도 안 한다. 두 번째를 고르면 내려가는 엘리베이터에서 뛰어도 발판이 내려가는 속도가 점프에 얹히지 않습니다.
유니티의 기본 캐릭터 컨트롤러는 발판 지원이 없습니다. 매뉴얼에 적힌 대로 물리의 영향을 받지 않아 발판 델타도 직접 넣어야 합니다. 새 엔티티 기반 캐릭터 컨트롤러 패키지에는 발판 문서가 따로 있고, 트랜스폼으로 직접 옮긴 발판은 캐릭터와 서로 미는 충돌을 제대로 풀지 못한다고 경고합니다. 캐릭터를 발판의 자식으로 붙이는 방법도 있지만 물리 층과 부딪치기 쉬워서, 이 패키지는 트랜스폼 부모 대신 매 프레임 논리적 부모(지금 타고 있는 발판)를 갱신하는 방식을 씁니다.
소스 엔진은 방식이 다릅니다. 델타로 옮기지 않고 이동 직전에 베이스 속도를 몸 속도에 더했다가 직후에 다시 빼고, 땅을 떠나는 순간에는 그 속도를 더해 둡니다. 결과는 같습니다.
더 나아가려면
이 글에서 다루지 않았지만 실무 구현이 챙기는 것들입니다.
- 베이스가 물리 몸일 때: 물리 시뮬이 끝난 뒤에 델타를 적용해야 합니다. 언리얼은 이 경우 베이스 갱신을 물리 뒤로 미룹니다.
- 회전 발판의 옵션: 언리얼에는 자리는 따라가되 방향은 안 돌리는 옵션과, 뛰어내릴 때 각속도까지 접선 속도로 물려주는 옵션이 있습니다. 우리 리그는 직선 속도만 물려줍니다.
- 베이스에 실린 채 공중에 뜨기: 발판이 아래로 빠르게 내려가면 캐릭터가 순간 공중에 뜹니다. 잠깐은 베이스에 붙어 있게 두는 옵션이 있습니다.
- 발판이 캐릭터를 미는 경우: 옆에서 오는 발판이나 문은 델타가 아니라 밀어내기 문제입니다. 앞에서 말한 캡슐의 스윕과 미끄러뜨리기가 그 일을 합니다.
참고 자료
- 언리얼 엔진 5.8 로컬 소스: Engine/Source/Runtime/Engine/Private/Components/CharacterMovementComponent.cpp — SetBaseFromFloor(2449), UpdateBasedMovement(2531, 회전 델타 2568, 발바닥 피벗 2626~2632, 스윕 이동 2669), ApplyImpartedMovementBaseVelocity(2744), GetImpartedMovementBaseVelocity(1193) · Classes/GameFramework/CharacterMovementComponent.h — bImpartBaseVelocityX/Y/Z(936~946), bImpartBaseAngularVelocity(948), bIgnoreBaseRotation(1026)
- Unity, Character Controller 매뉴얼 · https://docs.unity3d.com/Manual/class-CharacterController.html · CharacterController.Move · https://docs.unity3d.com/ScriptReference/CharacterController.Move.html
- Unity, Character Controller 패키지 1.3: Moving platforms · https://docs.unity3d.com/Packages/com.unity.charactercontroller@1.3/manual/moving-platforms.html · Parenting · https://docs.unity3d.com/Packages/com.unity.charactercontroller@1.3/manual/concepts-parenting.html
- Godot Engine, CharacterBody3D — move_and_slide, platform_on_leave, platform_floor_layers · https://docs.godotengine.org/en/stable/classes/class_characterbody3d.html
- Valve, Source SDK 2013 — src/game/shared/gamemovement.cpp WalkMove(1962~1971), SetGroundEntity(3611~3626) · https://github.com/ValveSoftware/source-sdk-2013
- 데브조생 98번, 캐릭터 발이 계단에 딱 붙는 이유 — 같은 골렘과 캡슐 리그 · https://ch5saeng.tistory.com/98
발판, 하늘 무대, 캡슐 컨트롤러, 발판 추적은 전부 직접 짠 것이고(파이썬 랩 + 언리얼 C++ 리그), 캐릭터 애니메이션만 엔진 마네킹 것을 리타깃했습니다. 화면과 그림의 수치는 우리 랩의 값입니다. 특정 게임의 코드나 화면이 아닙니다.
'게임개발뜯어보기' 카테고리의 다른 글
| 게임 거울은 방을 한 번 더 그립니다 - 평면 반사와 화면 공간 반사 (0) | 2026.09.23 |
|---|---|
| 총을 바꿔도 손이 딱 붙는 이유 - 두 본 IK(Two Bone IK) (0) | 2026.09.22 |
| 마인크래프트는 블록이 수억 칸인데 왜 안 끊길까 - 청크와 면 컬링 (0) | 2026.09.20 |
| 물보라만 튀는 게임 물, 제대로 하면 이렇게 됩니다 - 높이장과 파동 방정식 (1) | 2026.09.19 |
| 캐릭터 발이 계단에 딱 붙는 이유 - 발 IK(Foot IK) (0) | 2026.09.18 |