콘텐츠로 이동

Unity Animation Rigging 시스템

Animation Rigging은 Unity 공식 패키지로, 클립 기반 애니메이션 위에 절차적(Procedural) 애니메이션을 레이어로 추가합니다. IK(역운동학), 에임 컨스트레인트, 체인 IK 등을 실시간으로 적용해 캐릭터가 환경에 자연스럽게 반응하게 합니다.

설치: Package Manager → Animation Rigging 설치


Character Root
├── Rig Builder (컴포넌트)
└── Rig
├── Rig (컴포넌트, weight: 0~1)
├── TwoBoneIK_LeftArm
│ └── Two Bone IK Constraint (컴포넌트)
├── MultiAim_Head
│ └── Multi-Aim Constraint (컴포넌트)
└── ChainIK_Spine
└── Chain IK Constraint (컴포넌트)

손이 특정 위치에 닿도록 팔 전체를 역방향으로 계산합니다.

using UnityEngine;
using UnityEngine.Animations.Rigging;
public class ArmIKController : MonoBehaviour
{
[Header("IK Constraints")]
[SerializeField] private TwoBoneIKConstraint _leftArmIK;
[SerializeField] private TwoBoneIKConstraint _rightArmIK;
[Header("Rig")]
[SerializeField] private Rig _rig;
// IK 타겟 Transform
private Transform _leftHandTarget;
private Transform _rightHandTarget;
void Start()
{
// IK 타겟 생성
_leftHandTarget = new GameObject("LeftHandTarget").transform;
_rightHandTarget = new GameObject("RightHandTarget").transform;
// TwoBoneIK에 타겟 연결
_leftArmIK.data.target = _leftHandTarget;
_rightArmIK.data.target = _rightHandTarget;
// 힌트(Hint): 팔꿈치 방향 가이드
var leftHint = new GameObject("LeftElbowHint").transform;
leftHint.position = transform.position + Vector3.left + Vector3.back;
_leftArmIK.data.hint = leftHint;
}
// 왼손을 특정 위치로 이동 (문 손잡이, 버튼 등)
public void ReachWithLeftHand(Vector3 targetPosition, float weight = 1f)
{
_leftHandTarget.position = targetPosition;
_leftArmIK.data.targetPositionWeight = weight;
_leftArmIK.data.targetRotationWeight = weight;
}
// IK 비중 조절 (애니메이션 → IK 블렌딩)
public void SetRigWeight(float weight)
{
_rig.weight = Mathf.Clamp01(weight);
}
}

Multi-Aim Constraint — 머리/눈 조준

섹션 제목: “Multi-Aim Constraint — 머리/눈 조준”

캐릭터의 머리나 눈이 타겟을 바라보게 합니다.

public class HeadAimController : MonoBehaviour
{
[SerializeField] private MultiAimConstraint _headAim;
[SerializeField] private MultiAimConstraint _eyeAim;
private Transform _aimTarget;
private float _currentWeight;
void Start()
{
_aimTarget = new GameObject("AimTarget").transform;
// 소스 오브젝트 목록 설정
var sources = new WeightedTransformArray(1);
sources[0] = new WeightedTransform(_aimTarget, 1f);
_headAim.data.sourceObjects = sources;
_eyeAim.data.sourceObjects = sources;
// 회전 축 제한 (Y축만 허용: 고개 돌리기만, 끄덕임 없음)
_headAim.data.constrainedAxes = new BoolVector3(true, true, false);
}
void Update()
{
// 마우스/조이스틱 방향으로 에임 타겟 이동
var ray = Camera.main.ScreenPointToRay(Input.mousePosition);
if (Physics.Raycast(ray, out RaycastHit hit, 50f))
{
_aimTarget.position = hit.point;
// 타겟이 가까울수록 강하게 바라봄
float distance = Vector3.Distance(transform.position, hit.point);
_currentWeight = Mathf.Lerp(1f, 0.3f, distance / 20f);
}
_headAim.weight = Mathf.Lerp(_headAim.weight, _currentWeight, Time.deltaTime * 5f);
}
}

