다이나믹 래그돌 물리를 연동한 타격 반응 설계

애니메이션의 제어감을 유지하면서도 충격의 방향과 크기에 따라 몸이 자연스럽게 흔들리도록 만드는 방법을 정리합니다. Unity의 Rigidbody와 ConfigurableJoint를 기준으로 활성 래그돌, 피격 임펄스, 복귀 안정화까지 다룹니다.

✓ 복사 완료!
챕터애니메이션 & 리깅
난이도중급
상태● 검토 완료
대상 엔진
Unity
최종 업데이트
다이나믹 래그돌 물리를 연동한 타격 반응 설계

목표: 쓰러짐과 피격 반응을 같은 물리 모델로 다루기

일반적인 피격 애니메이션은 재생 속도와 방향이 정해져 있어 구현은 단순하지만 공격 위치와 충격 방향이 달라져도 반응이 비슷하게 보이기 쉽습니다. 반대로 완전한 래그돌은 물리적으로 자연스럽지만 조작 캐릭터의 자세와 이동을 유지하기 어렵습니다.

다이나믹 래그돌은 각 신체 파트의 Rigidbody를 물리 시뮬레이션에 참여시키고 관절 구동력으로 애니메이션 목표 자세를 따라가게 만드는 방식입니다. 피격 시에는 해당 부위에 충격량을 가하고 관절은 자세를 회복하려 하므로 캐릭터가 공격 방향으로 밀리거나 비틀린 뒤 다시 균형을 찾는 반응을 만들 수 있습니다.

flowchart LR
    A[공격 판정] --> B[피격 부위와 충돌점 확인]
    B --> C[충격량 계산]
    C --> D[Rigidbody에 Impulse 적용]
    D --> E[관절 구동력과 감쇠가 자세 복원]
    E --> F[안정화 또는 완전 래그돌 전환]

핵심은 힘을 가하는 물리 모델과 자세를 유지하는 제어 모델이 서로 싸우지 않도록 설계하는 데 있습니다. 충격을 무한히 상쇄할 정도로 관절을 강하게 설정하면 피격감이 사라지고 반대로 너무 약하면 평상시 자세를 유지하지 못합니다.

구성 요소와 데이터 흐름

활성 래그돌에는 보통 다음 요소가 필요합니다.

  • 애니메이터가 구동하는 원본 스켈레톤 또는 매 프레임 목표 자세를 제공하는 데이터
  • 물리 시뮬레이션용 뼈대의 Rigidbody와 Collider
  • 부모와 자식 뼈를 연결하는 ConfigurableJoint 또는 ArticulationBody
  • 피격 지점, 공격 방향, 피해량을 전달하는 전투 시스템

원본 애니메이션 뼈와 물리 뼈를 분리하면 목표 자세와 실제 물리 자세를 비교하기 쉽습니다. 다만 뼈대를 하나만 사용해도 구현할 수 있습니다. 이 경우 애니메이션이 Transform을 직접 덮어쓰지 않도록 실행 순서와 물리 동기화를 특히 조심해야 합니다.

애니메이션 목표 자세와 물리 래그돌 뼈대가 대응되는 구조

피격 이벤트에는 최소한 다음 정보를 담는 편이 좋습니다.

public readonly struct HitInfo
{
    public readonly Vector3 Point;
    public readonly Vector3 Direction;
    public readonly float Damage;
    public readonly float Impulse;
    public readonly Collider HitCollider;

    public HitInfo(
        Vector3 point,
        Vector3 direction,
        float damage,
        float impulse,
        Collider hitCollider)
    {
        Point = point;
        Direction = direction.normalized;
        Damage = damage;
        Impulse = impulse;
        HitCollider = hitCollider;
    }
}

Direction의 기준을 프로젝트 전체에서 통일해야 합니다. 예를 들어 공격자에서 피격자로 향하는 방향을 사용한다면 피격자를 뒤로 밀기 위해 그 방향 그대로 임펄스를 적용할 수 있습니다. 반대 기준을 섞으면 타격 반응이 공격자를 향해 당겨지는 문제가 생깁니다.

충격량은 힘보다 먼저 생각하기

짧은 타격 반응에는 지속적인 힘보다 임펄스가 잘 맞습니다. 임펄스는 짧은 시간에 전달된 운동량 변화이며 다음처럼 볼 수 있습니다.

J=FΔtJ = F\Delta t

질량이 일정한 강체에서는 속도 변화가 다음 관계를 따릅니다.

