跳到正文
cccskills
免费在 GitHub 公开

tools-unity-animation

Unity animation patterns including Animancer, state machines, and performance optimization.

中文简介正在准备中。当前显示原文说明。

查看安装方法

包含的文件(1)

  • SKILL.md11.8 KB

SKILL.md(原文)

安装前可以查看交给 AI 代理的指令内容。

Unity Animation

Overview

Unity animation systems include Mecanim Animator, Animancer (3rd party), and low-level Playables API. This skill covers common patterns and optimizations.

When to Use

  • Character animations
  • UI animations
  • Procedural animation
  • Animation blending
  • State-driven animation

Animancer Patterns

Basic State Playing

using Animancer;

public class CharacterAnimator : MonoBehaviour
{
    [SerializeField] private AnimancerComponent _animancer;
    [SerializeField] private AnimationClip _idle;
    [SerializeField] private AnimationClip _run;
    [SerializeField] private AnimationClip _attack;
    
    public void PlayIdle()
    {
        _animancer.Play(_idle);
    }
    
    public void PlayRun()
    {
        _animancer.Play(_run, 0.25f); // With fade
    }
    
    public void PlayAttack(Action onComplete)
    {
        var state = _animancer.Play(_attack);
        state.Events.OnEnd = () =>
        {
            PlayIdle();
            onComplete?.Invoke();
        };
    }
}

Animation State Machine

public class AnimationStateMachine : MonoBehaviour
{
    [SerializeField] private AnimancerComponent _animancer;
    
    private readonly Dictionary<string, ClipTransition> _states = new();
    private string _currentState;
    
    public void RegisterState(string name, ClipTransition clip)
    {
        _states[name] = clip;
    }
    
    public void Play(string stateName, float fadeDuration = 0.25f)
    {
        if (_currentState == stateName) return;
        
        if (_states.TryGetValue(stateName, out var clip))
        {
            _animancer.Play(clip, fadeDuration);
            _currentState = stateName;
        }
    }
    
    public bool IsPlaying(string stateName) => _currentState == stateName;
}

Blended Movement

public class LocomotionAnimator : MonoBehaviour
{
    [SerializeField] private AnimancerComponent _animancer;
    [SerializeField] private LinearMixerTransition _locomotion;
    
    private LinearMixerState _locomotionState;
    
    private void Awake()
    {
        _locomotionState = (LinearMixerState)_animancer.Play(_locomotion);
    }
    
    public void SetSpeed(float normalizedSpeed)
    {
        // Blend between idle (0), walk (0.5), run (1)
        _locomotionState.Parameter = normalizedSpeed;
    }
}

Layer-Based Animation

public class LayeredAnimator : MonoBehaviour
{
    [SerializeField] private AnimancerComponent _animancer;
    
    private const int BaseLayer = 0;
    private const int UpperBodyLayer = 1;
    
    private void Start()
    {
        // Setup layers
        _animancer.Layers.SetCapacity(2);
        
        // Upper body layer only affects specific bones
        _animancer.Layers[UpperBodyLayer].SetMask(CreateUpperBodyMask());
    }
    
    public void PlayUpperBodyAction(AnimationClip clip)
    {
        _animancer.Layers[UpperBodyLayer].Play(clip);
    }
    
    public void StopUpperBodyLayer(float fadeDuration = 0.2f)
    {
        _animancer.Layers[UpperBodyLayer].StartFade(0, fadeDuration);
    }
    
    private AvatarMask CreateUpperBodyMask()
    {
        // Create or load avatar mask for upper body
        var mask = new AvatarMask();
        // Configure mask...
        return mask;
    }
}

Mecanim Patterns

Safe Parameter Setting

public class SafeAnimator : MonoBehaviour
{
    private Animator _animator;
    private readonly Dictionary<string, int> _parameterHashes = new();
    private readonly HashSet<int> _validParameters = new();
    
    private void Awake()
    {
        _animator = GetComponent<Animator>();
        CacheParameters();
    }
    
    private void CacheParameters()
    {
        foreach (var param in _animator.parameters)
        {
            _parameterHashes[param.name] = param.nameHash;
            _validParameters.Add(param.nameHash);
        }
    }
    
    public void SetFloat(string name, float value)
    {
        if (TryGetHash(name, out int hash))
        {
            _animator.SetFloat(hash, value);
        }
    }
    
    public void SetBool(string name, bool value)
    {
        if (TryGetHash(name, out int hash))
        {
            _animator.SetBool(hash, value);
        }
    }
    
    public void SetTrigger(string name)
    {
        if (TryGetHash(name, out int hash))
        {
            _animator.SetTrigger(hash);
        }
    }
    
