qiuqiu 5 months ago
parent
commit
b07447dd01

+ 5 - 0
笔记文件/2.笔记/unity动画序列帧代码实现 代码缓存.md

@@ -0,0 +1,5 @@
+#unity/日常积累 
+
+[[unity动画序列帧代码实现 代码缓存_第一章|第一章]]
+
+[[unity动画序列帧代码实现 代码缓存_第二章|第二章]]

+ 1094 - 0
笔记文件/2.笔记/unity动画序列帧代码实现 代码缓存_第一章.md

@@ -0,0 +1,1094 @@
+单序列帧动画 & Example示例
+
+## SequenceFramePlayer
+
+
+``` cs
+using System.Collections;
+using System.Collections.Generic;
+using UnityEngine;
+using UnityEngine.UI;
+
+/// <summary>
+/// 动画序列数据结构
+/// </summary>
+[System.Serializable]
+public class AnimationSequence
+{
+    [Header("序列设置")]
+    public string name = "Animation"; // 动画名称
+    public Sprite[] frames; // 序列帧图片数组
+    [Range(1f, 120f)]
+    public float framerate = 30f; // 播放帧率
+    public bool loop = true; // 是否循环播放
+    
+    [Header("渲染组件")]
+    public SpriteRenderer targetSpriteRenderer; // 目标SpriteRenderer组件
+    public Image targetImage; // 目标UI Image组件
+    public bool useGlobalRenderer = true; // 是否使用全局渲染器(如果为false,使用上面指定的组件)
+    
+    [Header("渲染设置")]
+    public bool setNativeSize = false; // 是否设置为原始尺寸(仅对Image有效)
+    public bool preserveAspect = true; // 是否保持宽高比(仅对Image有效)
+    public Color tintColor = Color.white; // 着色颜色
+    public Material overrideMaterial; // 覆盖材质
+    
+    [Header("变换设置")]
+    public bool useCustomScale = false; // 是否使用自定义缩放
+    public Vector3 customScale = Vector3.one; // 自定义缩放值
+    public bool useCustomRotation = false; // 是否使用自定义旋转
+    public Vector3 customRotation = Vector3.zero; // 自定义旋转值
+    
+    [Header("排序设置")]
+    public bool overrideSortingOrder = false; // 是否覆盖排序层级
+    public int sortingOrder = 0; // 排序层级
+    public string sortingLayerName = "Default"; // 排序层名称
+    
+    [Header("曲线控制")]
+    public AnimationCurve curve; // 动画曲线,用于控制播放速度
+    public bool useCurve = false; // 是否使用曲线控制
+    
+    // 运行时数据(不序列化)
+    [System.NonSerialized]
+    private Vector3 originalScale;
+    [System.NonSerialized]
+    private Vector3 originalRotation;
+    [System.NonSerialized]
+    private Color originalColor;
+    [System.NonSerialized]
+    private Material originalMaterial;
+    [System.NonSerialized]
+    private int originalSortingOrder;
+    [System.NonSerialized]
+    private string originalSortingLayer;
+    [System.NonSerialized]
+    private bool hasStoredOriginalValues = false;
+    
+    // 构造函数
+    public AnimationSequence()
+    {
+        curve = AnimationCurve.Linear(0f, 1f, 1f, 1f);
+    }
+    
+    public AnimationSequence(string animName, Sprite[] animFrames, float rate = 30f, bool isLoop = true)
+    {
+        name = animName;
+        frames = animFrames;
+        framerate = rate;
+        loop = isLoop;
+        curve = AnimationCurve.Linear(0f, 1f, 1f, 1f);
+    }
+    
+    /// <summary>
+    /// 存储原始值(用于恢复)
+    /// </summary>
+    public void StoreOriginalValues(SpriteRenderer spriteRenderer, Image image)
+    {
+        if (hasStoredOriginalValues) return;
+        
+        if (!useGlobalRenderer)
+        {
+            if (targetSpriteRenderer != null)
+                spriteRenderer = targetSpriteRenderer;
+            if (targetImage != null)
+                image = targetImage;
+        }
+        
+        if (spriteRenderer != null)
+        {
+            originalScale = spriteRenderer.transform.localScale;
+            originalRotation = spriteRenderer.transform.localEulerAngles;
+            originalColor = spriteRenderer.color;
+            originalMaterial = spriteRenderer.material;
+            originalSortingOrder = spriteRenderer.sortingOrder;
+            originalSortingLayer = spriteRenderer.sortingLayerName;
+        }
+        else if (image != null)
+        {
+            originalScale = image.transform.localScale;
+            originalRotation = image.transform.localEulerAngles;
+            originalColor = image.color;
+            originalMaterial = image.material;
+        }
+        
+        hasStoredOriginalValues = true;
+    }
+    
+    /// <summary>
+    /// 恢复原始值
+    /// </summary>
+    public void RestoreOriginalValues(SpriteRenderer spriteRenderer, Image image)
+    {
+        if (!hasStoredOriginalValues) return;
+        
+        if (!useGlobalRenderer)
+        {
+            if (targetSpriteRenderer != null)
+                spriteRenderer = targetSpriteRenderer;
+            if (targetImage != null)
+                image = targetImage;
+        }
+        
+        if (spriteRenderer != null)
+        {
+            spriteRenderer.transform.localScale = originalScale;
+            spriteRenderer.transform.localEulerAngles = originalRotation;
+            spriteRenderer.color = originalColor;
+            spriteRenderer.material = originalMaterial;
+            spriteRenderer.sortingOrder = originalSortingOrder;
+            spriteRenderer.sortingLayerName = originalSortingLayer;
+        }
+        else if (image != null)
+        {
+            image.transform.localScale = originalScale;
+            image.transform.localEulerAngles = originalRotation;
+            image.color = originalColor;
+            image.material = originalMaterial;
+        }
+    }
+    
+    /// <summary>
+    /// 应用渲染设置
+    /// </summary>
+    public void ApplyRenderSettings(SpriteRenderer spriteRenderer, Image image)
+    {
+        if (!useGlobalRenderer)
+        {
+            if (targetSpriteRenderer != null)
+                spriteRenderer = targetSpriteRenderer;
+            if (targetImage != null)
+                image = targetImage;
+        }
+        
+        if (spriteRenderer != null)
+        {
+            // 应用颜色
+            spriteRenderer.color = tintColor;
+            
+            // 应用材质
+            if (overrideMaterial != null)
+                spriteRenderer.material = overrideMaterial;
+            
+            // 应用排序设置
+            if (overrideSortingOrder)
+            {
+                spriteRenderer.sortingOrder = sortingOrder;
+                spriteRenderer.sortingLayerName = sortingLayerName;
+            }
+            
+            // 应用变换设置
+            if (useCustomScale)
+                spriteRenderer.transform.localScale = customScale;
+            if (useCustomRotation)
+                spriteRenderer.transform.localEulerAngles = customRotation;
+        }
+        else if (image != null)
+        {
+            // 应用颜色
+            image.color = tintColor;
+            
+            // 应用材质
+            if (overrideMaterial != null)
+                image.material = overrideMaterial;
+            
+            // 应用Image特有设置
+            if (setNativeSize)
+                image.SetNativeSize();
+            image.preserveAspect = preserveAspect;
+            
+            // 应用变换设置
+            if (useCustomScale)
+                image.transform.localScale = customScale;
+            if (useCustomRotation)
+                image.transform.localEulerAngles = customRotation;
+        }
+    }
+    
+    /// <summary>
+    /// 获取实际使用的SpriteRenderer
+    /// </summary>
+    public SpriteRenderer GetSpriteRenderer(SpriteRenderer defaultRenderer)
+    {
+        return useGlobalRenderer ? defaultRenderer : targetSpriteRenderer;
+    }
+    
+    /// <summary>
+    /// 获取实际使用的Image
+    /// </summary>
+    public Image GetImage(Image defaultImage)
+    {
+        return useGlobalRenderer ? defaultImage : targetImage;
+    }
+}
+
+/// <summary>
+/// 序列帧动画播放器
+/// 支持SpriteRenderer和Image两种组件,支持多个动画序列
+/// </summary>
+public class SequenceFramePlayer : MonoBehaviour
+{
+    [Header("动画序列")]
+    [SerializeField] private List<AnimationSequence> animations = new List<AnimationSequence>(); // 多个动画序列
+    [SerializeField] private int defaultAnimationIndex = 0; // 默认播放的动画索引
+    [SerializeField] private bool playOnStart = true; // 启动时自动播放
+    [SerializeField] private bool ignoreTimeScale = false; // 是否忽略时间缩放
+    
+    [Header("渲染组件")]
+    [SerializeField] private SpriteRenderer spriteRenderer; // SpriteRenderer组件
+    [SerializeField] private Image image; // UI Image组件
+    
+    // 私有变量
+    private int currentAnimationIndex = 0; // 当前播放的动画索引
+    private int currentFrameIndex = 0;
+    private float timer = 0f;
+    private bool isPlaying = false;
+    
+    // 属性
+    public bool IsPlaying => isPlaying;
+    public int CurrentAnimationIndex => currentAnimationIndex;
+    public string CurrentAnimationName => GetCurrentAnimation()?.name ?? "None";
+    public int CurrentFrame => currentFrameIndex;
+    public int TotalFrames => GetCurrentAnimation()?.frames?.Length ?? 0;
+    public float Progress => TotalFrames > 0 ? (float)currentFrameIndex / TotalFrames : 0f;
+    public int AnimationCount => animations?.Count ?? 0;
+    
+    // 事件
+    public System.Action OnAnimationStart;
+    public System.Action OnAnimationComplete;
+    public System.Action<int> OnFrameChanged;
+    public System.Action<int, string> OnAnimationChanged; // 动画切换事件 (索引, 名称)
+    
+    private void Awake()
+    {
+        // 自动获取组件
+        if (spriteRenderer == null)
+            spriteRenderer = GetComponent<SpriteRenderer>();
+        if (image == null)
+            image = GetComponent<Image>();
+            
+        // 初始化默认动画索引
+        if (defaultAnimationIndex >= 0 && defaultAnimationIndex < animations.Count)
+        {
+            currentAnimationIndex = defaultAnimationIndex;
+        }
+    }
+    
+    private void Start()
+    {
+        if (playOnStart && HasValidAnimation())
+        {
+            Play();
+        }
+    }
+    
+    /// <summary>
+    /// 获取当前动画序列
+    /// </summary>
+    private AnimationSequence GetCurrentAnimation()
+    {
+        if (animations == null || currentAnimationIndex < 0 || currentAnimationIndex >= animations.Count)
+            return null;
+        return animations[currentAnimationIndex];
+    }
+    
+    /// <summary>
+    /// 检查当前是否有有效的动画
+    /// </summary>
+    private bool HasValidAnimation()
+    {
+        var anim = GetCurrentAnimation();
+        return anim != null && anim.frames != null && anim.frames.Length > 0;
+    }
+    
+    private void Update()
+    {
+        if (!isPlaying || !HasValidAnimation())
+            return;
+            
+        var currentAnim = GetCurrentAnimation();
+        
+        // 计算最终帧率
+        float finalFramerate = currentAnim.framerate;
+        
+        if (currentAnim.useCurve && currentAnim.curve != null)
+        {
+            // 从曲线获取当前帧率
+            float curveValue = currentAnim.curve.Evaluate((float)currentFrameIndex / currentAnim.frames.Length);
+            finalFramerate = curveValue * currentAnim.framerate;
+        }
+        
+        // 帧率有效(添加最小值保护)
+        if (finalFramerate > 0.01f) // 最小帧率保护
+        {
+            // 获取当前时间
+            float time = ignoreTimeScale ? Time.unscaledTime : Time.time;
+            // 计算帧间隔时间
+            float interval = 1.0f / finalFramerate;
+            
+            // 满足更新条件,执行更新操作
+            if (time - timer > interval)
+            {
+                // 执行更新操作
+                DoUpdate();
+                timer = time;
+            }
+        }
+        else
+        {
+            // 帧率过低时,使用默认帧率
+            Debug.LogWarning($"Framerate too low ({finalFramerate}), using default framerate.");
+            finalFramerate = 1f; // 使用1帧/秒作为最低帧率
+            
+            float time = ignoreTimeScale ? Time.unscaledTime : Time.time;
+            float interval = 1.0f / finalFramerate;
+            
+            if (time - timer > interval)
+            {
+                DoUpdate();
+                timer = time;
+            }
+        }
+    }
+    
+    /// <summary>
+    /// 执行帧更新
+    /// </summary>
+    private void DoUpdate()
+    {
+        var currentAnim = GetCurrentAnimation();
+        if (currentAnim == null) return;
+        
+        // 更新帧索引
+        currentFrameIndex++;
+        
+        // 检查是否播放完成
+        if (currentFrameIndex >= currentAnim.frames.Length)
+        {
+            if (currentAnim.loop)
+            {
+                currentFrameIndex = 0; // 循环播放
+            }
+            else
+            {
+                currentFrameIndex = currentAnim.frames.Length - 1;
+                Stop();
+                OnAnimationComplete?.Invoke();
+                return;
+            }
+        }
+        
+        // 更新显示的帧
+        UpdateFrame();
+        
+        // 触发帧变化事件
+        OnFrameChanged?.Invoke(currentFrameIndex);
+    }
+    
+    /// <summary>
+    /// 更新当前显示的帧
+    /// </summary>
+    private void UpdateFrame()
+    {
+        var currentAnim = GetCurrentAnimation();
+        if (currentAnim?.frames == null || currentFrameIndex < 0 || currentFrameIndex >= currentAnim.frames.Length)
+            return;
+            
+        Sprite currentSprite = currentAnim.frames[currentFrameIndex];
+        
+        // 获取实际使用的渲染组件
+        SpriteRenderer targetSpriteRenderer = currentAnim.GetSpriteRenderer(spriteRenderer);
+        Image targetImage = currentAnim.GetImage(image);
+        
+        // 更新SpriteRenderer
+        if (targetSpriteRenderer != null)
+        {
+            targetSpriteRenderer.sprite = currentSprite;
+        }
+        
+        // 更新Image
+        if (targetImage != null)
+        {
+            targetImage.sprite = currentSprite;
+        }
+    }
+    
+    /// <summary>
+    /// 开始播放当前动画
+    /// </summary>
+    public void Play()
+    {
+        if (!HasValidAnimation())
+        {
+            Debug.LogWarning("No valid animation to play!");
+            return;
+        }
+        
+        // 应用当前动画的渲染设置
+        var currentAnim = GetCurrentAnimation();
+        if (currentAnim != null)
+        {
+            currentAnim.StoreOriginalValues(spriteRenderer, image);
+            currentAnim.ApplyRenderSettings(spriteRenderer, image);
+        }
+        
+        isPlaying = true;
+        timer = ignoreTimeScale ? Time.unscaledTime : Time.time;
+        UpdateFrame();
+        OnAnimationStart?.Invoke();
+    }
+    
+    /// <summary>
+    /// 播放指定索引的动画
+    /// </summary>
+    public void PlayAnimation(int animationIndex)
+    {
+        if (SetAnimation(animationIndex))
+        {
+            Play();
+        }
+    }
+    
+    /// <summary>
+    /// 播放指定名称的动画
+    /// </summary>
+    public void PlayAnimation(string animationName)
+    {
+        int index = GetAnimationIndex(animationName);
+        if (index >= 0)
+        {
+            PlayAnimation(index);
+        }
+        else
+        {
+            Debug.LogWarning($"Animation '{animationName}' not found!");
+        }
+    }
+    
+    /// <summary>
+    /// 停止播放
+    /// </summary>
+    public void Stop()
+    {
+        isPlaying = false;
+    }
+    
+    /// <summary>
+    /// 暂停播放
+    /// </summary>
+    public void Pause()
+    {
+        isPlaying = false;
+    }
+    
+    /// <summary>
+    /// 重置到第一帧
+    /// </summary>
+    public void Reset()
+    {
+        currentFrameIndex = 0;
+        UpdateFrame();
+    }
+    
+    /// <summary>
+    /// 跳转到指定帧
+    /// </summary>
+    public void SetFrame(int frameIndex)
+    {
+        var currentAnim = GetCurrentAnimation();
+        if (currentAnim?.frames == null || frameIndex < 0 || frameIndex >= currentAnim.frames.Length)
+            return;
+            
+        currentFrameIndex = frameIndex;
+        UpdateFrame();
+        OnFrameChanged?.Invoke(currentFrameIndex);
+    }
+    
+    /// <summary>
+    /// 设置当前动画
+    /// </summary>
+    public bool SetAnimation(int animationIndex)
+    {
+        if (animations == null || animationIndex < 0 || animationIndex >= animations.Count)
+        {
+            Debug.LogWarning($"Invalid animation index: {animationIndex}");
+            return false;
+        }
+        
+        if (currentAnimationIndex != animationIndex)
+        {
+            // 恢复之前动画的设置
+            var previousAnim = GetCurrentAnimation();
+            if (previousAnim != null)
+            {
+                previousAnim.RestoreOriginalValues(spriteRenderer, image);
+            }
+            
+            // 切换到新动画
+            currentAnimationIndex = animationIndex;
+            
+            // 存储并应用新动画的设置
+            var newAnim = GetCurrentAnimation();
+            if (newAnim != null)
+            {
+                newAnim.StoreOriginalValues(spriteRenderer, image);
+                newAnim.ApplyRenderSettings(spriteRenderer, image);
+            }
+            
+            Reset();
+            OnAnimationChanged?.Invoke(currentAnimationIndex, CurrentAnimationName);
+        }
+        return true;
+    }
+    
+    /// <summary>
+    /// 设置当前动画(通过名称)
+    /// </summary>
+    public bool SetAnimation(string animationName)
+    {
+        int index = GetAnimationIndex(animationName);
+        if (index >= 0)
+        {
+            return SetAnimation(index);
+        }
+        return false;
+    }
+    
+    /// <summary>
+    /// 获取动画索引(通过名称)
+    /// </summary>
+    public int GetAnimationIndex(string animationName)
+    {
+        if (animations == null) return -1;
+        
+        for (int i = 0; i < animations.Count; i++)
+        {
+            if (animations[i].name == animationName)
+                return i;
+        }
+        return -1;
+    }
+    
+    /// <summary>
+    /// 添加新的动画序列
+    /// </summary>
+    public void AddAnimation(AnimationSequence newAnimation)
+    {
+        if (animations == null)
+            animations = new List<AnimationSequence>();
+        animations.Add(newAnimation);
+    }
+    
+    /// <summary>
+    /// 添加新的动画序列
+    /// </summary>
+    public void AddAnimation(string name, Sprite[] frames, float framerate = 30f, bool loop = true)
+    {
+        AddAnimation(new AnimationSequence(name, frames, framerate, loop));
+    }
+    
+    /// <summary>
+    /// 移除动画序列
+    /// </summary>
+    public bool RemoveAnimation(int index)
+    {
+        if (animations == null || index < 0 || index >= animations.Count)
+            return false;
+            
+        animations.RemoveAt(index);
+        
+        // 调整当前动画索引
+        if (currentAnimationIndex >= animations.Count)
+        {
+            currentAnimationIndex = Mathf.Max(0, animations.Count - 1);
+        }
+        return true;
+    }
+    
+    /// <summary>
+    /// 移除动画序列
+    /// </summary>
+    public bool RemoveAnimation(string animationName)
+    {
+        int index = GetAnimationIndex(animationName);
+        return index >= 0 && RemoveAnimation(index);
+    }
+    
+    /// <summary>
+    /// 获取所有动画名称
+    /// </summary>
+    public string[] GetAnimationNames()
+    {
+        if (animations == null) return new string[0];
+        
+        string[] names = new string[animations.Count];
+        for (int i = 0; i < animations.Count; i++)
+        {
+            names[i] = animations[i].name;
+        }
+        return names;
+    }
+    
+    /// <summary>
+    /// 获取当前动画使用的SpriteRenderer
+    /// </summary>
+    public SpriteRenderer GetCurrentSpriteRenderer()
+    {
+        var currentAnim = GetCurrentAnimation();
+        return currentAnim?.GetSpriteRenderer(spriteRenderer);
+    }
+    
+    /// <summary>
+    /// 获取当前动画使用的Image
+    /// </summary>
+    public Image GetCurrentImage()
+    {
+        var currentAnim = GetCurrentAnimation();
+        return currentAnim?.GetImage(image);
+    }
+    
+    /// <summary>
+    /// 设置当前动画的渲染颜色
+    /// </summary>
+    public void SetCurrentAnimationColor(Color color)
+    {
+        var currentAnim = GetCurrentAnimation();
+        if (currentAnim != null)
+        {
+            currentAnim.tintColor = color;
+            
+            // 立即应用
+            SpriteRenderer targetSpriteRenderer = currentAnim.GetSpriteRenderer(spriteRenderer);
+            Image targetImage = currentAnim.GetImage(image);
+            
+            if (targetSpriteRenderer != null)
+                targetSpriteRenderer.color = color;
+            if (targetImage != null)
+                targetImage.color = color;
+        }
+    }
+    
+    /// <summary>
+    /// 设置当前动画的排序层级
+    /// </summary>
+    public void SetCurrentAnimationSortingOrder(int order)
+    {
+        var currentAnim = GetCurrentAnimation();
+        if (currentAnim != null)
+        {
+            currentAnim.sortingOrder = order;
+            currentAnim.overrideSortingOrder = true;
+            
+            // 立即应用
+            SpriteRenderer targetSpriteRenderer = currentAnim.GetSpriteRenderer(spriteRenderer);
+            if (targetSpriteRenderer != null)
+                targetSpriteRenderer.sortingOrder = order;
+        }
+    }
+    
+    /// <summary>
+    /// 设置当前动画的缩放
+    /// </summary>
+    public void SetCurrentAnimationScale(Vector3 scale)
+    {
+        var currentAnim = GetCurrentAnimation();
+        if (currentAnim != null)
+        {
+            currentAnim.customScale = scale;
+            currentAnim.useCustomScale = true;
+            
+            // 立即应用
+            SpriteRenderer targetSpriteRenderer = currentAnim.GetSpriteRenderer(spriteRenderer);
+            Image targetImage = currentAnim.GetImage(image);
+            
+            if (targetSpriteRenderer != null)
+                targetSpriteRenderer.transform.localScale = scale;
+            if (targetImage != null)
+                targetImage.transform.localScale = scale;
+        }
+    }
+    
+    /// <summary>
+    /// 为指定动画设置目标渲染组件
+    /// </summary>
+    public void SetAnimationRenderer(int animationIndex, SpriteRenderer targetRenderer, Image targetImg = null)
+    {
+        if (animations == null || animationIndex < 0 || animationIndex >= animations.Count)
+            return;
+            
+        animations[animationIndex].targetSpriteRenderer = targetRenderer;
+        animations[animationIndex].targetImage = targetImg;
+        animations[animationIndex].useGlobalRenderer = (targetRenderer == null && targetImg == null);
+    }
+    
+    /// <summary>
+    /// 为指定动画设置目标渲染组件(通过名称)
+    /// </summary>
+    public void SetAnimationRenderer(string animationName, SpriteRenderer targetRenderer, Image targetImg = null)
+    {
+        int index = GetAnimationIndex(animationName);
+        if (index >= 0)
+        {
+            SetAnimationRenderer(index, targetRenderer, targetImg);
+        }
+    }
+    
+    /// <summary>
+    /// 启用/禁用组件
+    /// </summary>
+    private void OnEnable()
+    {
+        if (playOnStart && HasValidAnimation())
+        {
+            Play();
+        }
+    }
+    
+    private void OnDisable()
+    {
+        Stop();
+        
+        // 恢复原始设置
+        var currentAnim = GetCurrentAnimation();
+        if (currentAnim != null)
+        {
+            currentAnim.RestoreOriginalValues(spriteRenderer, image);
+        }
+    }
+} 
+```
+
+## SequenceFramePlayerExample
+
+``` cs
+using System.Collections;
+using UnityEngine;
+using UnityEngine.UI;
+
+/// <summary>
+/// SequenceFramePlayer 使用示例
+/// </summary>
+public class SequenceFramePlayerExample : MonoBehaviour
+{
+    [Header("示例组件")]
+    public SequenceFramePlayer framePlayer;
+    public SpriteRenderer backgroundRenderer;
+    public SpriteRenderer characterRenderer;
+    public Image uiImage;
+    
+    [Header("示例序列帧")]
+    public Sprite[] idleFrames;
+    public Sprite[] walkFrames;
+    public Sprite[] attackFrames;
+    public Sprite[] uiFrames;
+    
+    private void Start()
+    {
+        // 示例1:基础使用
+        BasicUsageExample();
+        
+        // 示例2:多渲染器使用
+        MultiRendererExample();
+        
+        // 示例3:运行时动态管理
+        RuntimeManagementExample();
+        
+        // 示例4:事件监听
+        EventListenerExample();
+    }
+    
+    /// <summary>
+    /// 示例1:基础使用 - 简单播放动画
+    /// </summary>
+    void BasicUsageExample()
+    {
+        Debug.Log("=== 基础使用示例 ===");
+        
+        // 直接播放当前动画(默认第0个)
+        framePlayer.Play();
+        
+        // 播放指定索引的动画
+        framePlayer.PlayAnimation(1);
+        
+        // 播放指定名称的动画
+        framePlayer.PlayAnimation("idle");
+        
+        // 控制播放
+        StartCoroutine(BasicPlaybackControl());
+    }
+    
+    IEnumerator BasicPlaybackControl()
+    {
+        yield return new WaitForSeconds(2f);
+        
+        // 暂停
+        framePlayer.Pause();
+        Debug.Log("动画暂停");
+        
+        yield return new WaitForSeconds(1f);
+        
+        // 继续播放
+        framePlayer.Play();
+        Debug.Log("动画继续");
+        
+        yield return new WaitForSeconds(2f);
+        
+        // 跳转到指定帧
+        framePlayer.SetFrame(5);
+        Debug.Log("跳转到第5帧");
+        
+        yield return new WaitForSeconds(1f);
+        
+        // 重置到第一帧
+        framePlayer.Reset();
+        Debug.Log("重置到第一帧");
+    }
+    
+    /// <summary>
+    /// 示例2:多渲染器使用 - 不同动画渲染到不同组件
+    /// </summary>
+    void MultiRendererExample()
+    {
+        Debug.Log("=== 多渲染器示例 ===");
+        
+        // 为不同动画设置不同的渲染器
+        framePlayer.SetAnimationRenderer("background", backgroundRenderer);
+        framePlayer.SetAnimationRenderer("character", characterRenderer);
+        framePlayer.SetAnimationRenderer("ui", null, uiImage); // UI动画渲染到Image
+        
+        // 播放背景动画
+        framePlayer.PlayAnimation("background");
+        
+        StartCoroutine(SwitchRenderers());
+    }
+    
+    IEnumerator SwitchRenderers()
+    {
+        yield return new WaitForSeconds(3f);
+        
+        // 切换到角色动画(会自动切换到characterRenderer)
+        framePlayer.PlayAnimation("character");
+        Debug.Log("切换到角色动画");
+        
+        yield return new WaitForSeconds(3f);
+        
+        // 切换到UI动画(会自动切换到uiImage)
+        framePlayer.PlayAnimation("ui");
+        Debug.Log("切换到UI动画");
+    }
+    
+    /// <summary>
+    /// 示例3:运行时动态管理 - 动态添加、修改动画
+    /// </summary>
+    void RuntimeManagementExample()
+    {
+        Debug.Log("=== 运行时管理示例 ===");
+        
+        // 运行时添加新动画
+        if (idleFrames != null && idleFrames.Length > 0)
+        {
+            framePlayer.AddAnimation("runtime_idle", idleFrames, 24f, true);
+            Debug.Log("添加了运行时空闲动画");
+        }
+        
+        if (walkFrames != null && walkFrames.Length > 0)
+        {
+            framePlayer.AddAnimation("runtime_walk", walkFrames, 30f, true);
+            Debug.Log("添加了运行时行走动画");
+        }
+        
+        // 获取所有动画名称
+        string[] animNames = framePlayer.GetAnimationNames();
+        Debug.Log($"当前动画数量: {animNames.Length}");
+        foreach (string name in animNames)
+        {
+            Debug.Log($"动画: {name}");
+        }
+        
+        StartCoroutine(RuntimeDynamicControl());
+    }
+    
+    IEnumerator RuntimeDynamicControl()
+    {
+        yield return new WaitForSeconds(1f);
+        
+        // 播放运行时添加的动画
+        framePlayer.PlayAnimation("runtime_idle");
+        Debug.Log("播放运行时空闲动画");
+        
+        yield return new WaitForSeconds(2f);
+        
+        // 动态修改当前动画的颜色
+        framePlayer.SetCurrentAnimationColor(Color.red);
+        Debug.Log("设置动画颜色为红色");
+        
+        yield return new WaitForSeconds(1f);
+        
+        // 动态修改当前动画的缩放
+        framePlayer.SetCurrentAnimationScale(new Vector3(1.5f, 1.5f, 1f));
+        Debug.Log("设置动画缩放为1.5倍");
+        
+        yield return new WaitForSeconds(1f);
+        
+        // 动态修改排序层级
+        framePlayer.SetCurrentAnimationSortingOrder(10);
+        Debug.Log("设置排序层级为10");
+        
+        yield return new WaitForSeconds(2f);
+        
+        // 切换到其他动画(会自动恢复原始设置)
+        framePlayer.PlayAnimation("runtime_walk");
+        Debug.Log("切换到行走动画(自动恢复设置)");
+    }
+    
+    /// <summary>
+    /// 示例4:事件监听 - 监听动画事件
+    /// </summary>
+    void EventListenerExample()
+    {
+        Debug.Log("=== 事件监听示例 ===");
+        
+        // 监听动画开始事件
+        framePlayer.OnAnimationStart += () => {
+            Debug.Log($"动画开始播放: {framePlayer.CurrentAnimationName}");
+        };
+        
+        // 监听动画完成事件
+        framePlayer.OnAnimationComplete += () => {
+            Debug.Log($"动画播放完成: {framePlayer.CurrentAnimationName}");
+            OnAnimationCompleted();
+        };
+        
+        // 监听帧变化事件
+        framePlayer.OnFrameChanged += (frameIndex) => {
+            Debug.Log($"当前帧: {frameIndex}/{framePlayer.TotalFrames},进度: {framePlayer.Progress:P1}");
+        };
+        
+        // 监听动画切换事件
+        framePlayer.OnAnimationChanged += (index, name) => {
+            Debug.Log($"动画切换: 索引 {index},名称 '{name}'");
+            OnAnimationSwitched(index, name);
+        };
+    }
+    
+    void OnAnimationCompleted()
+    {
+        // 动画完成后的逻辑
+        Debug.Log("执行动画完成后的逻辑");
+    }
+    
+    void OnAnimationSwitched(int index, string name)
+    {
+        // 动画切换后的逻辑
+        Debug.Log($"动画已切换到: {name}");
+        
+        // 获取当前使用的渲染组件
+        SpriteRenderer currentSpriteRenderer = framePlayer.GetCurrentSpriteRenderer();
+        Image currentImage = framePlayer.GetCurrentImage();
+        
+        if (currentSpriteRenderer != null)
+            Debug.Log($"当前使用SpriteRenderer: {currentSpriteRenderer.name}");
+        if (currentImage != null)
+            Debug.Log($"当前使用Image: {currentImage.name}");
+    }
+    
+    /// <summary>
+    /// 键盘控制示例
+    /// </summary>
+    void Update()
+    {
+        // 数字键1-4控制动画播放
+        if (Input.GetKeyDown(KeyCode.Alpha1))
+        {
+            framePlayer.PlayAnimation(0);
+            Debug.Log("播放动画 0");
+        }
+        if (Input.GetKeyDown(KeyCode.Alpha2))
+        {
+            framePlayer.PlayAnimation(1);
+            Debug.Log("播放动画 1");
+        }
+        if (Input.GetKeyDown(KeyCode.Alpha3))
+        {
+            framePlayer.PlayAnimation(2);
+            Debug.Log("播放动画 2");
+        }
+        if (Input.GetKeyDown(KeyCode.Alpha4))
+        {
+            framePlayer.PlayAnimation(3);
+            Debug.Log("播放动画 3");
+        }
+        
+        // 空格键暂停/播放
+        if (Input.GetKeyDown(KeyCode.Space))
+        {
+            if (framePlayer.IsPlaying)
+            {
+                framePlayer.Pause();
+                Debug.Log("暂停动画");
+            }
+            else
+            {
+                framePlayer.Play();
+                Debug.Log("继续动画");
+            }
+        }
+        
+        // R键重置
+        if (Input.GetKeyDown(KeyCode.R))
+        {
+            framePlayer.Reset();
+            Debug.Log("重置动画");
+        }
+        
+        // C键改变颜色
+        if (Input.GetKeyDown(KeyCode.C))
+        {
+            Color randomColor = new Color(Random.value, Random.value, Random.value, 1f);
+            framePlayer.SetCurrentAnimationColor(randomColor);
+            Debug.Log($"设置随机颜色: {randomColor}");
+        }
+        
+        // S键改变缩放
+        if (Input.GetKeyDown(KeyCode.S))
+        {
+            float scale = Random.Range(0.5f, 2f);
+            framePlayer.SetCurrentAnimationScale(Vector3.one * scale);
+            Debug.Log($"设置缩放: {scale}");
+        }
+    }
+    
+    /// <summary>
+    /// 高级用法示例 - 创建复杂的动画序列
+    /// </summary>
+    public void AdvancedUsageExample()
+    {
+        Debug.Log("=== 高级用法示例 ===");
+        
+        // 创建一个复杂的动画序列
+        var complexAnim = new AnimationSequence();
+        complexAnim.name = "complex_animation";
+        complexAnim.frames = attackFrames;
+        complexAnim.framerate = 60f;
+        complexAnim.loop = false;
+        
+        // 设置渲染属性
+        complexAnim.useGlobalRenderer = false;
+        complexAnim.targetSpriteRenderer = characterRenderer;
+        complexAnim.tintColor = Color.yellow;
+        complexAnim.useCustomScale = true;
+        complexAnim.customScale = new Vector3(2f, 2f, 1f);
+        complexAnim.overrideSortingOrder = true;
+        complexAnim.sortingOrder = 100;
+        
+        // 设置曲线控制
+        complexAnim.useCurve = true;
+        complexAnim.curve = AnimationCurve.EaseInOut(0f, 0.5f, 1f, 2f); // 先慢后快
+        
+        // 添加到播放器
+        framePlayer.AddAnimation(complexAnim);
+        
+        // 播放这个复杂动画
+        framePlayer.PlayAnimation("complex_animation");
+    }
+} 
+```