Δv=Jm\Delta v = \frac{J}{m}

Unity에서는 ForceMode.Impulse가 이 목적에 적합합니다. 충돌점에 힘을 적용하면 선형 운동뿐 아니라 회전도 생기므로 가슴이나 어깨를 맞았을 때 상체가 자연스럽게 젖혀집니다.

using UnityEngine;

public sealed class RagdollHitReceiver : MonoBehaviour
{
    [SerializeField] private Rigidbody hips;
    [SerializeField] private float torsoImpulseMultiplier = 1.0f;
    [SerializeField] private float fallbackImpulseMultiplier = 0.35f;

    public void ApplyHit(in HitInfo hit)
    {
        var body = hit.HitCollider.attachedRigidbody;
        if (body == null)
        {
            body = hips;
        }

        float multiplier = body == hips
            ? fallbackImpulseMultiplier
            : torsoImpulseMultiplier;

        Vector3 impulse = hit.Direction * (hit.Impulse * multiplier);
        body.AddForceAtPosition(impulse, hit.Point, ForceMode.Impulse);
    }
}

피해량을 곧바로 임펄스로 사용하기보다는 공격 데이터에 별도의 impulse 값을 두는 편이 안전합니다. 피해량은 게임 밸런스를 위한 값이고 임펄스는 연출과 물리 안정성을 위한 값이기 때문입니다. 작은 적의 강한 마법 공격, 큰 적의 약한 밀치기처럼 두 값이 독립적인 경우도 많습니다.

관절 구동력: 강성, 감쇠, 최대 힘

ConfigurableJoint의 Angular Drive는 목표 회전으로 돌아가려는 토크를 만듭니다. 개념적으로는 스프링-댐퍼 제어와 유사합니다.

τ=kθ−cω\tau = k\theta - c\omega

여기서 (k)에 해당하는 값은 관절의 자세 복원 성향이고 (c)에 해당하는 값은 흔들림을 줄이는 감쇠 성향입니다. 실제 Unity 설정에서는 positionSpring, positionDamper, maximumForce가 이에 대응합니다.

using UnityEngine;

public sealed class JointDriveTuner : MonoBehaviour
{
    [SerializeField] private ConfigurableJoint[] joints;
    [SerializeField] private float normalSpring = 900f;
    [SerializeField] private float normalDamper = 90f;
    [SerializeField] private float hitSpring = 350f;
    [SerializeField] private float hitDamper = 55f;
    [SerializeField] private float maxForce = 5000f;

    public void SetHitReaction(bool active)
    {
        float spring = active ? hitSpring : normalSpring;
        float damper = active ? hitDamper : normalDamper;

        foreach (var joint in joints)
        {
            var drive = joint.slerpDrive;
            drive.positionSpring = spring;
            drive.positionDamper = damper;
            drive.maximumForce = maxForce;
            joint.slerpDrive = drive;
        }
    }
}

피격 순간에 스프링 값을 낮추면 충격이 더 잘 드러납니다. 일정 시간이 지나면 원래 값으로 되돌려 자세를 회복합니다. 모든 관절에 같은 값을 쓰기보다 목과 손목은 약하게, 골반과 척추는 강하게 두는 편이 안정적입니다.

maximumForce가 너무 낮으면 큰 충격 뒤에 관절이 목표 자세로 돌아오지 못할 수 있습니다. 너무 높으면 물리 엔진이 매우 큰 보정 토크를 만들며 떨림이나 관절 폭주가 발생할 수 있습니다. 수치는 캐릭터의 질량, 뼈 길이, Fixed Timestep에 따라 달라지므로 한 캐릭터에서 안정적인 범위를 먼저 찾은 뒤 프리셋으로 관리하는 것이 좋습니다.

부위별 반응을 설계하는 방법

같은 임펄스라도 맞은 부위에 따라 반응을 달리해야 읽기 쉬운 타격감이 나옵니다.

머리와 상체

머리, 가슴, 척추는 AddForceAtPosition의 효과가 크게 나타납니다. 충돌점이 몸의 중심에서 멀수록 회전이 커지므로 과도한 목 꺾임을 막기 위해 목 관절의 각도 제한을 보수적으로 설정해야 합니다.

팔과 다리

팔다리는 가벼운 보조 반응에 적합합니다. 팔에 맞았을 때 팔 Rigidbody에 직접 작은 임펄스를 주고 상체에는 그 일부만 전달하면 피격 방향을 보여주면서도 전신이 과하게 흔들리는 것을 막을 수 있습니다.

