Unity编辑器脚本。这个脚本会在两个动画轨道上交替添加来自两个路径的序列帧,每个关键帧间隔180帧(3秒,假设帧率60帧/秒):
#if UNITY_EDITOR
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEditor;
using System.Text.RegularExpressions;
using UnityEngine.Animations;
public class SequenceAnimator : EditorWindow
{
private string pathA = "Assets/Sprites/PathA";
private string pathB = "Assets/Sprites/PathB";
private AnimationClip animationClip;
private GameObject targetObject;
private int frameInterval = 180;
[MenuItem("Window/Animation/Sequence Frame Creator")]
public static void ShowWindow()
{
GetWindow<SequenceAnimator>("Frame Sequencer");
}
void OnGUI()
{
GUILayout.Label("Sequence Frame Settings", EditorStyles.boldLabel);
targetObject = (GameObject)EditorGUILayout.ObjectField("Target Object", targetObject, typeof(GameObject), true);
animationClip = (AnimationClip)EditorGUILayout.ObjectField("Animation Clip", animationClip, typeof(AnimationClip), false);
pathA = EditorGUILayout.TextField("Path A:", pathA);
pathB = EditorGUILayout.TextField("Path B:", pathB);
frameInterval = EditorGUILayout.IntField("Frame Interval:", frameInterval);
if (GUILayout.Button("Generate Animation"))
{
if (targetObject == null || animationClip == null)
{
Debug.LogError("Please assign Target Object and Animation Clip!");
return;
}
GenerateAnimationSequence();
}
}
void GenerateAnimationSequence()
{
var spritesA = GetSortedSprites(pathA);
var spritesB = GetSortedSprites(pathB);
if (spritesA.Count == 0 || spritesB.Count == 0)
{
Debug.LogError("No sprites found in one of the paths!");
return;
}
if (spritesA.Count != spritesB.Count)
{
Debug.LogError("Sprite counts in both paths don't match!");
return;
}
// 创建两个动画轨道
CreateTrack("TrackA", spritesA, 0);
CreateTrack("TrackB", spritesB, frameInterval/2);
}
void CreateTrack(string trackName, List<Sprite> sprites, int offset)
{
AnimationClipSettings settings = AnimationUtility.GetAnimationClipSettings(animationClip);
float frameRate = settings.frameRate == 0 ? 60 : settings.frameRate;
var binding = new EditorCurveBinding
{
path = "", // 根路径
type = typeof(SpriteRenderer),
propertyName = "m_Sprite"
};
List<ObjectReferenceKeyframe> keyFrames = new List<ObjectReferenceKeyframe>();
for (int i = 0; i < sprites.Count; i++)
{
float time = (i * frameInterval + offset) / frameRate;
keyFrames.Add(new ObjectReferenceKeyframe
{
time = time,
value = sprites[i]
});
}
AnimationUtility.SetObjectReferenceCurve(animationClip, binding, keyFrames.ToArray());
}
List<Sprite> GetSortedSprites(string path)
{
List<Sprite> sprites = new List<Sprite>();
string[] guids = AssetDatabase.FindAssets("t:Sprite", new[] { path });
foreach (string guid in guids)
{
string assetPath = AssetDatabase.GUIDToAssetPath(guid);
Sprite sprite = AssetDatabase.LoadAssetAtPath<Sprite>(assetPath);
if (sprite != null)
{
sprites.Add(sprite);
}
}
// 自然排序
return sprites.OrderBy(s =>
Regex.Replace(s.name, @"\d+", m => m.Value.PadLeft(10, '0')))
.ToList();
}
}
#endif
使用方法:
- 通过菜单 Window > Animation > Sequence Frame Creator 打开窗口
- 指定目标物体(需要有SpriteRenderer组件)
- 创建或指定一个Animation Clip
- 输入两个图片路径(相对于Assets的路径)
- 点击Generate Animation按钮
特性说明:
- 自动检测两个路径下的所有Sprite资源
- 支持自然排序(能正确处理类似frame1, frame2,…, frame10的序列)
- 自动创建两个动画轨道,每个轨道间隔90帧(总间隔180帧)
- 自动处理时间计算(基于动画剪辑的帧率)
注意:
- 确保目标GameObject上有SpriteRenderer组件
- 图片需要设置为Sprite类型(在Import Settings里设置)
- 路径需要是Assets下的相对路径
- 动画剪辑建议设置为可循环播放模式
- 可以通过调整frameInterval参数修改关键帧间隔
这个脚本会在同一个SpriteRenderer组件上创建两个动画曲线轨道,通过交替显示两个路径的Sprite来达到组合动画的效果。第一个路径的帧会在0帧、180帧、360帧等位置,第二个路径的帧会在90帧、270帧、450帧等位置显示。