    private bool TryGetHash(string name, out int hash)
    {
        if (!_parameterHashes.TryGetValue(name, out hash))
        {
            hash = Animator.StringToHash(name);
            _parameterHashes[name] = hash;
        }
        
        return _validParameters.Contains(hash);
    }
}

State Checking

public static class AnimatorExtensions
{
    public static bool IsInState(this Animator animator, string stateName, int layer = 0)
    {
        var stateInfo = animator.GetCurrentAnimatorStateInfo(layer);
        return stateInfo.IsName(stateName);
    }
    
    public static bool IsTransitioning(this Animator animator, int layer = 0)
    {
        return animator.IsInTransition(layer);
    }
    
    public static float GetNormalizedTime(this Animator animator, int layer = 0)
    {
        var stateInfo = animator.GetCurrentAnimatorStateInfo(layer);
        return stateInfo.normalizedTime % 1f;
    }
    
    public static bool IsAnimationComplete(this Animator animator, int layer = 0)
    {
        var stateInfo = animator.GetCurrentAnimatorStateInfo(layer);
        return !stateInfo.loop && stateInfo.normalizedTime >= 1f;
    }
}

Animation Events

Safe Event Handling

public class AnimationEventReceiver : MonoBehaviour
{
    public event Action<string> OnAnimationEvent;
    
    private readonly Dictionary<string, Action> _eventHandlers = new();
    
    public void RegisterHandler(string eventName, Action handler)
    {
        _eventHandlers[eventName] = handler;
    }
    
    public void UnregisterHandler(string eventName)
    {
        _eventHandlers.Remove(eventName);
    }
    
    // Called by animation events
    public void OnAnimEvent(string eventName)
    {
        if (_eventHandlers.TryGetValue(eventName, out var handler))
        {
            handler?.Invoke();
        }
        
        OnAnimationEvent?.Invoke(eventName);
    }
    
    // Common events
    public void FootstepLeft() => OnAnimEvent("FootstepLeft");
    public void FootstepRight() => OnAnimEvent("FootstepRight");
    public void AttackHit() => OnAnimEvent("AttackHit");
    public void AttackEnd() => OnAnimEvent("AttackEnd");
}

Async Animation Waiting

public static class AnimationAsyncExtensions
{
    public static async UniTask WaitForStateComplete(
        this Animator animator, 
        string stateName, 
        int layer = 0,
        CancellationToken ct = default)
    {
        // Wait for state to start
        while (!animator.IsInState(stateName, layer) && !ct.IsCancellationRequested)
        {
            await UniTask.Yield(ct);
        }
        
        // Wait for state to complete
        while (!animator.IsAnimationComplete(layer) && !ct.IsCancellationRequested)
        {
            await UniTask.Yield(ct);
        }
    }
    
    public static async UniTask PlayAndWait(
        this AnimancerComponent animancer,
        AnimationClip clip,
        CancellationToken ct = default)
    {
        var state = animancer.Play(clip);
        
        await UniTask.WaitUntil(
            () => state.NormalizedTime >= 1f || !state.IsPlaying,
            cancellationToken: ct
        );
    }
}

Root Motion

Controlled Root Motion

public class RootMotionController : MonoBehaviour
{
    private Animator _animator;
    private CharacterController _controller;
    
    [SerializeField] private bool _useRootPosition = true;
    [SerializeField] private bool _useRootRotation = true;
    [SerializeField] private float _rootMotionScale = 1f;
    
    private void Awake()
    {
        _animator = GetComponent<Animator>();
        _controller = GetComponent<CharacterController>();
    }
    
    private void OnAnimatorMove()
    {
        if (_useRootPosition)
        {
            Vector3 deltaPosition = _animator.deltaPosition * _rootMotionScale;
            _controller.Move(deltaPosition);
        }
        
        if (_useRootRotation)
        {
            transform.rotation *= _animator.deltaRotation;
        }
    }
    
    public void SetRootMotionEnabled(bool position, bool rotation)
    {
        _useRootPosition = position;
        _useRootRotation = rotation;
    }
}

Performance Optimization

Animator Culling

public class AnimatorOptimizer : MonoBehaviour
{
    private Animator _animator;
    
    private void Awake()
    {
        _animator = GetComponent<Animator>();
        
        // Configure culling based on use case
        if (IsMainCharacter())
        {
            _animator.cullingMode = AnimatorCullingMode.AlwaysAnimate;
        }
        else
        {
            _animator.cullingMode = AnimatorCullingMode.CullUpdateTransforms;
        }
    }
    
    private bool IsMainCharacter()
    {
        return CompareTag("Player");
    }
}

LOD Animation

public class AnimationLOD : MonoBehaviour
{
    private Animator _animator;
    private float _lastUpdateTime;
    
