Quick Comparison
| 질문 | CharacterController | Rigidbody |
|---|---|---|
| 누가 위치를 결정하는가 | 스크립트가 Move에 넣은 이동량 | 물리 simulation과 힘·충돌 해법 |
| 중력·점프 | 직접 속도와 중력을 계산 | Use Gravity, AddForce, velocity 정책 |
| 접지·벽 충돌 | isGrounded, CollisionFlags, OnControllerColliderHit를 코드가 해석 | collision callback과 contact를 물리 상태로 해석 |
| 잘 맞는 목표 | 조작감, 계단·슬로프 규칙, 예측 가능한 캐릭터 | 밀림, 반동, 차량·공, 외력이 핵심인 물체 |
두 컴포넌트를 같은 GameObject의 주 이동 수단으로 동시에 쓰지 않습니다. CharacterController를 선택하면 중력·점프·접지·이동 속도를 코드가 소유하고, Rigidbody를 선택하면 물리 힘과 collision response를 중심으로 설계합니다.
private Vector3 _verticalVelocity;
private void Update()
{
if (_controller.isGrounded && _verticalVelocity.y < 0f)
_verticalVelocity.y = -groundStickSpeed;
_verticalVelocity.y += Physics.gravity.y * Time.deltaTime;
Vector3 motion = ReadMoveDirection() * moveSpeed + _verticalVelocity;
CollisionFlags flags = _controller.Move(motion * Time.deltaTime);
}CharacterController가 맡는 것
CharacterController.Move는 이동량을 받고 CollisionFlags를 반환합니다. 중력은 자동 적용하지 않으므로 위 예제처럼 vertical velocity를 별도로 누적해야 합니다. isGrounded는 Move 결과에 따라 갱신되는 상태이므로, 이동 전후 어느 시점의 값을 쓰는지 명확히 합니다.
| Inspector 값 | 단위·기본 성격 | 언제 바꾸는가 | 잘못되면 |
|---|---|---|---|
Center | local 좌표 | 모델 pivot이 발 위치와 다를 때 | capsule이 발이나 머리를 제대로 감싸지 않습니다. |
Radius, Height | world unit | 캐릭터 실제 폭·키에 맞출 때 | 통로 판정과 충돌 공간이 시각 모델과 다릅니다. |
Slope Limit | degree | 오를 수 있는 경사 규칙을 정할 때 | 경사에서 멈추거나 의도보다 가파른 벽을 오릅니다. |
Step Offset | world unit | 자동으로 넘을 수 있는 낮은 단차를 정할 때 | 계단에서 걸리거나 너무 높은 장애물을 넘습니다. |
Skin Width | world unit | 접촉 안정성과 틈새 문제를 조절할 때 | 너무 작으면 떨림·끼임, 너무 크면 보이는 접촉 전 멈춤이 생길 수 있습니다. |
Min Move Distance | world unit | 아주 작은 이동을 무시해야 할 때 | 큰 값은 미세 이동·접지 보정을 건너뜁니다. |
OnControllerColliderHit은 Move 중 Controller가 Collider를 만났을 때의 callback입니다. 이를 이용해 밀 수 있는 Rigidbody에 힘을 주려면 힘을 받는 쪽이 dynamic인지와 질량·ForceMode를 함께 정합니다. CharacterController가 자동으로 물리 반동을 받는 것은 아닙니다.
Rigidbody를 선택할 때
Rigidbody는 중력, 충돌, Joint, 힘이 movement 결과를 바꿔도 되는 경우에 맞습니다. dynamic Rigidbody는 FixedUpdate에서 AddForce 또는 속도 정책을 적용하고, 제어형 kinematic 이동은 MovePosition을 사용합니다. Mass, Linear Damping, Constraints, Collision Detection, Interpolation은 움직임의 결과와 화면 표현을 바꾸므로 Rigidbody 이동 기본 카드에서 목적에 맞게 함께 정합니다.
캐릭터가 물리 오브젝트를 밀 수는 있지만, 주 이동 모델은 하나여야 합니다. 예를 들어 CharacterController 캐릭터가 상자를 밀 때 캐릭터에는 CharacterController만 두고, 상자는 dynamic Rigidbody로 둡니다. 그 반대처럼 같은 Transform에 Controller Move와 Rigidbody AddForce를 섞으면 두 시스템이 위치 제어권을 경쟁합니다.
자주 틀리는 부분
| 증상 | 원인 | 수정 방향 |
|---|---|---|
| CharacterController가 떨어지지 않음 | Move가 중력을 자동 적용한다고 생각했습니다. | vertical velocity를 코드로 누적합니다. |
| 계단에서 멈추거나 벽을 오름 | Step Offset, Slope Limit과 실제 collider가 맞지 않습니다. | 캐릭터 scale로 Inspector 값을 검증합니다. |
| 벽 충돌에 물리 반동을 기대함 | CharacterController는 dynamic Rigidbody가 아닙니다. | 필요한 반응을 CollisionFlags/callback으로 구현하거나 Rigidbody를 선택합니다. |
| 이동이 튀거나 contact가 이상함 | 같은 객체에 Controller와 Rigidbody 이동을 섞었습니다. | 주 이동 모델 하나만 남깁니다. |
CharacterController는 "물리를 쓰지 않는 Collider"가 아니라 캐릭터 이동을 위한 별도 collision 시스템입니다. 어떤 규칙을 Unity가 자동으로 해 줄지 추측하지 말고, 중력·접지·단차·밀기 정책을 각각 코드와 Inspector 중 어디에서 소유하는지 정하세요.
참고 링크
3 sources