골반

골반은 캐릭터 전체의 움직임에 큰 영향을 줍니다. 골반에 강한 임펄스를 자주 적용하면 이동 제어와 접지 판정이 불안정해질 수 있으므로 피격 부위가 골반이 아니면 일부만 전달하거나 별도의 캐릭터 이동 반동으로 처리하는 방식을 고려합니다.

피격 상태와 완전 래그돌 상태를 분리하기

다이나믹 반응과 사망·넉다운용 완전 래그돌은 같은 Rigidbody 구성을 공유할 수 있지만 상태 전환 규칙은 분리하는 편이 좋습니다.

public enum RagdollMode
{
    Animated,
    ActiveRagdoll,
    FullRagdoll,
    Recovering
}
  • Animated: 물리 뼈대가 비활성화됐거나 애니메이션에 거의 고정된 상태입니다.
  • ActiveRagdoll: 관절 구동력으로 목표 자세를 추적하며 피격 임펄스를 받습니다.
  • FullRagdoll: 관절 구동을 끄고 모든 Rigidbody가 자유롭게 움직입니다.
  • Recovering: 바닥 위치와 방향을 확인한 뒤 기상 애니메이션으로 복귀합니다.

완전 래그돌 전환 시에는 애니메이터의 루트 이동, CharacterController, NavMeshAgent처럼 위치를 직접 제어하는 컴포넌트가 Rigidbody와 충돌하지 않게 비활성화하거나 제어권을 넘겨야 합니다. 기상할 때는 골반 위치와 상체 방향을 기준으로 캐릭터 루트를 재배치한 뒤 애니메이션을 다시 활성화합니다.

안정성 점검 목록

다이나믹 래그돌이 흔들리거나 폭발적으로 튀는 문제는 대체로 설정 불일치에서 시작합니다.

  • 부모 Rigidbody와 자식 Rigidbody의 질량 비율을 극단적으로 만들지 않습니다. 몸통은 무겁고 손발은 가볍게 두되 한 단계에서 수십 배씩 차이 나지 않게 조절합니다.
  • 인접한 래그돌 Collider끼리는 보통 충돌하지 않도록 설정합니다. 자기 충돌이 꼭 필요하다면 필요한 쌍만 선택적으로 허용합니다.
  • 관절 축과 뼈의 로컬 축을 일관되게 맞춥니다. 모델마다 축이 다르면 같은 드라이브 설정도 전혀 다르게 동작합니다.
  • Rigidbody 보간과 물리 갱신 주기를 확인합니다. 렌더 프레임에서 힘을 적용하기보다 FixedUpdate에 모아 처리하거나 물리 이벤트에서 처리합니다.
  • 피격 임펄스에 상한을 둡니다. 여러 히트박스가 한 프레임에 중복 판정될 수 있으므로 공격별 재피격 쿨다운도 필요합니다.

디버깅 순서

문제가 생기면 전체 시스템을 한 번에 조정하지 말고 순서를 정해 확인합니다.

  1. 애니메이션 목표 자세 없이 관절과 Rigidbody만으로 안정적으로 서 있는지 확인합니다.
  2. 작은 임펄스를 골반, 가슴, 팔에 각각 적용해 질량과 관절 제한이 적절한지 봅니다.
  3. 관절 드라이브를 연결하고 목표 회전이 올바른 축으로 적용되는지 검증합니다.
  4. 전투 시스템의 피격점과 방향을 연결합니다.
  5. 마지막으로 상태 전환, 이동 제어, 기상 애니메이션을 통합합니다.

특히 관절 목표 회전이 잘못된 경우 스프링과 감쇠 값을 아무리 조절해도 해결되지 않습니다. 먼저 각 관절의 로컬 회전과 목표 회전을 Gizmo나 디버그 메시로 시각화해 축 문제를 제거하는 것이 빠릅니다.

마무리

좋은 다이나믹 래그돌 타격 반응은 큰 물리 힘보다 일관된 기준에서 시작합니다. 공격 방향, 충돌점, 부위별 질량, 관절 강성, 상태 전환의 책임을 분명히 나누면 작은 임펄스만으로도 설득력 있는 반응을 만들 수 있습니다. 구현 초기에는 상체 한두 관절만 활성 래그돌로 구성해 안정성을 검증한 뒤 팔다리와 넉다운 상태를 단계적으로 추가하는 접근이 가장 관리하기 쉽습니다.