    [SerializeField] private float _nearDistance = 10f;
    [SerializeField] private float _farDistance = 30f;
    
    private Camera _mainCamera;
    
    private void Start()
    {
        _animator = GetComponent<Animator>();
        _mainCamera = Camera.main;
    }
    
    private void LateUpdate()
    {
        float distance = Vector3.Distance(transform.position, _mainCamera.transform.position);
        
        if (distance > _farDistance)
        {
            // Very far - minimal updates
            _animator.updateMode = AnimatorUpdateMode.Normal;
            _animator.speed = 0.5f; // Slow motion illusion from distance
        }
        else if (distance > _nearDistance)
        {
            // Medium distance
            _animator.updateMode = AnimatorUpdateMode.Normal;
            _animator.speed = 1f;
        }
        else
        {
            // Close - full quality
            _animator.updateMode = AnimatorUpdateMode.AnimatePhysics;
            _animator.speed = 1f;
        }
    }
}

Disable When Offscreen

public class AnimatorVisibilityOptimizer : MonoBehaviour
{
    private Animator _animator;
    private Renderer _renderer;
    
    private void Awake()
    {
        _animator = GetComponent<Animator>();
        _renderer = GetComponentInChildren<Renderer>();
    }
    
    private void OnBecameInvisible()
    {
        if (_animator != null)
        {
            _animator.enabled = false;
        }
    }
    
    private void OnBecameVisible()
    {
        if (_animator != null)
        {
            _animator.enabled = true;
        }
    }
}

Best Practices

  1. Use Animancer for flexibility over Mecanim
  2. Cache parameter hashes - Avoid string lookups
  3. Set culling modes appropriately
  4. Use animation events sparingly
  5. Pool animated objects - Avoid Instantiate
  6. Reduce bone count for mobile
  7. Use LOD for distant characters
  8. Disable when invisible - Save CPU
  9. Bake animations when possible
  10. Profile animation cost - Major performance factor

Troubleshooting

IssueSolution
Animation not playingCheck Animator enabled, state exists
Jerky transitionsAdjust fade duration
Root motion driftVerify Apply Root Motion setting
Parameter not foundCache and validate parameters
Performance issuesEnable culling, reduce bones
Stuck in stateCheck exit conditions, transitions

评价

还没有评价。欢迎分享你的使用感受。

评论

无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。

还没有评论。来写第一条吧。

提交后会立即公开。请勿填写邮箱、电话等个人信息。URL 最多 2 个。

0 / 1000 字

此表单受 reCAPTCHA 保护,适用 Google 的隐私权政策和服务条款。

同一仓库的技能

adopt

免费

Brownfield onboarding — audits existing project artifacts for template format compliance (not just existence), classifies gaps by impact, and produces a numbered migration plan. Run this when joining an in-progress project or upgrading from an older template version. Distinct from /project-stage-detect (which checks what exists) — this checks whether what exists will actually work with the template's skills.

中文简介正在准备中。当前显示原文说明。

IdoCohen560/claude-unity-game-studio222026年7月8日 更新

Creates an Architecture Decision Record (ADR) documenting a significant technical decision, its context, alternatives considered, and consequences. Every major technical choice should have an ADR.

中文简介正在准备中。当前显示原文说明。

IdoCohen560/claude-unity-game-studio222026年7月8日 更新

Validates completeness and consistency of the project architecture against all GDDs. Builds a traceability matrix mapping every GDD technical requirement to ADRs, identifies coverage gaps, detects cross-ADR conflicts, verifies engine compatibility consistency across all decisions, and produces a PASS/CONCERNS/FAIL verdict. The architecture equivalent of /design-review.

中文简介正在准备中。当前显示原文说明。

IdoCohen560/claude-unity-game-studio222026年7月8日 更新

art-bible

免费

Guided, section-by-section Art Bible authoring. Creates the visual identity specification that gates all asset production. Run after /brainstorm is approved and before /map-systems or any GDD authoring begins.

中文简介正在准备中。当前显示原文说明。

IdoCohen560/claude-unity-game-studio222026年7月8日 更新

Audits game assets for compliance with naming conventions, file size budgets, format standards, and pipeline requirements. Identifies orphaned assets, missing references, and standard violations.

中文简介正在准备中。当前显示原文说明。

IdoCohen560/claude-unity-game-studio222026年7月8日 更新

Generate per-asset visual specifications and AI generation prompts from GDDs, level docs, or character profiles. Produces structured spec files and updates the master asset manifest. Run after art bible and GDD/level design are approved, before production begins.

中文简介正在准备中。当前显示原文说明。

IdoCohen560/claude-unity-game-studio222026年7月8日 更新

查看 IdoCohen560 的全部技能

报告此技能的问题