원클릭으로
animation
Unity animation system — Animator controllers, layers, blend trees, state machine behaviors, root motion, animation events, Timeline.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
메뉴
Unity animation system — Animator controllers, layers, blend trees, state machine behaviors, root motion, animation events, Timeline.
Codex 또는 Claude로 설치 이 Prompt를 복사해 Codex, Claude 또는 다른 어시스턴트에 붙여 넣으면 Skill 페이지를 검토하고 설치를 진행할 수 있습니다.
SOC 직업 분류 기준
Card game patterns — deck building, hand management, turn structure, card effects, battlefield zones
Racing game patterns — vehicle physics, track design, lap tracking, AI opponents, drift mechanics
Roguelike/roguelite patterns — procedural dungeons, permadeath, meta-progression, loot systems, turn-based or real-time
Tower defense game patterns — placement grids, enemy pathing, wave spawning, tower upgrades, economy
Endless runner architecture — procedural chunk spawning, lane-based or free movement, obstacle patterns, speed ramping, coin/collectible systems, distance scoring.
Hyper-casual mobile game architecture — one-tap/swipe controls, instant onboarding, short sessions, ad monetization, minimalist visuals, level progression, score systems.
| name | animation |
| description | Unity animation system — Animator controllers, layers, blend trees, state machine behaviors, root motion, animation events, Timeline. |
| globs | ["**/*.controller","**/*Anim*.cs","**/*.anim"] |
// Cache hash IDs — NEVER use string version in Update
private static readonly int k_SpeedHash = Animator.StringToHash("Speed");
private static readonly int k_JumpHash = Animator.StringToHash("Jump");
private static readonly int k_IsGroundedHash = Animator.StringToHash("IsGrounded");
private static readonly int k_AttackHash = Animator.StringToHash("Attack");
private void Update()
{
m_Animator.SetFloat(k_SpeedHash, m_CurrentSpeed);
m_Animator.SetBool(k_IsGroundedHash, m_IsGrounded);
}
// Triggers: fire once, auto-reset
public void Attack() => m_Animator.SetTrigger(k_AttackHash);
1D: Speed parameter → walk/run blend 2D Simple Directional: X/Y input → directional movement (forward, back, strafe) 2D Freeform: more flexible placement of motion clips
public sealed class AttackStateBehavior : StateMachineBehaviour
{
public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
// Enable hitbox
animator.GetComponent<CombatSystem>().EnableHitbox();
}
public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
// Disable hitbox
animator.GetComponent<CombatSystem>().DisableHitbox();
}
}
Apply Root Motion on AnimatorOnAnimatorMove() for custom control:private void OnAnimatorMove()
{
// Use animation's root motion for position
Vector3 deltaPosition = m_Animator.deltaPosition;
transform.position += deltaPosition;
// Use animation's rotation
transform.rotation *= m_Animator.deltaRotation;
}
Call methods from specific frames in animation clips:
// Called from animation event on frame 12
public void OnFootstep()
{
m_AudioSource.PlayOneShot(m_FootstepClip);
}
public void OnAttackHit()
{
// Check hitbox collisions at this exact frame
}
private void OnAnimatorIK(int layerIndex)
{
if (m_LookTarget != null)
{
m_Animator.SetLookAtWeight(1f, 0.3f, 0.6f, 1f);
m_Animator.SetLookAtPosition(m_LookTarget.position);
}
// Foot IK for uneven terrain
m_Animator.SetIKPositionWeight(AvatarIKGoal.LeftFoot, 1f);
m_Animator.SetIKPosition(AvatarIKGoal.LeftFoot, m_LeftFootTarget);
}
PlayableAsset + PlayableBehaviourAvatar masks let you isolate body regions so different layers can control different parts independently.
Create an AvatarMask asset (Assets > Create > Avatar Mask). In the Humanoid tab, toggle body parts:
Assign the mask to the Animator layer's Avatar Mask slot in the controller inspector.
private static readonly int k_UpperBodyLayerIndex = 1;
private Animator m_Animator;
private void Awake()
{
m_Animator = GetComponent<Animator>();
}
// Smoothly enable upper body override when aiming
public void SetAimWeight(float weight)
{
m_Animator.SetLayerWeight(k_UpperBodyLayerIndex, weight);
}
Layer setup in the Animator Controller:
Additive layers add motion on top of the base pose. Use them for:
Set the layer to Additive blending and control intensity via SetLayerWeight. A weight of 0.5 plays the additive clip at half intensity.
Best for locomotion where speed and direction vary independently. Place clips at positions in 2D parameter space:
Parameter X: MoveX (strafe direction, -1 to 1)
Parameter Y: MoveY (forward/backward, -1 to 1)
Clip positions:
Idle → (0, 0)
Walk Fwd → (0, 0.5)
Run Fwd → (0, 1)
Walk Back → (0, -0.5)
Strafe Left → (-1, 0)
Strafe Right→ (1, 0)
Freeform Directional handles any direction. Freeform Cartesian is better when clips map to exact positions on a grid.
Enable Mirror on individual blend tree motions to reuse left-side animations for the right side. This halves the number of directional clips needed for strafing. Set the Mirror checkbox per motion in the blend tree inspector.
Enable Foot IK on each motion in the blend tree to keep feet planted correctly during blends between walk and run. Without Foot IK, feet can slide or float when two clips blend at different step cadences.
When blending between walk and run clips with different lengths, enable Adjust Time Scale so both clips align their foot cycles. This prevents the walk foot planting at a different time than the run foot, eliminating the "moonwalk" effect during blend transitions.
StateMachineBehaviour scripts attach to Animator states and receive lifecycle callbacks. Use them for tightly timed gameplay events.
public sealed class MeleeAttackBehaviour : StateMachineBehaviour
{
[SerializeField] private float m_HitboxEnableTime = 0.2f;
[SerializeField] private float m_HitboxDisableTime = 0.6f;
private bool m_HitboxActive;
public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
m_HitboxActive = false;
}
public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
float normalizedTime = stateInfo.normalizedTime;
if (!m_HitboxActive && normalizedTime >= m_HitboxEnableTime)
{
m_HitboxActive = true;
// Cache this reference — GetComponent in OnStateEnter, store in field
animator.GetComponent<HitboxController>().EnableHitbox();
}
if (m_HitboxActive && normalizedTime >= m_HitboxDisableTime)
{
m_HitboxActive = false;
animator.GetComponent<HitboxController>().DisableHitbox();
}
}
public override void OnStateExit(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
// Safety: always disable on exit in case of interruption
if (m_HitboxActive)
{
m_HitboxActive = false;
animator.GetComponent<HitboxController>().DisableHitbox();
}
}
}
public sealed class SoundOnStateBehaviour : StateMachineBehaviour
{
[SerializeField] private AudioClip m_Clip;
[SerializeField] private float m_Volume = 1f;
public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex)
{
AudioSource source = animator.GetComponent<AudioSource>();
if (source != null)
{
source.PlayOneShot(m_Clip, m_Volume);
}
}
}
GetComponent<T> in OnStateEnter is acceptable (called once per state entry), but never in OnStateUpdate.RuntimeAnimatorController — avoid storing per-instance state that differs between characters. Use the animator parameter to access per-object data instead.Chain attacks using Animator state transitions with timed input windows.
Entry → Idle
Idle → Attack1 (trigger: Attack)
Attack1 → Attack2 (trigger: Attack, Has Exit Time: true, Exit Time: 0.6)
Attack2 → Attack3 (trigger: Attack, Has Exit Time: true, Exit Time: 0.6)
Attack3 → Idle (Has Exit Time: true)
Attack1 → Idle (Has Exit Time: true, no conditions — timeout fallback)
Attack2 → Idle (Has Exit Time: true, no conditions — timeout fallback)
public sealed class ComboSystem
{
private static readonly int k_AttackTrigger = Animator.StringToHash("Attack");
private readonly Animator m_Animator;
private int m_ComboStep;
private float m_ComboResetTimer;
private bool m_InputBuffered;
private const float k_ComboWindowDuration = 0.8f;
private const int k_MaxComboSteps = 3;
public ComboSystem(Animator animator)
{
m_Animator = animator;
}
public void OnAttackInput()
{
if (m_ComboStep == 0)
{
// Start combo
m_Animator.SetTrigger(k_AttackTrigger);
m_ComboStep = 1;
m_ComboResetTimer = k_ComboWindowDuration;
m_InputBuffered = false;
}
else if (m_ComboStep < k_MaxComboSteps)
{
// Buffer the next attack — consumed when current animation reaches exit time
m_InputBuffered = true;
}
}
public void Tick(float deltaTime)
{
if (m_ComboStep == 0)
{
return;
}
m_ComboResetTimer -= deltaTime;
if (m_ComboResetTimer <= 0f)
{
ResetCombo();
return;
}
if (m_InputBuffered)
{
m_InputBuffered = false;
m_Animator.SetTrigger(k_AttackTrigger);
m_ComboStep++;
m_ComboResetTimer = k_ComboWindowDuration;
}
}
public void ResetCombo()
{
m_ComboStep = 0;
m_InputBuffered = false;
m_ComboResetTimer = 0f;
}
}
The View calls OnAttackInput() from the input callback. Tick() runs from Update. When the current attack animation reaches its exit time window, the buffered trigger advances to the next combo state.
Set Animator.cullingMode to control what happens when the character is off-screen:
// Set in Awake or when LOD changes
m_Animator.cullingMode = AnimatorCullingMode.CullUpdateTransforms;
// Prevent state machine reset when pooling (SetActive toggle)
m_Animator.keepAnimatorStateOnDisable = true;
This is critical for object pooling. Without it, re-enabling the Animator resets to the entry state and replays Awake transitions.
For distant characters, reduce animation cost:
// Reduce bone evaluation at distance
public void SetAnimatorLOD(int lodLevel)
{
switch (lodLevel)
{
case 0: // Close — full quality
m_Animator.cullingMode = AnimatorCullingMode.AlwaysAnimate;
m_Animator.speed = 1f;
break;
case 1: // Medium — skip transforms when off-screen
m_Animator.cullingMode = AnimatorCullingMode.CullUpdateTransforms;
m_Animator.speed = 1f;
break;
case 2: // Far — cull completely, half-rate update
m_Animator.cullingMode = AnimatorCullingMode.CullCompletely;
break;
}
}
Set Animator.updateMode based on context:
Update(). Standard for most characters.FixedUpdate(). Use when root motion drives a Rigidbody.Time.timeScale. Use for UI animations and pause menus.// Physics-driven root motion character
m_Animator.updateMode = AnimatorUpdateMode.AnimatePhysics;
// UI animation that plays during pause
m_Animator.updateMode = AnimatorUpdateMode.UnscaledTime;
Add animation events at each foot-plant frame in the walk/run clips. The event calls a receiver on the same GameObject:
public sealed class AnimationEventReceiver : MonoBehaviour
{
[SerializeField] private AudioClip[] m_FootstepClips;
[SerializeField] private AudioSource m_AudioSource;
// Called by animation event — method name matches event function name
public void OnFootstep(int footIndex)
{
if (m_FootstepClips.Length == 0)
{
return;
}
int clipIndex = footIndex % m_FootstepClips.Length;
m_AudioSource.PlayOneShot(m_FootstepClips[clipIndex]);
}
}
Place animation events at impact frames to spawn VFX at the exact moment of contact. The event forwards to a VFX controller that pulls from an object pool.
Keep MonoBehaviour event receivers thin. Forward to injected systems:
public sealed class AnimEventForwarder : MonoBehaviour
{
private ICombatSystem m_CombatSystem;
private IAudioSystem m_AudioSystem;
[Inject]
public void Construct(ICombatSystem combatSystem, IAudioSystem audioSystem)
{
m_CombatSystem = combatSystem;
m_AudioSystem = audioSystem;
}
public void OnHitFrame() => m_CombatSystem.ProcessHitFrame();
public void OnFootstep(int foot) => m_AudioSystem.PlayFootstep(foot);
public void OnVFXSpawn(string vfxId) => m_CombatSystem.SpawnVFX(vfxId);
}
Animation events use method names as strings, which cannot be validated at compile time. Mitigate this by:
AnimEventForwarder component per characterOn + action name (OnFootstep, OnHitFrame, OnVFXSpawn)#if UNITY_EDITOR validation that checks event method existence on the target component