+ 692 - 0
笔记文件/2.笔记/unity动画序列帧代码实现 代码缓存_第二章.md

@@ -0,0 +1,692 @@
+组合多序列帧动画播放
+
+## MultiSequenceFramePlayer
+
+``` cs
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
+using UnityEngine;
+using UnityEngine.UI;
+
+/// <summary>
+/// 播放状态数据
+/// </summary>
+[System.Serializable]
+public class AnimationPlayState
+{
+    public int animationIndex;
+    public int currentFrameIndex;
+    public float timer;
+    public bool isPlaying;
+    public bool isPaused;
+    
+    public AnimationPlayState(int animIndex)
+    {
+        animationIndex = animIndex;
+        currentFrameIndex = 0;
+        timer = 0f;
+        isPlaying = false;
+        isPaused = false;
+    }
+}
+
+/// <summary>
+/// 多序列帧动画播放器 - 支持同时播放多个动画
+/// </summary>
+public class MultiSequenceFramePlayer : MonoBehaviour
+{
+    [Header("动画序列")]
+    [SerializeField] private List<AnimationSequence> animations = new List<AnimationSequence>();
+    [SerializeField] private bool playAllOnStart = false; // 启动时播放所有动画
+    [SerializeField] private bool ignoreTimeScale = false;
+    
+    [Header("全局渲染组件")]
+    [SerializeField] private SpriteRenderer globalSpriteRenderer;
+    [SerializeField] private Image globalImage;
+    
+    // 播放状态管理
+    private Dictionary<int, AnimationPlayState> playStates = new Dictionary<int, AnimationPlayState>();
+    private Dictionary<int, AnimationSequence> storedOriginalStates = new Dictionary<int, AnimationSequence>();
+    
+    // 属性
+    public int AnimationCount => animations?.Count ?? 0;
+    public bool HasAnyPlaying => playStates.Values.Any(state => state.isPlaying);
+    
+    // 事件
+    public System.Action<int, string> OnAnimationStarted; // 动画开始 (索引, 名称)
+    public System.Action<int, string> OnAnimationCompleted; // 动画完成 (索引, 名称)
+    public System.Action<int, int> OnFrameChanged; // 帧变化 (动画索引, 帧索引)
+    
+    private void Awake()
+    {
+        // 自动获取组件
+        if (globalSpriteRenderer == null)
+            globalSpriteRenderer = GetComponent<SpriteRenderer>();
+        if (globalImage == null)
+            globalImage = GetComponent<Image>();
+    }
+    
+    private void Start()
+    {
+        if (playAllOnStart)
+        {
+            PlayAllAnimations();
+        }
+    }
+    
+    private void Update()
+    {
+        if (playStates.Count == 0) return;
+        
+        float currentTime = ignoreTimeScale ? Time.unscaledTime : Time.time;
+        
+        // 更新所有正在播放的动画
+        var playingStates = playStates.Values.Where(state => state.isPlaying && !state.isPaused).ToList();
+        
+        foreach (var playState in playingStates)
+        {
+            UpdateAnimationState(playState, currentTime);
+        }
+    }
+    
+    /// <summary>
+    /// 更新单个动画状态
+    /// </summary>
+    private void UpdateAnimationState(AnimationPlayState playState, float currentTime)
+    {
+        if (playState.animationIndex >= animations.Count) return;
+        
+        var animation = animations[playState.animationIndex];
+        if (animation?.frames == null || animation.frames.Length == 0) return;
+        
+        // 计算帧率
+        float finalFramerate = animation.framerate;
+        if (animation.useCurve && animation.curve != null)
+        {
+            float progress = (float)playState.currentFrameIndex / animation.frames.Length;
+            float curveValue = animation.curve.Evaluate(progress);
+            finalFramerate = curveValue * animation.framerate;
+        }
+        
+        // 检查是否需要更新帧
+        if (finalFramerate > 0.01f)
+        {
+            float interval = 1.0f / finalFramerate;
+            
+            if (currentTime - playState.timer > interval)
+            {
+                playState.timer = currentTime;
+                UpdateFrame(playState, animation);
+            }
+        }
+    }
+    
+    /// <summary>
+    /// 更新帧
+    /// </summary>
+    private void UpdateFrame(AnimationPlayState playState, AnimationSequence animation)
+    {
+        // 更新帧索引
+        playState.currentFrameIndex++;
+        
+        // 检查是否播放完成
+        if (playState.currentFrameIndex >= animation.frames.Length)
+        {
+            if (animation.loop)
+            {
+                playState.currentFrameIndex = 0; // 循环播放
+            }
+            else
+            {
+                playState.currentFrameIndex = animation.frames.Length - 1;
+                playState.isPlaying = false;
+                OnAnimationCompleted?.Invoke(playState.animationIndex, animation.name);
+                return;
+            }
+        }
+        
+        // 更新显示的帧
+        UpdateAnimationFrame(playState.animationIndex, animation, playState.currentFrameIndex);
+        
+        // 触发帧变化事件
+        OnFrameChanged?.Invoke(playState.animationIndex, playState.currentFrameIndex);
+    }
+    
+    /// <summary>
+    /// 更新动画帧显示
+    /// </summary>
+    private void UpdateAnimationFrame(int animationIndex, AnimationSequence animation, int frameIndex)
+    {
+        if (frameIndex < 0 || frameIndex >= animation.frames.Length) return;
+        
+        Sprite currentSprite = animation.frames[frameIndex];
+        
+        // 获取目标渲染组件
+        SpriteRenderer targetSpriteRenderer = animation.GetSpriteRenderer(globalSpriteRenderer);
+        Image targetImage = animation.GetImage(globalImage);
+        
+        // 更新渲染组件
+        if (targetSpriteRenderer != null)
+        {
+            targetSpriteRenderer.sprite = currentSprite;
+        }
+        
+        if (targetImage != null)
+        {
+            targetImage.sprite = currentSprite;
+        }
+    }
+    
+    /// <summary>
+    /// 播放指定动画
+    /// </summary>
+    public bool PlayAnimation(int animationIndex)
+    {
+        if (animationIndex < 0 || animationIndex >= animations.Count) return false;
+        
+        var animation = animations[animationIndex];
+        if (animation?.frames == null || animation.frames.Length == 0) return false;
+        
+        // 创建或更新播放状态
+        if (!playStates.ContainsKey(animationIndex))
+        {
+            playStates[animationIndex] = new AnimationPlayState(animationIndex);
+        }
+        
+        var playState = playStates[animationIndex];
+        playState.isPlaying = true;
+        playState.isPaused = false;
+        playState.currentFrameIndex = 0;
+        playState.timer = ignoreTimeScale ? Time.unscaledTime : Time.time;
+        
+        // 获取实际的目标渲染器
+        SpriteRenderer targetSpriteRenderer = animation.GetSpriteRenderer(globalSpriteRenderer);
+        Image targetImage = animation.GetImage(globalImage);
+        
+        // 存储并应用渲染设置
+        animation.StoreOriginalValues(targetSpriteRenderer, targetImage);
+        animation.ApplyRenderSettings(targetSpriteRenderer, targetImage);
+        
+        // 立即更新第一帧
+        UpdateAnimationFrame(animationIndex, animation, 0);
+        
+        OnAnimationStarted?.Invoke(animationIndex, animation.name);
+        return true;
+    }
+    
+    /// <summary>
+    /// 播放指定名称的动画
+    /// </summary>
+    public bool PlayAnimation(string animationName)
+    {
+        int index = GetAnimationIndex(animationName);
+        return index >= 0 && PlayAnimation(index);
+    }
+    
+    /// <summary>
+    /// 播放所有动画
+    /// </summary>
+    public void PlayAllAnimations()
+    {
+        for (int i = 0; i < animations.Count; i++)
+        {
+            PlayAnimation(i);
+        }
+    }
+    
+    /// <summary>
+    /// 暂停指定动画
+    /// </summary>
+    public void PauseAnimation(int animationIndex)
+    {
+        if (playStates.ContainsKey(animationIndex))
+        {
+            playStates[animationIndex].isPaused = true;
+        }
+    }
+    
+    /// <summary>
+    /// 暂停指定名称的动画
+    /// </summary>
+    public void PauseAnimation(string animationName)
+    {
+        int index = GetAnimationIndex(animationName);
+        if (index >= 0) PauseAnimation(index);
+    }
+    
+    /// <summary>
+    /// 暂停所有动画
+    /// </summary>
+    public void PauseAllAnimations()
+    {
+        foreach (var state in playStates.Values)
+        {
+            state.isPaused = true;
+        }
+    }
+    
+    /// <summary>
+    /// 恢复指定动画
+    /// </summary>
+    public void ResumeAnimation(int animationIndex)
+    {
+        if (playStates.ContainsKey(animationIndex))
+        {
+            playStates[animationIndex].isPaused = false;
+        }
+    }
+    
+    /// <summary>
+    /// 恢复指定名称的动画
+    /// </summary>
+    public void ResumeAnimation(string animationName)
+    {
+        int index = GetAnimationIndex(animationName);
+        if (index >= 0) ResumeAnimation(index);
+    }
+    
+    /// <summary>
+    /// 恢复所有动画
+    /// </summary>
+    public void ResumeAllAnimations()
+    {
+        foreach (var state in playStates.Values)
+        {
+            state.isPaused = false;
+        }
+    }
+    
+    /// <summary>
+    /// 停止指定动画
+    /// </summary>
+    public void StopAnimation(int animationIndex)
+    {
+        if (playStates.ContainsKey(animationIndex))
+        {
+            playStates[animationIndex].isPlaying = false;
+            playStates[animationIndex].isPaused = false;
+            
+            // 恢复原始设置
+            if (animationIndex < animations.Count)
+            {
+                var animation = animations[animationIndex];
+                SpriteRenderer targetSpriteRenderer = animation.GetSpriteRenderer(globalSpriteRenderer);
+                Image targetImage = animation.GetImage(globalImage);
+                animation.RestoreOriginalValues(targetSpriteRenderer, targetImage);
+            }
+        }
+    }
+    
+    /// <summary>
+    /// 停止指定名称的动画
+    /// </summary>
+    public void StopAnimation(string animationName)
+    {
+        int index = GetAnimationIndex(animationName);
+        if (index >= 0) StopAnimation(index);
+    }
+    
+    /// <summary>
+    /// 停止所有动画
+    /// </summary>
+    public void StopAllAnimations()
+    {
+        foreach (var kvp in playStates)
+        {
+            StopAnimation(kvp.Key);
+        }
+        playStates.Clear();
+    }
+    
+    /// <summary>
+    /// 检查动画是否正在播放
+    /// </summary>
+    public bool IsAnimationPlaying(int animationIndex)
+    {
+        return playStates.ContainsKey(animationIndex) && 
+               playStates[animationIndex].isPlaying && 
+               !playStates[animationIndex].isPaused;
+    }
+    
+    /// <summary>
+    /// 检查动画是否正在播放
+    /// </summary>
+    public bool IsAnimationPlaying(string animationName)
+    {
+        int index = GetAnimationIndex(animationName);
+        return index >= 0 && IsAnimationPlaying(index);
+    }
+    
+    /// <summary>
+    /// 获取动画当前帧
+    /// </summary>
+    public int GetCurrentFrame(int animationIndex)
+    {
+        return playStates.ContainsKey(animationIndex) ? playStates[animationIndex].currentFrameIndex : -1;
+    }
+    
+    /// <summary>
+    /// 获取动画当前帧
+    /// </summary>
+    public int GetCurrentFrame(string animationName)
+    {
+        int index = GetAnimationIndex(animationName);
+        return index >= 0 ? GetCurrentFrame(index) : -1;
+    }
+    
+    /// <summary>
+    /// 设置动画帧
+    /// </summary>
+    public void SetAnimationFrame(int animationIndex, int frameIndex)
+    {
+        if (playStates.ContainsKey(animationIndex) && animationIndex < animations.Count)
+        {
+            var animation = animations[animationIndex];
+            if (animation?.frames != null && frameIndex >= 0 && frameIndex < animation.frames.Length)
+            {
+                playStates[animationIndex].currentFrameIndex = frameIndex;
+                UpdateAnimationFrame(animationIndex, animation, frameIndex);
+            }
+        }
+    }
+    
+    /// <summary>
+    /// 添加动画序列
+    /// </summary>
+    public void AddAnimation(AnimationSequence newAnimation)
+    {
+        if (animations == null)
+            animations = new List<AnimationSequence>();
+        animations.Add(newAnimation);
+    }
+    
+    /// <summary>
+    /// 添加动画序列
+    /// </summary>
+    public void AddAnimation(string name, Sprite[] frames, float framerate = 30f, bool loop = true)
+    {
+        AddAnimation(new AnimationSequence(name, frames, framerate, loop));
+    }
+    
+    /// <summary>
+    /// 获取动画索引
+    /// </summary>
+    public int GetAnimationIndex(string animationName)
+    {
+        if (animations == null) return -1;
+        
+        for (int i = 0; i < animations.Count; i++)
+        {
+            if (animations[i].name == animationName)
+                return i;
+        }
+        return -1;
+    }
+    
+    /// <summary>
+    /// 获取所有动画名称
+    /// </summary>
+    public string[] GetAnimationNames()
+    {
+        if (animations == null) return new string[0];
+        
+        string[] names = new string[animations.Count];
+        for (int i = 0; i < animations.Count; i++)
+        {
+            names[i] = animations[i].name;
+        }
+        return names;
+    }
+    
+    /// <summary>
+    /// 获取正在播放的动画列表
+    /// </summary>
+    public List<string> GetPlayingAnimations()
+    {
+        var playingNames = new List<string>();
+        foreach (var kvp in playStates)
+        {
+            if (kvp.Value.isPlaying && !kvp.Value.isPaused)
+            {
+                if (kvp.Key < animations.Count)
+                    playingNames.Add(animations[kvp.Key].name);
+            }
+        }
+        return playingNames;
+    }
+    
+    /// <summary>
+    /// 调试方法:检查动画的目标渲染器设置
+    /// </summary>
+    public void DebugAnimationRenderers()
+    {
+        Debug.Log("=== 动画渲染器设置调试 ===");
+        for (int i = 0; i < animations.Count; i++)
+        {
+            var anim = animations[i];
+            var targetSpriteRenderer = anim.GetSpriteRenderer(globalSpriteRenderer);
+            var targetImage = anim.GetImage(globalImage);
+            
+            Debug.Log($"动画 '{anim.name}' (索引 {i}):");
+            Debug.Log($"  - useGlobalRenderer: {anim.useGlobalRenderer}");
+            Debug.Log($"  - targetSpriteRenderer: {(anim.targetSpriteRenderer != null ? anim.targetSpriteRenderer.name : "null")}");
+            Debug.Log($"  - targetImage: {(anim.targetImage != null ? anim.targetImage.name : "null")}");
+            Debug.Log($"  - 实际使用的SpriteRenderer: {(targetSpriteRenderer != null ? targetSpriteRenderer.name : "null")}");
+            Debug.Log($"  - 实际使用的Image: {(targetImage != null ? targetImage.name : "null")}");
+        }
+    }
+    
+    /// <summary>
+    /// 为指定动画设置目标渲染器
+    /// </summary>
+    public void SetAnimationRenderer(int animationIndex, SpriteRenderer targetRenderer, Image targetImg = null)
+    {
+        if (animationIndex >= 0 && animationIndex < animations.Count)
+        {
+            animations[animationIndex].targetSpriteRenderer = targetRenderer;
+            animations[animationIndex].targetImage = targetImg;
+            animations[animationIndex].useGlobalRenderer = (targetRenderer == null && targetImg == null);
+            
+            Debug.Log($"设置动画 '{animations[animationIndex].name}' 的渲染器: " +
+                     $"SpriteRenderer={targetRenderer?.name}, Image={targetImg?.name}, useGlobal={animations[animationIndex].useGlobalRenderer}");
+        }
+    }
+    
+    /// <summary>
+    /// 为指定动画设置目标渲染器(通过名称)
+    /// </summary>
+    public void SetAnimationRenderer(string animationName, SpriteRenderer targetRenderer, Image targetImg = null)
+    {
+        int index = GetAnimationIndex(animationName);
+        if (index >= 0)
+        {
+            SetAnimationRenderer(index, targetRenderer, targetImg);
+        }
+        else
+        {
+            Debug.LogWarning($"动画 '{animationName}' 不存在!");
+        }
+    }
+} 
+```
+
+## MultiSequenceExample
+
+``` cs
+using System.Collections;
+using UnityEngine;
+using UnityEngine.UI;
+
+/// <summary>
+/// 多序列帧播放器使用示例
+/// </summary>
+public class MultiSequenceExample : MonoBehaviour
+{
+    [Header("多序列播放器")]
+    public MultiSequenceFramePlayer multiPlayer;
+    
+    [Header("序列帧资源")]
+    public Sprite[] backgroundFrames;
+    public Sprite[] symbol1Frames;
+    public Sprite[] symbol2Frames;
+    public Sprite[] symbol3Frames;
+    public Sprite[] effectFrames;
+    
+    [Header("渲染器")]
+    public SpriteRenderer backgroundRenderer;
+    public SpriteRenderer[] symbolRenderers = new SpriteRenderer[3];
+    public SpriteRenderer effectRenderer;
+    
+    private void Start()
+    {
+        SetupAnimations();
+        StartCoroutine(DemoSequence());
+    }
+    
+    void SetupAnimations()
+    {
+        // 方法1:直接创建AnimationSequence时设置
+        var bgAnim = new AnimationSequence("background", backgroundFrames, 15f, true);
+        bgAnim.useGlobalRenderer = false;
+        bgAnim.targetSpriteRenderer = backgroundRenderer;
+        bgAnim.overrideSortingOrder = true;
+        bgAnim.sortingOrder = -10;
+        multiPlayer.AddAnimation(bgAnim);
+        
+        // 方法2:先添加动画,再设置渲染器(推荐)
+        multiPlayer.AddAnimation("symbol1", symbol1Frames, 30f, true);
+        multiPlayer.AddAnimation("symbol2", symbol2Frames, 25f, true);
+        multiPlayer.AddAnimation("symbol3", symbol3Frames, 35f, true);
+        multiPlayer.AddAnimation("effect", effectFrames, 60f, false);
+        
+        // 为每个动画设置独立的渲染器
+        multiPlayer.SetAnimationRenderer("symbol1", symbolRenderers[0]);
+        multiPlayer.SetAnimationRenderer("symbol2", symbolRenderers[1]);
+        multiPlayer.SetAnimationRenderer("symbol3", symbolRenderers[2]);
+        multiPlayer.SetAnimationRenderer("effect", effectRenderer);
+        
+        // 调试:打印所有渲染器设置
+        multiPlayer.DebugAnimationRenderers();
+        
+        // 监听事件
+        multiPlayer.OnAnimationStarted += (index, name) => {
+            Debug.Log($"动画开始: {name}");
+        };
+        
+        multiPlayer.OnAnimationCompleted += (index, name) => {
+            Debug.Log($"动画完成: {name}");
+        };
+    }
+    
+    IEnumerator DemoSequence()
+    {
+        // 1. 先播放背景动画
+        Debug.Log("=== 播放背景动画 ===");
+        multiPlayer.PlayAnimation("background");
+        yield return new WaitForSeconds(2f);
+        
+        // 2. 同时播放所有符号动画
+        Debug.Log("=== 同时播放所有符号动画 ===");
+        multiPlayer.PlayAnimation("symbol1");
+        multiPlayer.PlayAnimation("symbol2");
+        multiPlayer.PlayAnimation("symbol3");
+        
+        yield return new WaitForSeconds(3f);
+        
+        // 3. 暂停第二个符号
+        Debug.Log("=== 暂停 symbol2 ===");
+        multiPlayer.PauseAnimation("symbol2");
+        
+        yield return new WaitForSeconds(2f);
+        
+        // 4. 恢复第二个符号
+        Debug.Log("=== 恢复 symbol2 ===");
+        multiPlayer.ResumeAnimation("symbol2");
+        
+        yield return new WaitForSeconds(2f);
+        
+        // 5. 播放特效(不循环)
+        Debug.Log("=== 播放特效动画 ===");
+        multiPlayer.PlayAnimation("effect");
+        
+        yield return new WaitForSeconds(3f);
+        
+        // 6. 停止所有符号,只保留背景
+        Debug.Log("=== 停止所有符号 ===");
+        multiPlayer.StopAnimation("symbol1");
+        multiPlayer.StopAnimation("symbol2");
+        multiPlayer.StopAnimation("symbol3");
+        
+        yield return new WaitForSeconds(2f);
+        
+        // 7. 显示当前播放状态
+        var playingAnims = multiPlayer.GetPlayingAnimations();
+        Debug.Log($"当前播放的动画: {string.Join(", ", playingAnims)}");
+    }
+    
+    void Update()
+    {
+        // 键盘控制
+        if (Input.GetKeyDown(KeyCode.Alpha1))
+        {
+            multiPlayer.PlayAnimation("symbol1");
+            Debug.Log("播放 symbol1");
+        }
+        
+        if (Input.GetKeyDown(KeyCode.Alpha2))
+        {
+            multiPlayer.PlayAnimation("symbol2");
+            Debug.Log("播放 symbol2");
+        }
+        
+        if (Input.GetKeyDown(KeyCode.Alpha3))
+        {
+            multiPlayer.PlayAnimation("symbol3");
+            Debug.Log("播放 symbol3");
+        }
+        
+        if (Input.GetKeyDown(KeyCode.B))
+        {
+            multiPlayer.PlayAnimation("background");
+            Debug.Log("播放背景");
+        }
+        
+        if (Input.GetKeyDown(KeyCode.E))
+        {
+            multiPlayer.PlayAnimation("effect");
+            Debug.Log("播放特效");
+        }
+        
+        if (Input.GetKeyDown(KeyCode.P))
+        {
+            multiPlayer.PauseAllAnimations();
+            Debug.Log("暂停所有动画");
+        }
+        
+        if (Input.GetKeyDown(KeyCode.R))
+        {
+            multiPlayer.ResumeAllAnimations();
+            Debug.Log("恢复所有动画");
+        }
+        
+        if (Input.GetKeyDown(KeyCode.S))
+        {
+            multiPlayer.StopAllAnimations();
+            Debug.Log("停止所有动画");
+        }
+        
+        if (Input.GetKeyDown(KeyCode.A))
+        {
+            multiPlayer.PlayAllAnimations();
+            Debug.Log("播放所有动画");
+        }
+        
+        if (Input.GetKeyDown(KeyCode.D))
+        {
+            multiPlayer.DebugAnimationRenderers();
+            Debug.Log("调试渲染器设置");
+        }
+    }
+} 
+```

+ 34 - 0
笔记文件/日记/2025_07_10_星期四.md

@@ -0,0 +1,34 @@
+
+11:11
+
+###### [[unity日常积累]]
+
+###### [[节奏天国]]
+
+###### [[帧同步王者荣耀]]
+
+###### [[从零开发跨平台通用日志插件]]
+
+###### [[通用异步网络通信库]]
+
+###### [[高性能定时系统实现]]
+
+###### [[学习资料]]
+
+###### [[其他]]
+
+#### [[看板]]
+
+# 今日任务
+
+- [ ] 把所有序列帧动效都加上
+- [ ] 艺术数字滚动增加
+- [ ] 把音效也沟通加上
+- [ ] 完善剩余的实现逻辑
+- [ ] 调平滑的动画效果
+- [ ] 压缩图片 & 优化性能 & 缩小包体
+- [ ] 把灵动平台提交到Plink中台库
+- [ ] 灵动平台 在Plink中台库,和AMP埋点整合一下
+---
+[[unity动画序列帧代码实现 代码缓存]]
+# Journal