以下是实现上述功能的Unity编辑器脚本:
#if UNITY_EDITOR
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEditor;
using System.Text.RegularExpressions;
public class AlternateFrameAnimator : EditorWindow
{
private string pathA = "Assets/Sprites/PathA";
private string pathB = "Assets/Sprites/PathB";
private GameObject targetObject;
private AnimationClip animationClip;
private int frameInterval = 30;
[MenuItem("Window/Animation/Alternate Frame Creator")]
public static void ShowWindow()
{
GetWindow<AlternateFrameAnimator>("Alternate Frame Sequencer");
}
void OnGUI()
{
GUILayout.Label("Alternate 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;
}
// 获取目标物体的SpriteRenderer
var spriteRenderer = targetObject.GetComponent<SpriteRenderer>();
if (spriteRenderer == null)
{
Debug.LogError("Target object must have a SpriteRenderer component!");
return;
}
// 计算帧率
float frameRate = animationClip.frameRate > 0 ? animationClip.frameRate : 60f;
// 获取物体在层级中的路径
string relativePath = AnimationUtility.CalculateTransformPath(
targetObject.transform,
FindRootTransform(targetObject.transform)
);
var binding = new EditorCurveBinding
{
path = relativePath,
type = typeof(SpriteRenderer),
propertyName = "m_Sprite"
};
List<ObjectReferenceKeyframe> keyFrames = new List<ObjectReferenceKeyframe>();
// 交替添加A和B的图片
for (int i = 0; i < spritesA.Count; i++)
{
// A的图片在偶数间隔
float timeA = (i * 2 * frameInterval) / frameRate;
keyFrames.Add(new ObjectReferenceKeyframe
{
time = timeA,
value = spritesA[i]
});
// B的图片在奇数间隔
float timeB = ((i * 2 + 1) * frameInterval) / frameRate;
keyFrames.Add(new ObjectReferenceKeyframe
{
time = timeB,
value = spritesB[i]
});
}
AnimationUtility.SetObjectReferenceCurve(animationClip, binding, keyFrames.ToArray());
EditorUtility.SetDirty(animationClip);
AssetDatabase.SaveAssets();
Debug.Log("Animation sequence generated successfully!");
}
Transform FindRootTransform(Transform t)
{
while (t.parent != null)
{
t = t.parent;
}
return t;
}
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
功能说明:
- 指定路径:可以分别指定路径A和路径B,分别获取两组图片序列。
- 帧间隔设置:可以通过
Frame Interval参数设置每两张图片之间的帧数间隔,默认为30帧。 - 交替显示:在同一个目标物体的SpriteRenderer中,交替显示路径A和路径B的图片。A的图片显示在
0, 60, 120, ...帧,B的图片显示在30, 90, 150, ...帧。 - 自然排序:自动对图片文件进行自然排序,确保按正确顺序加载序列图片。
使用方法:
- 通过菜单 Window > Animation > Alternate Frame Creator 打开窗口。
- 指定带有SpriteRenderer组件的目标物体。
- 创建或选择一个Animation Clip。
- 输入路径A和路径B(相对于Assets的路径)。
- 调整Frame Interval为所需的帧间隔(默认为30)。
- 点击Generate Animation按钮生成动画。
注意事项:
- 确保路径正确:路径A和路径B中的图片数量必须相同,且格式一致(如
img1, img2, ...)。 - 动画剪辑设置:建议在生成后检查动画剪辑的帧率和循环设置。
- Sprite设置:确保图片被正确导入为Sprite类型,且Pivot设置正确。
这个脚本将帮助你高效地为同一个目标物体创建交替显示两组序列图片的动画,节省手动设置关键帧的时间。

被折叠的 条评论
为什么被折叠?



