using UnityEngine;
using System.Collections.Generic;
using System.Linq;
#if
using UnityEditor;
using UnityEditor.Animations;
using AD = UnityEditor.AssetDatabase;
using EGL = UnityEditor.EditorGUILayout;
#endif
/// <summary>
/// キャラクタ
/// </summary>
[RequireComponent(typeof(SpriteRenderer))]
public class Character : MonoBehaviour
{
#if
/// <summary>
/// エディタ拡張
/// </summary>
[CustomEditor(typeof(Character))]
[CanEditMultipleObjects]
public class CharacterEditor : Editor
{
/// <summary>
/// 設定
/// </summary>
private static class Settings
{
/// <summary>
/// 名前設定
/// </summary>
public static class Names
{
/// <summary>ステート名接頭語:歩行中</summary>
public const string stateWalk = "Walk";
/// <summary>ステート名接頭語:停止</summary>
public const string stateIdol = "Idol";
/// <summary>パラメータ名:X方向</summary>
public const string paramDirecitonX = "DirectionX";
/// <summary>パラメータ名:Y方向</summary>
public const string paramDirecitonY = "DirectionY";
/// <summary>パラメータ名:歩行中フラグ</summary>
public const string paramWalking = "Walking";
}
/// <summary>方向名とBlendTreeにおける座標ペア</summary>
public static List<Direction> directions =
new List<Direction>()
{
new Direction("Down",new Vector2(0,-1) ), //テクスチャ1行目
new Direction("Left",new Vector2(-1,0) ), //2行目
new Direction("Right",new Vector2(1,0) ), //3行目
new Direction("Up",new Vector2(0,1) ), //4行目
};
}
/// <summary>
/// 方向名とBlendTreeにおける座標ペアを保持するクラス
/// </summary>
private class Direction
{
public string Name { get; set; }
public Vector2 TreePosition { get; set; }
public Direction(string name, Vector2 treePosition)
{
Name = name;
TreePosition = treePosition;
}
}
/// <summary>AnimatorControllerアセット拡張子</summary>
private const string extensionController = ".controller";
/// <summary>AnimationClipアセット拡張子</summary>
private const string extensionClip = ".anim";
/// <summary>アセット保存フォルダパス</summary>
private static string folderPath = "Assets/Animations/";
/// <summary>AnimationClipをAnimatorControllerの子アセットにするフラグ</summary>
private static bool subassetFlag = true;
/// <summary>歩行パターン(インデックスのList)</summary>
private static List<int> walkIndexes = new List<int>() { 0, 1, 2, 1 };
/// <summary>静止画インデックス</summary>
private static int idolIndex = 1;
/// <summary>Inspector折りたたみフラグ</summary>
private static bool foldout = false;
/// <summary>
/// Inspectorのカスタム
/// </summary>
public override void OnInspectorGUI()
{
var t = (Character)target;
//デフォルトInspector
DrawDefaultInspector();
//歩行アニメーション自動生成ボタン&実装
EGL.Separator();
foldout = EGL.Foldout(foldout, "歩行アニメーション自動生成");
if(!foldout)
return;
//設定
folderPath = EGL.TextField("フォルダパス", folderPath);
subassetFlag = EGL.Toggle("Clipサブアセット化", subassetFlag);
if(subassetFlag)
EGL.HelpBox(
"AnimatorControllerアセットの子アセットとしてAnimationClipを保存",
MessageType.Info);
else
EGL.HelpBox(
"AnimatorControllerアセットと同じフォルダにAnimationClipアセットを保存",
MessageType.Info);
if(GUILayout.Button("歩行アニメーション生成"))
{
if(!folderPath.EndsWith("/"))
folderPath = folderPath + "/";
var sr = t.GetComponent<SpriteRenderer>();
if(sr == null || sr.sprite == null)
{
Debug.LogError("SpriteRenderer.sprite が見つかりません。");
return;
}
var allasset =
new List<Object>(
AD.LoadAllAssetsAtPath(AD.GetAssetPath(sr.sprite)));
var texture =
(Texture2D)allasset.
Single(x => x.GetType() == typeof(Texture2D));
var sprites =
allasset.
Where(x => x.GetType() == typeof(Sprite)).
Select(x => (Sprite)x).ToList();
var assetPath = folderPath + texture.name + extensionController;
//ファイル存在確認
if(AD.LoadAssetAtPath<Object>(assetPath))
{
Debug.LogError("既に " + assetPath + " が存在します。");
return;
}
//Animatorアタッチ
var animator = t.GetComponent<Animator>();
if(animator == null)
animator = t.gameObject.AddComponent<Animator>();
//AnimatorController生成
var controller = new AnimatorController();
controller.name = texture.name;
controller.AddLayer("Base Layer");
animator.runtimeAnimatorController = controller;
//Parameterを登録
controller.AddParameter(
Settings.Names.paramDirecitonX,
AnimatorControllerParameterType.Float);
controller.AddParameter(
Settings.Names.paramDirecitonY,
AnimatorControllerParameterType.Float);
controller.AddParameter(
Settings.Names.paramWalking,
AnimatorControllerParameterType.Bool);
controller.parameters[0].defaultFloat = 0;
controller.parameters[1].defaultFloat = -1;
controller.parameters[2].defaultBool = false;
//AnimationClip生成
var walkClipsWithPos = new List<KeyValuePair<AnimationClip, Vector2>>();
var idolClipsWithPos = new List<KeyValuePair<AnimationClip, Vector2>>();
for(int i = 0; i < Settings.directions.Count; ++i)
{
////静止アニメーション
idolClipsWithPos.Add(new KeyValuePair<AnimationClip, Vector2>(
MakeAnimacionCrip(
texture.name + "_" + Settings.Names.stateIdol + Settings.directions[i].Name,
sprites.Skip(i * 3).Take(3).ToList(),
new List<int>() { idolIndex }),
Settings.directions[i].TreePosition));
////歩行アニメーション
walkClipsWithPos.Add(new KeyValuePair<AnimationClip, Vector2>(
MakeAnimacionCrip(
texture.name + "_" + Settings.Names.stateWalk + Settings.directions[i].Name,
sprites.Skip(i * 3).Take(3).ToList(),
walkIndexes),
Settings.directions[i].TreePosition));
}
//BlendTree生成・登録
var stateMachine = controller.layers[0].stateMachine;
var idolTree = MakeBlendTree(Settings.Names.stateIdol + "Tree", idolClipsWithPos);
var walkTree = MakeBlendTree(Settings.Names.stateWalk + "Tree", walkClipsWithPos);
var idolState = stateMachine.AddState(Settings.Names.stateIdol + "Tree");
var walkState = stateMachine.AddState(Settings.Names.stateWalk + "Tree");
idolState.motion = idolTree;
walkState.motion = walkTree;
//BlendTree間のTransition生成
var idolWalkTransition = idolState.AddTransition(walkState);
var walkIdolTransition = walkState.AddTransition(idolState);
idolWalkTransition.AddCondition(
AnimatorConditionMode.If,
0,
Settings.Names.paramWalking);
walkIdolTransition.AddCondition(
AnimatorConditionMode.IfNot,
0,
Settings.Names.paramWalking);
//デフォルトスプライトを設定
sr.sprite = sprites[1];
//アセットファイル出力
AD.CreateAsset(controller, assetPath);
AD.AddObjectToAsset(stateMachine, controller);
foreach(var state in stateMachine.states)
{
AD.AddObjectToAsset(state.state, controller);
if(!(state.state.motion is AnimationClip))
AD.AddObjectToAsset(state.state.motion, controller);
foreach(var transition in state.state.transitions)
AD.AddObjectToAsset(transition, controller);
}
//AnimationClip出力
if(subassetFlag)
{
//サブアセットとして登録
foreach(var clip in controller.animationClips)
AD.AddObjectToAsset(clip, controller);
AD.ImportAsset(AD.GetAssetPath(controller));
}
else
{
//アセットとして保存
foreach(var clip in controller.animationClips)
AD.CreateAsset(clip, folderPath + clip.name + extensionClip);
}
AD.SaveAssets();
AD.Refresh();
}
}
/// <summary>
/// AnimationClip生成
/// </summary>
/// <param name="name">名前</param>
/// <param name="sprites">スプライトList</param>
/// <param name="animationIndexes">アニメーションのインデックス</param>
/// <returns></returns>
private AnimationClip MakeAnimacionCrip(
string name,
IList<Sprite> sprites,
IList<int> animationIndexes)
{
var clip = new AnimationClip();
var binding = new EditorCurveBinding();
binding.path = "";
binding.type = typeof(SpriteRenderer);
binding.propertyName = "m_Sprite";
//キーフレーム設定
var keyFrames = new List<ObjectReferenceKeyframe>();
for(int i = 0; i < animationIndexes.Count; i++)
{
if(animationIndexes[i] >= sprites.Count)
{
Debug.LogWarning(
"スプライト " +
sprites.Count +
"枚 に対してインデックス " +
animationIndexes[i] +
" が指定されたのでスキップします");
continue;
}
keyFrames.Add(new ObjectReferenceKeyframe()
{
time = (float)i / animationIndexes.Count,
value = sprites[animationIndexes[i]]
});
}
//名前、フレームレート、ループ設定
clip.name = name;
clip.frameRate = animationIndexes.Count;
var settings = AnimationUtility.GetAnimationClipSettings(clip);
settings.loopTime = true;
AnimationUtility.SetAnimationClipSettings(clip, settings);
//登録
AnimationUtility.SetObjectReferenceCurve(
clip,
binding,
keyFrames.ToArray());
return clip;
}
/// <summary>
/// BlencTree生成
/// </summary>
/// <param name="name">名前</param>
/// <param name="clipWithPositions">Clipと座標のペア</param>
/// <returns></returns>
private BlendTree MakeBlendTree(
string name,
ICollection<KeyValuePair<AnimationClip, Vector2>> clipWithPositions)
{
var tree = new BlendTree();
tree.name = name;
tree.blendType = BlendTreeType.SimpleDirectional2D;
tree.blendParameter = Settings.Names.paramDirecitonX;
tree.blendParameterY = Settings.Names.paramDirecitonY;
foreach(var clip in clipWithPositions)
tree.AddChild(clip.Key, clip.Value);
return tree;
}
}
#endif
}