Unity Animation Rigging 시스템
Animation Rigging이란
섹션 제목: “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 (컴포넌트)Two Bone IK — 팔/다리 IK
섹션 제목: “Two Bone IK — 팔/다리 IK”손이 특정 위치에 닿도록 팔 전체를 역방향으로 계산합니다.
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); }}Chain IK — 척추 흔들림
섹션 제목: “Chain IK — 척추 흔들림”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 ); } }}발 IK — 지형 적응
섹션 제목: “발 IK — 지형 적응”캐릭터 발이 울퉁불퉁한 지형에 맞닿게 합니다.
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; } }}런타임 Rig 가중치 블렌딩
섹션 제목: “런타임 Rig 가중치 블렌딩”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를 켜고 끄면 자연스러운 캐릭터 움직임을 구현할 수 있습니다.