public class SpineController : MonoBehaviour
{
[SerializeField] private ChainIKConstraint _spineChain;
[SerializeField] private float _leanAmount = 15f;
void Update()
{
// 캐릭터 이동 방향에 따라 척추 기울이기
Vector3 velocity = GetComponent<Rigidbody>().velocity;
float forwardSpeed = Vector3.Dot(velocity, transform.forward);
float targetLean = Mathf.Clamp(-forwardSpeed * _leanAmount / 10f,
-_leanAmount, _leanAmount);
// Chain IK 타겟 회전 조정
if (_spineChain.data.tip != null)
{
_spineChain.data.tip.localRotation = Quaternion.Euler(
Mathf.LerpAngle(
_spineChain.data.tip.localEulerAngles.x,
targetLean,
Time.deltaTime * 8f
),
0, 0
);
}
}
}

캐릭터 발이 울퉁불퉁한 지형에 맞닿게 합니다.

public class FootIKController : MonoBehaviour
{
[SerializeField] private TwoBoneIKConstraint _leftFootIK;
[SerializeField] private TwoBoneIKConstraint _rightFootIK;
[SerializeField] private LayerMask _groundMask;
[SerializeField] private float _raycastDistance = 1.5f;
private Transform _leftFootTarget;
private Transform _rightFootTarget;
void Start()
{
_leftFootTarget = new GameObject("LeftFootIKTarget").transform;
_rightFootTarget = new GameObject("RightFootIKTarget").transform;
_leftFootIK.data.target = _leftFootTarget;
_rightFootIK.data.target = _rightFootTarget;
}
void LateUpdate()
{
UpdateFootIK(_leftFootIK, _leftFootTarget, "LeftFoot");
UpdateFootIK(_rightFootIK, _rightFootTarget, "RightFoot");
}
void UpdateFootIK(TwoBoneIKConstraint ik, Transform target, string boneName)
{
// 발 뼈대 위치 가져오기
var animator = GetComponent<Animator>();
Vector3 footPos = animator.GetBoneTransform(
boneName == "LeftFoot" ? HumanBodyBones.LeftFoot : HumanBodyBones.RightFoot
).position;
// 발 아래로 레이캐스트
if (Physics.Raycast(footPos + Vector3.up * 0.5f, Vector3.down,
out RaycastHit hit, _raycastDistance, _groundMask))
{
// 지면 위치로 IK 타겟 이동
target.position = hit.point;
// 지면 법선에 맞게 발 회전
target.rotation = Quaternion.FromToRotation(Vector3.up, hit.normal)
* transform.rotation;
ik.weight = 1f;
}
else
{
ik.weight = 0f;
}
}
}

public class RigBlender : MonoBehaviour
{
[SerializeField] private Rig _combatRig; // 전투 IK
[SerializeField] private Rig _locomotionRig; // 이동 IK
private Coroutine _blendCoroutine;
public void SwitchToCombat()
{
if (_blendCoroutine != null) StopCoroutine(_blendCoroutine);
_blendCoroutine = StartCoroutine(BlendRigs(
fromRig: _locomotionRig, toRig: _combatRig, duration: 0.3f));
}
public void SwitchToLocomotion()
{
if (_blendCoroutine != null) StopCoroutine(_blendCoroutine);
_blendCoroutine = StartCoroutine(BlendRigs(
fromRig: _combatRig, toRig: _locomotionRig, duration: 0.3f));
}
private IEnumerator BlendRigs(Rig fromRig, Rig toRig, float duration)
{
float elapsed = 0f;
float startFrom = fromRig.weight;
float startTo = toRig.weight;
while (elapsed < duration)
{
elapsed += Time.deltaTime;
float t = elapsed / duration;
fromRig.weight = Mathf.Lerp(startFrom, 0f, t);
toRig.weight = Mathf.Lerp(startTo, 1f, t);
yield return null;
}
fromRig.weight = 0f;
toRig.weight = 1f;
}
}

컨스트레인트용도
Two Bone IK팔, 다리 역운동학
Multi-Aim머리, 눈이 타겟 바라보기
Chain IK척추, 꼬리 연쇄 움직임
Multi-Parent여러 부모 사이 블렌딩
Position Constraint위치만 추적
Rotation Constraint회전만 추적

Animation Rigging은 클립 기반 애니메이션과 절차적 애니메이션을 매끄럽게 결합합니다. Rig.weight를 블렌딩해 애니메이션 상태에 따라 IK를 켜고 끄면 자연스러운 캐릭터 움직임을 구현할 수 있습니다.