본문 바로가기
Unity

Rigidbody 를 사용하면서, transform 코드가 제대로 동작을 안할 때

by Oz Driver 2025. 7. 16.

Rigidbody 를 추가하고 물체를 움직이다 보면, 내가 작성한 코드랑 별개로 움직이는 느낌을 받게 됩니다. 이것은 유니티가 렌더링용 Transform물리 엔진이 다루는 Transform 을 별도로 관리하기 때문입니다. 

이번 글에서는 이 둘의 관계와 내부 동작 원리에 대해 설명하고, 직접 이동, 회전시키고 싶을 때 어떻게 처리해야 하는지를 예제와 함께 다뤄보겠습니다.

 

우리가 흔히 쓰고 있는 Transform (렌더링용)

transform.Translate(Vector3.forward * Time.deltaTime);
transform.Rotate(Vector3.up * 90f * Time.deltaTime);

 

이 코드는 일반적으로 잘 동작합니다. 

하지만 Rigidbody가 붙어 있으면 다음과 같은 어색한 문제들이 발생합니다

•   오브젝트가 움직였다가 다시 원래 위치로 되돌아감

•   회전한 것처럼 보이지만 곧 원래 방향으로 복귀

 

왜 이런 일이 일어날까?

이유는 간단합니다.

Rigidbody 가 가지고 있는 transform 은 물리 연산에 의해 위치와 회전값이 변환되고, FixedUpdate() 에서 그 계산 결과를 렌더링용 transform 에 덮어씌우기 때문입니다. 

 

내가 원하는 대로 움직이게 하려면?

물리 연산을 일시적으로 끊고 직접 덮어쓰기

// 물리 회전 무시
rb.freezeRotation = true;                     

// 내가 직접 코드로 제어
transform.Rotate(Vector3.up * 90f);          

 // 다시 물리 엔진에 제어권 넘김
rb.freezeRotation = false;

 

이 방식은 freezeRotation = true 일 때, Unity 내부에 내가 직접 코드로 회전시킨 transform.rotation 값을 Rigidbody.rotation 에도 복사해준다는 점입니다.

내부 동작은 다음과 같이 유추할 수 있습니다.

if (rb.freezeRotation)
{
    // Transform 값을 물리 회전에 반영
    rb.rotation = transform.rotation; 
}

 

그래서 이렇게 하면 다음 FixedUpdate() 에서도 내가 코드로 작성한 값이 유지되고, 물리 엔진은 그 회전값을 기준으로 연산을 이어갑니다.
 

그런데 이 방법은...

•   freezeRotation 을 반복적으로 켰다 껐다 하면 부하가 생길 수 있습니다.

•   물리 연산에 개입하여 강제로 회전시켰기 때문에, 자연스러운 동작이 어긋날 수 있습니다. 

 

다른 방법은?

Rigidbody 가 제공하는 함수들을 쓰면 됩니다.
이 방법은 애초에 Rigidbody 가 사용하는 위치, 회전값을 직접 조작하여 물리 엔진이 이 결과값에 이어서 계산을 계속 이어나가기 때문에 렌더링과 물리 엔진의 값이 일치하게 됩니다. 

// 위치 이동
rb.MovePosition(rb.position + transform.forward * speed * Time.fixedDeltaTime);

// 회전
Quaternion delta = Quaternion.Euler(0f, 90f * Time.fixedDeltaTime, 0f);
rb.MoveRotation(rb.rotation * delta);

 

이 방법은 자연스럽고 충돌도 잘 적용되며, Rigidbody 의 물리 연산과도 잘 맞습니다.

다만, 주의해야 할 점은 물리 연산에 의해 계산되기 때문에 물리 엔진의 계산 주기와 일치시켜야 합니다. 

 

따라서, 

  Time.DeltaTime 대신에 Time.fixedDeltaTime 을 써야 합니다.

   Update() 함수가 아닌 FixedUpdate() 함수에서 코드를 작성해야 합니다. 

private void FixedUpdate()
{
    ProcessInput();
}

private void ProcessInput()
{
    if (trustAction.IsPressed())
    {
        rb.AddForce( transform.up * ( trustStrength * Time.deltaTime ) );
    }

    if (rotateAction.IsPressed())
    {
        
        float inputValue = rotateAction.ReadValue<float>();
        inputValue *= -1.0f;
        Quaternion rot = Quaternion.Euler(0, 0, inputValue * rotateSpeed * Time.fixedDeltaTime);
        rb.MoveRotation(rb.rotation * rot);  
    }
}

 

그럼 어떤 것을 사용하면 좋을까?

Rigidbody.MoveRotation()

어떤 경우에는 물리 함수를 이용하는 것이 게임적으로는 불편할 수 있습니다.

예를 들어, 두 개의 물체가 있고, 그 중 하나를 MoveRotation 으로 돌리면 바닥이나 옆 물체가 회전 방향을 막아버려서 회전이 안됩니다. 

Rigidbody.MoveRotation() 은 충돌 감지와 함께 작동하기 때문에, 회전하려는 방향에 다른 Collider가 있으면, Unity는 그걸 “부딪히는 상황” 으로 인식하고 회전을 제한하거나 물리 반발로 되돌려 버리기 때문입니다. 

 

transform.Rotate()

rb.freezeRotation = true;
transform.Rotate(...);
rb.freezeRotation = false;

 

반면, freezeRotate = true; 로 놓고 transform.Rotate() 를 사용하면, 물리 연산을 아예 무시하기 때문에 충돌이든 반발력이든 무시하고 그냥 회전시켜 버립니다. 

따라서 물리적으로는 말이 안되지만 게임적으로는 오히려 자연스럽게 보일 수 있습니다. 

 

정리

•   Rigidbody 가 있으면 transform.position과 transform.rotation 은 물리 공식으로 계산된 transform 으로 덮어씌워집니다. 덮어씌워지는 시점은 fixedupdate() 함수가 호출되는 순간입니다. 

•  transform 으로 직접 움직이려면 freezeRotation이나 freezePosition 으로 물리 개입을 끊어야 합니다.

•  물리 계산이 필요한 경우는 fixedDeltaTime, fixedUpdate() 와 같은 변수나 함수를 사용해야 합니다. 

 

추가 사항 (중요)

처음엔 freezeRotation = true 를 일시적으로 켰다가 끄면 회전을 시킬 수 있다고 생각했습니다.
하지만 테스트해 보니, 물리가 transform 을 덮어쓰는 시점은 FixedUpdate() 마다 반복적으로 일어나기 때문에, 일시적으로 제어권을 빼앗아도 곧바로 다시 덮어씌워져 버리는 문제가 생겼습니다. 그래서 내가 원하는 대로 회전이 안되고 물리가 회전을 가로막아 답답한 느낌이 들었습니다.

결국은, 내가 직접 회전시키고 그걸 유지하려면 freezeRotation 옵션을 지속적으로 켜둬야만 했습니다.

 

•   rb.freezeRotation = true; 로 하고 false; 로 돌려놓지 않거나,

•   Inspector 창에서 모든 축 회전에 물리 계산을 막아두는 것으로 일단은 넘어갔습니다.

•   물리 연산과 혼용해서 쓰는 것은 예측이 안되기 때문에 역시 어렵습니다.