using System; using System.Collections.Generic; using UnityEngine; namespace UnityEditor.U2D.Aseprite { internal static class AnimationClipGeneration { const string k_RootName = "Root"; public static AnimationClip[] Generate(string assetName, Sprite[] sprites, AsepriteFile file, List layers, List frames, List tags, Dictionary layerIdToGameObject) { var noOfFrames = file.noOfFrames; if (tags.Count == 0) { var tag = new Tag(); tag.name = assetName + "_Clip"; tag.fromFrame = 0; tag.toFrame = noOfFrames; tags.Add(tag); } var clips = new List(tags.Count); var animationNames = new HashSet(tags.Count); for (var i = 0; i < tags.Count; ++i) { var clipName = tags[i].name; if (animationNames.Contains(clipName)) { var nameIndex = 0; while(animationNames.Contains(clipName)) clipName = $"{tags[i].name}_{nameIndex++}"; Debug.LogWarning($"The animation clip name {tags[i].name} is already in use. Renaming to {clipName}."); } clips.Add(CreateClip(tags[i], clipName, layers, frames, sprites, layerIdToGameObject)); animationNames.Add(clipName); } return clips.ToArray(); } static AnimationClip CreateClip(Tag tag, string clipName, List layers, IReadOnlyList frames, Sprite[] sprites, Dictionary layerIdToGameObject) { var animationClip = new AnimationClip() { name = clipName, frameRate = 100f }; var clipSettings = new AnimationClipSettings(); clipSettings.loopTime = tag.isRepeating; AnimationUtility.SetAnimationClipSettings(animationClip, clipSettings); for (var i = 0; i < layers.Count; ++i) { var layer = layers[i]; if (layer.layerType != LayerTypes.Normal) continue; var layerGo = layerIdToGameObject[layer.index]; if (layerGo.GetComponent() == null) continue; var layerTransform = layerGo.transform; var spriteKeyframes = new List(); var cells = layer.cells; var activeFrames = AddCellsToClip(cells, in tag, in sprites, frames, ref spriteKeyframes); var linkedCells = layer.linkedCells; activeFrames.UnionWith(AddLinkedCellsToClip(linkedCells, in cells, in tag, in sprites, frames, ref spriteKeyframes)); spriteKeyframes.Sort((x, y) => x.time.CompareTo(y.time)); DuplicateLastFrame(ref spriteKeyframes, frames[tag.toFrame - 1]); var path = GetTransformPath(layerTransform); var spriteBinding = EditorCurveBinding.PPtrCurve(path, typeof(SpriteRenderer), "m_Sprite"); AnimationUtility.SetObjectReferenceCurve(animationClip, spriteBinding, spriteKeyframes.ToArray()); AddEnabledKeyframes(layerTransform, tag, frames, in activeFrames, in animationClip); AddSortOrderKeyframes(layerTransform, layer, tag, frames, in cells, in animationClip); AddAnimationEvents(in tag, frames, animationClip); } return animationClip; } static HashSet AddCellsToClip(IReadOnlyList cells, in Tag tag, in Sprite[] sprites, IReadOnlyList frames, ref List keyFrames) { var activeFrames = new HashSet(); var startTime = GetTimeFromFrame(frames, tag.fromFrame); for (var i = 0; i < cells.Count; ++i) { var cell = cells[i]; if (cell.frameIndex < tag.fromFrame || cell.frameIndex >= tag.toFrame) continue; var sprite = Array.Find(sprites, x => x.GetSpriteID() == cell.spriteId); if (sprite == null) continue; var keyframe = new ObjectReferenceKeyframe(); var time = GetTimeFromFrame(frames, cell.frameIndex); keyframe.time = time - startTime; keyframe.value = sprite; keyFrames.Add(keyframe); activeFrames.Add(cell.frameIndex); } return activeFrames; } static HashSet AddLinkedCellsToClip(IReadOnlyList linkedCells, in List cells, in Tag tag, in Sprite[] sprites, IReadOnlyList frames, ref List keyFrames) { var activeFrames = new HashSet(); var startTime = GetTimeFromFrame(frames, tag.fromFrame); for (var i = 0; i < linkedCells.Count; ++i) { var linkedCell = linkedCells[i]; if (linkedCell.frameIndex < tag.fromFrame || linkedCell.frameIndex >= tag.toFrame) continue; var cellIndex = cells.FindIndex(x => x.frameIndex == linkedCell.linkedToFrame); if (cellIndex == -1) continue; var cell = cells[cellIndex]; var sprite = Array.Find(sprites, x => x.GetSpriteID() == cell.spriteId); if (sprite == null) continue; var keyframe = new ObjectReferenceKeyframe(); var time = GetTimeFromFrame(frames, linkedCell.frameIndex); keyframe.time = time - startTime; keyframe.value = sprite; keyFrames.Add(keyframe); activeFrames.Add(linkedCell.frameIndex); } return activeFrames; } static void DuplicateLastFrame(ref List keyFrames, Frame lastFrame) { if (keyFrames.Count == 0) return; var lastKeyFrame = keyFrames[^1]; var duplicatedFrame = new ObjectReferenceKeyframe(); duplicatedFrame.time = lastKeyFrame.time + MsToSeconds(lastFrame.duration); duplicatedFrame.value = lastKeyFrame.value; keyFrames.Add(duplicatedFrame); } static string GetTransformPath(Transform transform) { var path = transform.name; if (transform.name == k_RootName) return ""; if (transform.parent.name == k_RootName) return path; var parentPath = GetTransformPath(transform.parent) + "/"; path = path.Insert(0, parentPath); return path; } static void AddEnabledKeyframes(Transform layerTransform, Tag tag, IReadOnlyList frames, in HashSet activeFrames, in AnimationClip animationClip) { if (activeFrames.Count == tag.noOfFrames) return; var path = GetTransformPath(layerTransform); var enabledBinding = EditorCurveBinding.FloatCurve(path, typeof(SpriteRenderer), "m_Enabled"); var enabledKeyframes = new List(); var disabledPrevFrame = false; var startTime = GetTimeFromFrame(frames, tag.fromFrame); for (var frameIndex = tag.fromFrame; frameIndex < tag.toFrame; ++frameIndex) { var time = GetTimeFromFrame(frames, frameIndex); time -= startTime; if (!activeFrames.Contains(frameIndex) && !disabledPrevFrame) { var keyframe = GetBoolKeyFrame(false, time); enabledKeyframes.Add(keyframe); disabledPrevFrame = true; } else if (activeFrames.Contains(frameIndex) && disabledPrevFrame) { var keyframe = GetBoolKeyFrame(true, time); enabledKeyframes.Add(keyframe); disabledPrevFrame = false; } } if (enabledKeyframes.Count == 0) return; // Make sure there is an enable keyframe on the first frame, if the first frame is active. if (activeFrames.Contains(tag.fromFrame)) { var keyframe = GetBoolKeyFrame(true, 0f); enabledKeyframes.Add(keyframe); } var animCurve = new AnimationCurve(enabledKeyframes.ToArray()); AnimationUtility.SetEditorCurve(animationClip, enabledBinding, animCurve); } static void AddSortOrderKeyframes(Transform layerTransform, Layer layer, Tag tag, IReadOnlyList frames, in List cells, in AnimationClip animationClip) { var layerGo = layerTransform.gameObject; var spriteRenderer = layerGo.GetComponent(); if (spriteRenderer == null) return; var sortOrderKeyframes = new List(); var path = GetTransformPath(layerTransform); var sortOrderBinding = EditorCurveBinding.FloatCurve(path, typeof(SpriteRenderer), "m_SortingOrder"); var startTime = GetTimeFromFrame(frames, tag.fromFrame); var hasKeyOnFirstFrame = false; for (var i = 0; i < cells.Count; ++i) { var cell = cells[i]; if (cell.frameIndex < tag.fromFrame || cell.frameIndex >= tag.toFrame) continue; var additiveSortOrder = cell.additiveSortOrder; if (additiveSortOrder == 0) continue; if (cell.frameIndex == tag.fromFrame) hasKeyOnFirstFrame = true; var time = GetTimeFromFrame(frames, cell.frameIndex) - startTime; var keyframe = GetIntKeyFrame(layer.index + additiveSortOrder, time); sortOrderKeyframes.Add(keyframe); } if (sortOrderKeyframes.Count == 0) return; if (!hasKeyOnFirstFrame) { var firstFrame = GetIntKeyFrame(layer.index, 0f); sortOrderKeyframes.Add(firstFrame); } var animCurve = new AnimationCurve(sortOrderKeyframes.ToArray()); AnimationUtility.SetEditorCurve(animationClip, sortOrderBinding, animCurve); } static float GetTimeFromFrame(IReadOnlyList frames, int frameIndex) { var totalMs = 0; for (var i = 0; i < frameIndex; ++i) totalMs += frames[i].duration; return MsToSeconds(totalMs); } static float MsToSeconds(int ms) => ms / 1000f; static Keyframe GetBoolKeyFrame(bool value, float time) { var keyframe = new Keyframe(); keyframe.value = value ? 1f : 0f; keyframe.time = time; keyframe.inTangent = float.PositiveInfinity; keyframe.outTangent = float.PositiveInfinity; return keyframe; } static Keyframe GetIntKeyFrame(int value, float time) { var keyframe = new Keyframe(); keyframe.value = value; keyframe.time = time; keyframe.inTangent = float.PositiveInfinity; keyframe.outTangent = float.PositiveInfinity; return keyframe; } static void AddAnimationEvents(in Tag tag, IReadOnlyList frames, AnimationClip animationClip) { var events = new List(); var startTime = GetTimeFromFrame(frames, tag.fromFrame); for (var frameIndex = tag.fromFrame; frameIndex < tag.toFrame; ++frameIndex) { var frame = frames[frameIndex]; if (frame.eventStrings.Length == 0) continue; var frameTime = GetTimeFromFrame(frames, frameIndex); var eventStrings = frame.eventStrings; for (var m = 0; m < eventStrings.Length; ++m) { events.Add(new AnimationEvent() { time = frameTime - startTime, functionName = eventStrings[m] }); } } if (events.Count > 0) AnimationUtility.SetAnimationEvents(animationClip, events.ToArray()); } } }