using UnityEngine; using System; using System.Collections; using System.Collections.Generic; using System.Text.RegularExpressions; using UnityEditor; using System.Linq; namespace AnimationImporter { public class ImportedAnimationSheet { public string name { get; set; } public string assetDirectory { get; set; } public int width { get; set; } public int height { get; set; } public int maxTextureSize { get { return Mathf.Max(width, height); } } public List frames = new List(); public List animations = new List(); public bool hasAnimations { get { return animations != null && animations.Count > 0; } } private Dictionary _animationDatabase = null; private PreviousImportSettings _previousImportSettings = null; public PreviousImportSettings previousImportSettings { get { return _previousImportSettings; } set { _previousImportSettings = value; } } public bool hasPreviousTextureImportSettings { get { return _previousImportSettings != null && _previousImportSettings.hasPreviousTextureImportSettings; } } // ================================================================================ // public methods // -------------------------------------------------------------------------------- // get animation by name; used when updating an existing AnimatorController public AnimationClip GetClip(string clipName) { if (_animationDatabase == null) BuildIndex(); if (_animationDatabase.ContainsKey(clipName)) return _animationDatabase[clipName].animationClip; return null; } /* get animation by name; used when creating an AnimatorOverrideController we look for similar names so the OverrideController is still functional in cases where more specific or alternative animations are not present idle <- idle idleAlt <- idle */ public AnimationClip GetClipOrSimilar(string clipName) { AnimationClip clip = GetClip(clipName); if (clip != null) return clip; List similarAnimations = new List(); foreach (var item in animations) { if (clipName.Contains(item.name)) similarAnimations.Add(item); } if (similarAnimations.Count > 0) { ImportedAnimation similar = similarAnimations.OrderBy(x => x.name.Length).Reverse().First(); return similar.animationClip; } return null; } public void CreateAnimation(ImportedAnimation anim, string basePath, string masterName, AnimationTargetObjectType targetType) { AnimationClip clip; string fileName = basePath + "/" + masterName + "_" + anim.name + ".anim"; bool isLooping = anim.isLooping; // check if animation file already exists clip = AssetDatabase.LoadAssetAtPath(fileName); if (clip != null) { // get previous animation settings targetType = PreviousImportSettings.GetAnimationTargetFromExistingClip(clip); } else { clip = new AnimationClip(); AssetDatabase.CreateAsset(clip, fileName); } // change loop settings if (isLooping) { clip.wrapMode = WrapMode.Loop; clip.SetLoop(true); } else { clip.wrapMode = WrapMode.Clamp; clip.SetLoop(false); } // convert keyframes ImportedAnimationFrame[] srcKeyframes = anim.ListFramesAccountingForPlaybackDirection().ToArray(); ObjectReferenceKeyframe[] keyFrames = new ObjectReferenceKeyframe[srcKeyframes.Length + 1]; float timeOffset = 0f; for (int i = 0; i < srcKeyframes.Length; i++) { // first sprite will be set at the beginning (t=0) of the animation keyFrames[i] = new ObjectReferenceKeyframe { time = timeOffset, value = srcKeyframes[i].sprite }; // add duration of frame in seconds timeOffset += srcKeyframes[i].duration / 1000f; } // repeating the last frame at a point "just before the end" so the animation gets its correct length keyFrames[srcKeyframes.Length] = new ObjectReferenceKeyframe { time = timeOffset - (1f / clip.frameRate), // substract the duration of one frame value = srcKeyframes.Last().sprite }; // save curve into clip, either for SpriteRenderer, Image, or both if (targetType == AnimationTargetObjectType.SpriteRenderer) { AnimationUtility.SetObjectReferenceCurve(clip, AnimationClipUtility.spriteRendererCurveBinding, keyFrames); AnimationUtility.SetObjectReferenceCurve(clip, AnimationClipUtility.imageCurveBinding, null); } else if (targetType == AnimationTargetObjectType.Image) { AnimationUtility.SetObjectReferenceCurve(clip, AnimationClipUtility.spriteRendererCurveBinding, null); AnimationUtility.SetObjectReferenceCurve(clip, AnimationClipUtility.imageCurveBinding, keyFrames); } else if (targetType == AnimationTargetObjectType.SpriteRendererAndImage) { AnimationUtility.SetObjectReferenceCurve(clip, AnimationClipUtility.spriteRendererCurveBinding, keyFrames); AnimationUtility.SetObjectReferenceCurve(clip, AnimationClipUtility.imageCurveBinding, keyFrames); } EditorUtility.SetDirty(clip); anim.animationClip = clip; } public void ApplyGlobalFramesToAnimationFrames() { for (int i = 0; i < animations.Count; i++) { ImportedAnimation anim = animations[i]; anim.frames = frames.GetRange(anim.firstSpriteIndex, anim.Count).ToArray(); } } // ================================================================================ // determine looping state of animations // -------------------------------------------------------------------------------- public void SetNonLoopingAnimations(List nonLoopingAnimationNames) { Regex nonLoopingAnimationsRegex = GetRegexFromNonLoopingAnimationNames(nonLoopingAnimationNames); foreach (var item in animations) { item.isLooping = ShouldLoop(nonLoopingAnimationsRegex, item.name); } } private bool ShouldLoop(Regex nonLoopingAnimationsRegex, string name) { if (!string.IsNullOrEmpty(nonLoopingAnimationsRegex.ToString())) { if (nonLoopingAnimationsRegex.IsMatch(name)) { return false; } } return true; } private Regex GetRegexFromNonLoopingAnimationNames(List value) { string regexString = string.Empty; if (value.Count > 0) { // Add word boundaries to treat non-regular expressions as exact names regexString = string.Concat("\\b", value[0], "\\b"); } for (int i = 1; i < value.Count; i++) { string anim = value[i]; // Add or to speed up the test rather than building N regular expressions regexString = string.Concat(regexString, "|", "\\b", anim, "\\b"); } return new System.Text.RegularExpressions.Regex(regexString); } // ================================================================================ // Sprite Data // -------------------------------------------------------------------------------- public SpriteMetaData[] GetSpriteSheet(SpriteAlignment spriteAlignment, float customX, float customY) { SpriteMetaData[] metaData = new SpriteMetaData[frames.Count]; for (int i = 0; i < frames.Count; i++) { ImportedAnimationFrame spriteInfo = frames[i]; SpriteMetaData spriteMetaData = new SpriteMetaData(); // sprite alignment spriteMetaData.alignment = (int)spriteAlignment; if (spriteAlignment == SpriteAlignment.Custom) { spriteMetaData.pivot.x = customX; spriteMetaData.pivot.y = customY; } spriteMetaData.name = spriteInfo.name; spriteMetaData.rect = new Rect(spriteInfo.x, spriteInfo.y, spriteInfo.width, spriteInfo.height); metaData[i] = spriteMetaData; } return metaData; } public void ApplySpriteNamingScheme(SpriteNamingScheme namingScheme) { const string NAME_DELIMITER = "_"; if (namingScheme == SpriteNamingScheme.Classic) { for (int i = 0; i < frames.Count; i++) { frames[i].name = name + " " + i.ToString(); } } else { foreach (var anim in animations) { for (int i = 0; i < anim.frames.Length; i++) { var animFrame = anim.frames[i]; switch (namingScheme) { case SpriteNamingScheme.FileAnimationZero: animFrame.name = name + NAME_DELIMITER + anim.name + NAME_DELIMITER + i.ToString(); break; case SpriteNamingScheme.FileAnimationOne: animFrame.name = name + NAME_DELIMITER + anim.name + NAME_DELIMITER + (i + 1).ToString(); break; case SpriteNamingScheme.AnimationZero: animFrame.name = anim.name + NAME_DELIMITER + i.ToString(); break; case SpriteNamingScheme.AnimationOne: animFrame.name = anim.name + NAME_DELIMITER + (i + 1).ToString(); break; } } } } // remove unused frames from the list so they don't get created for the sprite sheet for (int i = frames.Count - 1; i >= 0; i--) { if (string.IsNullOrEmpty(frames[i].name)) { frames.RemoveAt(i); } } } public void ApplyCreatedSprites(Sprite[] sprites) { if (sprites == null) { return; } // add final Sprites to frames by comparing names // as we can't be sure about the right order of the sprites for (int i = 0; i < sprites.Length; i++) { Sprite sprite = sprites[i]; for (int k = 0; k < frames.Count; k++) { if (frames[k].name == sprite.name) { frames[k].sprite = sprite; break; } } } } // ================================================================================ // private methods // -------------------------------------------------------------------------------- private void BuildIndex() { _animationDatabase = new Dictionary(); for (int i = 0; i < animations.Count; i++) { ImportedAnimation anim = animations[i]; _animationDatabase[anim.name] = anim; } } } }