【Unity】アニメーション統合クリップについて
はじめに
初めての方も、そうでない方もこんにちは!
現役ゲームプログラマーのたむぼーです。
自己紹介を載せているので、気になる方は見ていただければ嬉しいです!
今回は
アニメーション統合クリップ
について紹介します
アニメーション統合クリップとは
アニメーションは、オブジェクト毎にAnimatorControllerとAnimationClipを使います
例①:ホーム画面にあるバナーのアニメーションの場合
画像①のように名前による管理が必要になります
このような状態だと、オブジェクト毎にファイルが増えていき、管理が大変になる。
そのため、AnimatorControllerに紐づく(オブジェクトに紐づく)AnimationClipは統合して管理します
先ほどの例①のAnimationClipをAnimatorControllerに統合した場合
画像②のように、HomeBannerAnimatorに内包する形で紐づくアニメーションを持たせる
このようにすることで、オブジェクト毎のアニメーションの管理がしやすくなる。
使い方
・AnimatorControllerを置きたいProjectの階層で
右クリックし、AssetsメニューからAnimationCombineClipを選択
・AnimationCombineClipウィンドウが立ち上がるので
Target Clipに作成済みのAnimatorControllerを設定するか、
Animator Controller Nameに作成したいAnimatorControllerの名前を入力し、Createを押す
・作成されたAnimatorControllerはデフォルトで、ShowとHideを持っています。
また、ShowとHideはAnimatorにも自動で設定されます。
【Clipの追加、リネーム、削除について】
・Clipの追加はAdd New Clipに名前を入力し、Createを押す(名前を入力するとCreateボタンが出ます)
・Clipのリネームは対象のクリップのRenameボタンを押し、入力欄に変更後の名前を入力する
・Clipの削除は対象のクリップのRemoveボタンを押す
スクリプト
using UnityEngine;
using UnityEditor;
using System.Collections.Generic;
using System.IO;
using UnityEditor.Animations;
using System.Linq;
namespace Editor
{
sealed public class AnimationCombineClip : EditorWindow
{
private string _controllerName;
private AnimatorController _controller;
private string _clipName;
private HashSet<AnimationClip> _clipSet;
private List<AnimationClip> _clipsToRemove;
private Dictionary<AnimationClip, string> _renameClips;
private const string AddControllerName = "Animator";
/// <summary>
/// メニュー項目を追加し、ウィンドウを作成するメソッド
/// </summary>
[MenuItem("Assets/AnimationCombineClip")]
private static void Create()
{
AnimationCombineClip window = GetWindow<AnimationCombineClip>();
if (Selection.activeObject is AnimatorController)
{
window._controller = Selection.activeObject as AnimatorController;
window.CacheClips();
}
}
/// <summary>
/// アニメーターコントローラーに含まれるアニメーションクリップをキャッシュするメソッド
/// </summary>
private void CacheClips()
{
// クリップのセットを初期化
_clipSet = new HashSet<AnimationClip>();
if (_controller == null)
{
return;
}
// コントローラーに含まれる全てのアセットを取得
Object[] allAssets = AssetDatabase.LoadAllAssetsAtPath(AssetDatabase.GetAssetPath(_controller));
foreach (Object asset in allAssets)
{
// アセットがアニメーションクリップの場合、セットに追加
if (asset is AnimationClip clip)
{
_clipSet.Add(clip);
}
}
_clipsToRemove = new List<AnimationClip>();
_renameClips = new Dictionary<AnimationClip, string>();
}
/// <summary>
/// エディタウィンドウのGUIを描画するメソッド
/// </summary>
private void OnGUI()
{
EditorGUILayout.LabelField("Target Clip");
AnimatorController newController = EditorGUILayout.ObjectField(_controller, typeof(AnimatorController), false) as AnimatorController;
// 新しいコントローラーが選択された場合、キャッシュを更新
if (newController != _controller)
{
_controller = newController;
CacheClips();
}
if (_controller == null)
{
EditorGUILayout.Space();
EditorGUILayout.HelpBox("Please select an Animator Controller.", MessageType.Warning);
EditorGUILayout.Space();
EditorGUILayout.LabelField("Animator Controller Name");
_controllerName = EditorGUILayout.TextField(_controllerName);
if (GUILayout.Button("Create New Animator Controller"))
{
CreateNewAnimatorController();
}
return;
}
EditorGUILayout.Space();
EditorGUILayout.LabelField("Add New Clip");
EditorGUILayout.BeginVertical("Box");
if (GUILayout.Button("CreateDefaultClip"))
{
if (!ContainsClipName("Show"))
{
CreateNewClip("Show");
}
if (!ContainsClipName("Hide"))
{
CreateNewClip("Hide");
}
}
_clipName = EditorGUILayout.TextField(_clipName);
if (_clipSet == null || ContainsClipName(_clipName) || string.IsNullOrEmpty(_clipName))
{
EditorGUILayout.LabelField("Cannot create duplicate or empty names");
}
else if (GUILayout.Button("Create"))
{
CreateNewClip(_clipName);
_clipName = string.Empty;
}
EditorGUILayout.EndVertical();
// クリップが存在しない場合は終了
if (_clipSet == null || _clipSet.Count == 0)
{
return;
}
EditorGUILayout.Space();
EditorGUILayout.LabelField("Remove or Rename Clip");
EditorGUILayout.BeginVertical("Box");
foreach (AnimationClip clip in _clipSet)
{
EditorGUILayout.BeginHorizontal();
EditorGUILayout.LabelField(clip.name);
// Rename機能の追加
if (_renameClips.ContainsKey(clip))
{
_renameClips[clip] = EditorGUILayout.TextField(_renameClips[clip], GUILayout.Width(100));
if (GUILayout.Button("Apply", GUILayout.Width(100)))
{
RenameClip(clip, _renameClips[clip]);
_renameClips.Remove(clip);
}
}
else
{
if (GUILayout.Button("Rename", GUILayout.Width(100)))
{
_renameClips[clip] = clip.name;
}
}
if (GUILayout.Button("Remove", GUILayout.Width(100)))
{
_clipsToRemove.Add(clip);
}
EditorGUILayout.EndHorizontal();
}
EditorGUILayout.EndVertical();
foreach (AnimationClip clip in _clipsToRemove)
{
RemoveClip(clip);
}
_clipsToRemove.Clear();
}
/// <summary>
/// 新しいアニメーターコントローラーを作成する
/// </summary>
private void CreateNewAnimatorController()
{
var selects = Selection.GetFiltered(typeof(UnityEngine.Object), SelectionMode.Assets);
if (selects.Length == 0)
{
EditorUtility.DisplayDialog("Error", "Please select a folder in the Project window.", "OK");
return;
}
if (!_controllerName.EndsWith(AddControllerName))
{
_controllerName += AddControllerName;
}
string path = AssetDatabase.GetAssetPath(selects[0]);
Debug.Log(path);
if (!Directory.Exists(path))
{
path = Path.GetDirectoryName(path);
}
path = Path.Combine(path, _controllerName + ".controller");
path = AssetDatabase.GenerateUniqueAssetPath(path);
_controller = AnimatorController.CreateAnimatorControllerAtPath(path);
CacheClips();
// BaseLayerにDoNothingステートを追加
AnimatorStateMachine stateMachine = _controller.layers[0].stateMachine;
AnimatorState doNothingState = stateMachine.AddState("DoNothing");
doNothingState.writeDefaultValues = false;
// ShowとHideクリップを作成し、BaseLayerに追加
if (!ContainsClipName("Show"))
{
CreateNewClip("Show");
}
if (!ContainsClipName("Hide"))
{
CreateNewClip("Hide");
}
// ShowとHideステートを追加
AddAnimatorState(stateMachine, "Show");
AddAnimatorState(stateMachine, "Hide");
}
/// <summary>
/// AnimatorStateを追加
/// </summary>
private AnimatorState AddAnimatorState(AnimatorStateMachine stateMachine, string name)
{
AnimatorState state = stateMachine.AddState(name);
state.motion = _clipSet.FirstOrDefault(clip => clip.name == name);
state.writeDefaultValues = false;
return state;
}
/// <summary>
/// Exitに遷移するように設定
/// </summary>
private void AddExitTransition(AnimatorState state)
{
AnimatorStateTransition transition = state.AddExitTransition();
transition.hasExitTime = true;
transition.exitTime = 0;
transition.duration = 0;
transition.offset = 0;
transition.interruptionSource = TransitionInterruptionSource.None;
transition.orderedInterruption = false;
}
/// <summary>
/// 新しいアニメーションクリップを作成し、コントローラーに追加する
/// </summary>
private void CreateNewClip(string clipName)
{
AnimationClip newClip = AnimatorController.AllocateAnimatorClip(clipName);
AnimationClipSettings settings = AnimationUtility.GetAnimationClipSettings(newClip);
settings.loopTime = false;
AnimationUtility.SetAnimationClipSettings(newClip, settings);
// アセットとして追加
AssetDatabase.AddObjectToAsset(newClip, _controller);
AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(_controller));
AssetDatabase.Refresh();
_clipSet.Add(newClip);
}
/// <summary>
/// 指定されたアニメーションクリップの名前を変更する
/// </summary>
private void RenameClip(AnimationClip clip, string newName)
{
string oldName = clip.name;
clip.name = newName;
EditorUtility.SetDirty(clip);
foreach (AnimatorControllerLayer layer in _controller.layers)
{
UpdateStateMachine(layer.stateMachine, oldName, newName);
}
AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(_controller));
AssetDatabase.Refresh();
}
/// <summary>
/// 状態マシン内のすべての状態を更新してクリップ名を変更する
/// </summary>
private void UpdateStateMachine(AnimatorStateMachine stateMachine, string oldName, string newName)
{
foreach (ChildAnimatorState state in stateMachine.states)
{
AnimationClip stateClip = state.state.motion as AnimationClip;
if (stateClip != null && stateClip.name == newName && state.state.name == oldName)
{
state.state.name = newName;
EditorUtility.SetDirty(state.state);
}
}
foreach (ChildAnimatorStateMachine childStateMachine in stateMachine.stateMachines)
{
UpdateStateMachine(childStateMachine.stateMachine, oldName, newName);
}
}
/// <summary>
/// 指定されたアニメーションクリップを削除する
/// </summary>
private void RemoveClip(AnimationClip clip)
{
DestroyImmediate(clip, true);
AssetDatabase.ImportAsset(AssetDatabase.GetAssetPath(_controller));
AssetDatabase.Refresh();
_clipSet.Remove(clip);
}
/// <summary>
/// 指定された名前のアニメーションクリップが存在するか
/// </summary>
private bool ContainsClipName(string name)
{
foreach (AnimationClip clip in _clipSet)
{
if (clip.name == name)
{
return true;
}
}
return false;
}
}
}
Discussion