この投稿ではEditor拡張コードを用いて、SpriteRenderに表示するSpriteをキーフレームごとに変更するAnimationClipを生成する方法を紹介します。
※この投稿で示すコードは一部リフレクションを用いています。この箇所は、Unityのバージョンアップに伴い内部の実装が変更されて、該当箇所が使えなくなる可能性があります。またこのコードの利用は自己責任でお願いいたします。
やりたいこと
やりたいことは「AnimationClipの生成をGUIからではなく、Editor拡張のコードを通して行う」ということです。
AnimationClipの生成方法としては、Unity Technologies JapanさんのチュートリアルではSprite群のドラッグ&ドロップのやり方が紹介されています。tsubaki_t1さんのテラシュールブログでは、同じようにGUIのAnimaton Windowによる操作・設定方法が紹介されています。
GUIにより、AnimationClipを生成することは可能ですが、大量にある一定ルールに沿ってSprite群からAnimationClipの生成をすることを考えた場合、GUIの操作を通じたAnimationClipの生成は大変煩雑です。
このようなプロセスは自動化、もしくは適切なレベルまで半自動化してしまいましょう。この投稿ではその方法を紹介します。
またこの投稿ではメソッドのリフレクションを用いて、ループ設定が有効になったAnimationClipをコードから生成する方法を紹介します。
Unityのバージョンアップに伴い実装が変更されて、この方法が使えなくなる可能性があります。またこのコードの利用は自己責任でお願いいたします。
ソースコード
以下にSpriteAnimationClipDefinition.csを示します。
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
public class SpriteAnimationClipDefinition
{
public List<SpriteKeyframeDefinition> SpriteKeyframes { get; set; }
public WrapMode WrapMode { get; set; }
public bool IsLoop { get; set; }
public float FrameRate { get; set; }
public string ResulutPath { get; set; }
}
public class SpriteKeyframeDefinition
{
public Sprite Value { get; set; }
public float Time { get; set; }
public static explicit operator ObjectReferenceKeyframe (SpriteKeyframeDefinition spriteKeyframe)
{
return new ObjectReferenceKeyframe {
time = spriteKeyframe.Time,
value = spriteKeyframe.Value
};
}
}
SpriteKeyframeDefinitionクラスは、AnimationClipのあるフレームが参照するSpriteと、そのフレームの時刻を司る自作クラスです。
SpriteAnimationClipDefinitionクラスは、SpriteKeyframeDefinitionクラスのインスタンスを各フレームの情報としてリスト形式で所有し、またWrapMode
やFrameRate
など生成したAnimationClipの情報を司るクラスです。
以下にSpriteAnimationClipCreator.csを示します。
using UnityEditor;
using UnityEngine;
using System.Linq;
using System.Reflection;
public static class SpriteAnimationClipCreator
{
public static AnimationClip CreateAnimationClip (SpriteAnimationClipDefinition spriteAnimationDefinition)
{
AnimationClip animClip = ConvertToAnimationClip (spriteAnimationDefinition);
AssetDatabase.CreateAsset (animClip, spriteAnimationDefinition.ResulutPath);
EditorUtility.SetDirty (animClip);
return animClip;
}
public static AnimationClip ConvertToAnimationClip (SpriteAnimationClipDefinition spriteAnimationDefinition)
{
AnimationClip animClip = new AnimationClip ();
animClip.frameRate = spriteAnimationDefinition.FrameRate;
animClip.wrapMode = spriteAnimationDefinition.WrapMode;
if (spriteAnimationDefinition.IsLoop) {
AnimationClipSettings settings = AnimationUtility.GetAnimationClipSettings (animClip);
settings.loopTime = true;
MethodInfo setAnimationClipSettingMethod = typeof(AnimationUtility).GetMethod ("SetAnimationClipSettings", BindingFlags.NonPublic | BindingFlags.Static);
setAnimationClipSettingMethod.Invoke (null, new object[] { animClip, settings});
}
AnimationUtility.SetAnimationType (animClip, ModelImporterAnimationType.Generic);
EditorCurveBinding editorCurveBinding = new EditorCurveBinding (){
type = typeof(SpriteRenderer),
path = string.Empty,
propertyName = "m_Sprite",
};
ObjectReferenceKeyframe[] keyframes = spriteAnimationDefinition.SpriteKeyframes
.Select (spriteKeyframe => (ObjectReferenceKeyframe)spriteKeyframe)
.ToArray ();
AnimationUtility.SetObjectReferenceCurve (animClip, editorCurveBinding, keyframes);
return animClip;
}
}
SpriteAnimationClipCreatorクラスのCreateAnimationClipメソッドは、SpriteAnimationClipDefinitionを引数にとり、その情報を元にAnimationClipを生成するメソッドです。
ポイント
普段なじみの無いクラス群
など、この処理をおこなわなければあまり目にしないようなクラスを用います。
AnimationUtility以外は、まだドキュメントが整っていません。
リフレクションによりループ設定
GUIからSpriteのAnimationClipを生成した場合、デフォルトでループ設定が有効になっています。
残念ながらEditor拡張のコードからAnimationClipを生成した場合、ループの設定は無効になっています。おそらくbool型のデフォルトがfalseなのに起因すると思います。
さらに残念なことに、AnmationClipは複数選択状態でのGUI操作に対応しておらず、GUIから一気にループ設定を有効にすることはできません。
そしてさらに残念なことにループを設定するためのメソッドは存在するのですが、Unity 4.6.1f1ではそのメソッドのアクセス修飾子がinternalに設定されています。
大量にAnmationClipを生成した場合、それら全てのループ設定を有効にするのは大変煩雑です。
今回はリフレクションを用いてinternalアクセスレベルのメソッドを無理矢理呼び出すことで、ループ設定にも対応しています。次の部分です。
if (spriteAnimationDefinition.IsLoop) {
AnimationClipSettings settings = AnimationUtility.GetAnimationClipSettings (animClip);
settings.loopTime = true;
MethodInfo setAnimationClipSettingMethod = typeof(AnimationUtility).GetMethod ("SetAnimationClipSettings", BindingFlags.NonPublic | BindingFlags.Static);
setAnimationClipSettingMethod.Invoke (null, new object[] { animClip, settings});
}
Unityのバージョンアップにともない内部の実装が変更され、上記のコードが動かなくなることが考えられます。
また、利用は自己責任でお願いします。
Keyframeの定義
AnmationClipを生成にはObjectReferenceKeyframeというクラスを用います。このクラスはSpriteRendererを用いて、Spriteを用いるアニメーション以外にも使われます。
しかし今回はSpriteを用いたAnimationClipの生成が対象のため、SpriteKeyframeDefinitionという自作クラスを定義しました。
SpriteKeyframeDefinitionクラスから、ObjectReferenceKeyframeへの変換は、明示的な変換演算子を定義しており以下のように記述することで変換できます。
(ObjectReferenceKeyframe)spriteKeyframe
利用例(呼び出すコード)
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.Linq;
public static class SpriteAnimationClipCreatorCaller
{
[MenuItem("Assets/Create AnimationClip")]
public static void CreateAnimationClip ()
{
IEnumerable<Sprite> sprites = Selection.objects.OfType<Sprite> ();
if (!sprites.Any ()) {
Debug.LogWarning ("Please selecting sprites.");
return;
}
List<SpriteKeyframeDefinition> spriteKeyframes = sprites
.Select ((Sprite sprite, int index) => new SpriteKeyframeDefinition {
Value = sprite,
Time = 0.1F * index,
})
.ToList ();
SpriteAnimationClipDefinition definition = new SpriteAnimationClipDefinition {
SpriteKeyframes = spriteKeyframes,
WrapMode = WrapMode.Loop,
IsLoop = true,
FrameRate = 60.0F,
ResulutPath = "Assets/Sample.anim",
};
SpriteAnimationClipCreator.CreateAnimationClip (definition);
}
}
Spriteを選択した状態で、メニューからAssets -> Create AnimationClip
を選択してください。