355 lines
9.8 KiB
C#
355 lines
9.8 KiB
C#
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<ImportedAnimationFrame> frames = new List<ImportedAnimationFrame>();
|
|
public List<ImportedAnimation> animations = new List<ImportedAnimation>();
|
|
|
|
public bool hasAnimations
|
|
{
|
|
get
|
|
{
|
|
return animations != null && animations.Count > 0;
|
|
}
|
|
}
|
|
|
|
private Dictionary<string, ImportedAnimation> _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<ImportedAnimation> similarAnimations = new List<ImportedAnimation>();
|
|
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<AnimationClip>(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<string> 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<string> 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<string, ImportedAnimation>();
|
|
|
|
for (int i = 0; i < animations.Count; i++)
|
|
{
|
|
ImportedAnimation anim = animations[i];
|
|
_animationDatabase[anim.name] = anim;
|
|
}
|
|
}
|
|
}
|
|
}
|