本記事は QualiArts Advent Calendar 2025の17日目の記事になります。
はじめに簡単に自己紹介させていただきます。
私は株式会社QualiArtsでエンジニアとして勤務しており、主にスマートフォン向けゲームアプリの開発に携わっています。
今年、次のブログ記事を書きましたが、その後も開発フロー改善の一環として Unity エディタの改修を継続してきました。今回は、その中からもう少し具体的な開発事例を紹介できればと思います。Unity におけるアニメーション制作や開発環境改善の参考になれば幸いです。
本記事では次の2つの開発事例について紹介をします。
- TimelineのClipのミュート機能
- 演出用 プレハブ を一覧表示し、生成も行える専用Window
TimelineのClipのミュート機能
この機能は、Timeline 上に配置された 各 Clip を個別に再生/非再生へ切り替えられるものです。
タイムラインを細かく調整する際、特定の Clip を一時的に無効化することで、前後の動きの確認や調整を行いやすくなります。
Unity の Timeline には「Track 単位」でミュートする機能は用意されていますが、Clip 単位でのミュート機能は存在しません。
そのため、今回のプロジェクトでは Clip を個別にミュートできる機能を独自に実装しました。
この機能は「右クリックメニュー」と「ショートカット」で利用ができるようにしています。
右クリックメニュー 実装例
Unity の Timeline 上で右クリックメニューの機能を追加したい場合は、
ClipAction を継承してカスタムアクションを定義することで実現できます。
例えば、「右クリックメニューから選択した Clip の情報をログ出力する」だけの
シンプルな機能であれば、次のように実装できます。
実装例
[MenuEntry("Custom Actions/Hoge Clip Action")]
public class HogeClipMenuAction : ClipAction
{
public override bool Execute(IEnumerable<TimelineClip> clips)
{
Debug.Log("HogeClipMenuAction Execute");
return true;
}
public override ActionValidity Validate(IEnumerable<TimelineClip> clips)
{
return ActionValidity.Valid;
}
}
右クリックメニュー追加の例
Validateで、「このアクションは今の選択状態で実行できるか?」を判断し、Executeで、選択されたクリップに対して実際の処理を行います。今回の例ではデバックログを出力しています。
ショートカットの実装例
右クリックメニューに加えて、ショートカットキーからアクションを実行できるようにしておくと、操作性が大きく向上します。
特にTimeline 上での細かな調整作業では、ショートカットを用意しておくと作業の効率化に役立ちます。
Timeline 用のショートカットは、TimelineShortcut 属性を使うことでシンプルに実装できます。
以下は、ショートカット入力によってログを出力するだけのサンプルです。
public static class HogeshortCut
{
[TimelineShortcut("Create Debug Log", KeyCode.O, ShortcutModifiers.Action)]
public static void CreateDebugLogErrorSample(ShortcutArguments args)
{
Debug.LogError("カスタムされたショートカットです。");
}
}
このショートカットでは、Command + o を押すと設定されたログが出力されます。
先程の右クリックメニューのアクションを発火できるように調整をします。
[TimelineShortcut("Action Hoge Clip Menu Action", KeyCode.O, ShortcutModifiers.Action)]
public static void CreateDebugLogErrorSample(ShortcutArguments args)
{
Invoker.InvokeWithSelectedClips<HogeClipMenuAction>();
}
Invoker.InvokeWithSelectedClipsは現在Timelineウィンドウで選択中のクリップを取り出し、そのクリップを引数に渡して指定された処理を実行します。
ミュート機能の実装例
ミュート機能を実装する際は、まず Clip がミュートされているかどうかを判定するためのフラグ用プロパティ を用意します。
このフラグが基準となり、Clip が再生されるべきか、あるいは無視されるべきかを制御できるようになります。
ClipActionのExecuteを次のように変更します。
public override bool Execute(IEnumerable<TimelineClip> clips)
{
var clipsList = clips.ToArray();
UndoExtensions.RegisterClips(clipsList, "Mute Clips");
foreach (var clip in clipsList)
{
// ミュートフラグを調整
if (clip.asset is HogeClipBase clipAsset)
{
clipAsset.IsMuteClip = !clipAsset.IsMuteClip;
}
}
// Editorを更新する
TimelineEditor.Refresh(
RefreshReason.ContentsModified |
RefreshReason.WindowNeedsRedraw |
RefreshReason.SceneNeedsUpdate
);
return true;
}
HogeClipBaseはClipを拡張したクラスになりIsMuteClipというbool値のフラグを保持しています。
Execute内ではこのフラグを操作することで、ミュートしたかどうかの切り替えを行います。
Timelineでの処理
ここからは Timeline 上での個別処理に関わるため詳細には踏み込みませんが、
Clip の処理が実際に呼び出されるタイミングでミュートフラグを参照し、
再生/非再生を切り替えることができます。
たとえば、次のようにミュート状態をチェックして処理をスキップするだけでも、
動作としては十分機能します。
#if UNITY_EDITOR
if (clipAsset.IsMuteClip) return;
#endif
ミュート機能は Editor 上のみで動作させたいケースがほとんど だと思いますので、
このように #if UNITY_EDITOR を用いて Editor 限定で動作させる実装にしておくのが適切です。
これにより、ビルド時にはミュート制御のコードが含まれず、
ゲーム本番環境への影響を避けることができます。
Timelineエディタ上の見た目の調整
ミュートされた Clip がひと目で判別できるように、
Timeline エディタ上の見た目を変更して視覚的なフィードバックを加えると便利です。
Unity の ClipEditor を利用すると、
Clip のアイコンや背景色など、エディタ上での表示を柔軟にカスタマイズできます。
今回は、ミュート状態をわかりやすくするために、次の 2 点を調整します。
- アイコン表示
- 背景の変更
アイコンの表示
public override void DrawBackground(TimelineClip clip, ClipBackgroundRegion region)
{
var clipAsset = clip.asset as HogeClipBase;
if (clipAsset.IsMuteClip)
{
var iconPos = new Rect(region.position.xMax - 30, region.position.y, 30, 11);
GUI.DrawTexture(iconPos, (Texture), ScaleMode.StretchToFill, true, 1,
Color.white, Vector4.zero, Vector4.zero);
}
}
GUI.DrawTextureでClip上に画像を表示しています。
(Texture)になっている箇所に、表示したい画像データを指定してください。
Clipの背景の変更
ClipEditorのClipDrawOptionsで調整を行います。
public override ClipDrawOptions GetClipOptions(TimelineClip clip)
{
var options = base.GetClipOptions(clip);
var clipAsset = clip.asset as HogeClipBase;
if (clipAsset == null) return options;
if (clipAsset.IsMuteClip)
{
options.highlightColor = Color.gray;
options.displayClipName = false;
}
return options;
}
ClipDrawOptionsのhighlightColorを調整して色の変更を行っています。
見た目
Clipはミュート時、次のように表示されるようになりました。
ミュート時のClipの見た目の例
感想
こちらはデザイナーからの要望により実装した機能です。
当初は右クリックメニューからのみ操作できましたが、右クリック操作は煩雑になりやすいため、ショートカットでもミュートできるように対応しました。
今回のミュート機能は、デザイナーからの要望を受けて実装した機能です。
当初は右クリックメニューからのみ操作できる仕様でしたが、右クリック操作はどうしても煩雑になりがちなため、
よりスムーズに扱えるよう ショートカットからもミュート切り替えができるよう改善しました。
Timeline に限らず、ショートカットで操作できる機能は作業効率を大きく向上させるため、もし実装が可能であれば、同様の操作系機能を今後も検討していただければと思います。
演出用プレハブを一覧表示し、生成も行える専用Window
プロジェクトの規模が大きくなるにつれ、演出用の実装やプレハブが増えていきます。
これらは再利用しやすいよう外部化していますが、数が増えるほど「どのプレハブがどの演出なのか」把握しづらくなる問題がありました。
一覧ドキュメントはあるものの、Unity 上で目的のプレハブを探す手間は残り、
特にデザイナーにとっては目的のアセットにたどり着くまで時間がかかることもありました。
そこで今回、必要なプレハブを簡単に生成できる機能を新たに用意し、この課題の解消を図りました。
以下では、その概要をご紹介します。
専用のWindow
次のような画像のように、専用のWindowを作成し、その中から対象の共通エフェクトを選べるようにしました。
専用windowの見た目の例
実装例
次の手順で実装方法を紹介します。
- ProjectSettingsからWindow内容を調整できるようにする
- 選択したときのロジック
ProjectSettingsからWindowの内容を調整できるようにする
Windowに表示する内容を、UnityのProjectSettingsから編集できるように対応しました。
ここで設定を一元管理することで、後からの追加・削除が容易になり、
デザイナー自身が調整できる点も大きなメリットです。
そのために、ProjectSettings 上で編集可能な ScriptableObject を用意し、今回は次の3項目を設定できるようにしました。
- 概要
- 演出のプレハブ
- Windowに表示する画像
ScriptableObjectの実装例
必要なパラメーターを用意します。
[Serializable]
public class CommonAnimationPrefabResourcesData
{
[SerializeField]
private string _description;
public string Description => _description;
[SerializeField]
private GameObject _commonAnimationPrefab;
public GameObject CommonAnimationPrefab => _commonAnimationPrefab;
[SerializeField]
private Texture2D _effectImage;
public Texture2D EffectImage => _effectImage;
}
/// <summary>
/// 共通アニメーションPrefabの参照を持つScriptableObject
/// </summary>
public class CommonAnimationPrefabResources : ScriptableObject
{
private const string AssetGuid = "*************";
private static CommonAnimationPrefabResources _instance;
public static CommonAnimationPrefabResources Instance
{
get
{
if (_instance == null)
{
_instance = AssetDatabase.LoadAssetAtPath<CommonAnimationPrefabResources>(
AssetDatabase.GUIDToAssetPath(AssetGuid)
);
}
return _instance;
}
}
[SerializeField]
private CommonAnimationPrefabResourcesData[] _dataList;
public CommonAnimationPrefabResourcesData[] DataList => _dataList;
}
ProjectSettingsの実装例
先程定義をしたScriptableObjectをProjectSettingsで表示できるようにします。
/// <summary>
/// Project SettingsにCommon UI Animation Windowの設定を追加する
/// </summary>
public class UiAnimationSettingsProvider : SettingsProvider
{
private const string SettingPath = "Project/UI Animation/";
private CommonAnimationPrefabResources _instance;
private UnityEditor.Editor _editor;
private UiAnimationSettingsProvider() : base(SettingPath, SettingsScope.Project)
{
label = "共通演出プレハブ設定";
}
[SettingsProvider]
public static SettingsProvider CreateUiAnimationSettingsProvider()
{
var provider = new UiAnimationSettingsProvider()
{
guiHandler = _ => EditorGUILayout.Toggle("Menu", true),
keywords = new HashSet<string>(new[] { "Campus", "UI", "Animation" })
};
return provider;
}
/// <summary>
/// 設定を開く
/// </summary>
public static void Open()
{
SettingsService.OpenProjectSettings(SettingPath);
}
public override void OnGUI(string searchContext)
{
if (_instance == null)
{
var instance = CommonAnimationPrefabResources.Instance;
if (instance == null)
{
return;
}
_instance = instance;
UnityEditor.Editor.CreateCachedEditor(_instance, null, ref _editor);
}
_editor.OnInspectorGUI();
}
}
ProjectSettingsに設定項目が追加されました。必要なアセットを設定してWindowを作っていきます。
ProjectSettingsの見た目の例
感想
Projectからアセットを検索するのは、エンジニアでも結構手間になるので、このWindowを用意したことで、デザイナーの方にも喜んでもらえたのが良かった。
最後に
今回のブログでは、2つのツール改修事例をご紹介しましたが、この他にも継続的に開発環境の改善を進めています。
また機会があれば、そうした取り組みも改めて紹介したいと思います。



