Ⓜ️

アセットの選択履歴を記録するシンプルなエディタ拡張「UnityAssetHistory」を作りました。

に公開

UnityAssetHistory

https://9cguide.appspot.com/unity_unity_asset_history.html

どんなエディタ拡張なのか

ProjectViewで選択したアセットを自動で順番通りに記録するエディタ拡張です。
Unityで作業していると「さっきのアセットはどれだっけ?」となることがしばしばありますが、
本エディタ拡張があれば、そんなときに、さっきのアセットにすぐに戻ることができます。

使い方

Project View でアセットを選択します。

選択したアセットが自動的に記録されます。

選択したアセットは自動で順番に記録されます。最後に選択したアセットが一番上に表示されます。

戻りたいアセットをクリックすればすぐに戻ることができます。
さっきのアセットの場所で迷うことはもうありません!

インストール

Gitプロジェクトなどは用意していません。
以下のソースコードをコピーして、UnityAssetHistory.cs というファイル名で、Editorフォルダに保存してください。
Unity Editor で「メニュー > Tools > UnityAssetHistory」から開きます。

ソースコード

UnityAssetHistory.cs
#if UNITY_EDITOR

using System.Collections.Generic;
using UnityEditor;
using UnityEngine;

/// <summary>
/// UnityAssetHistory Ver 1.0.0
/// Copyright (c) 2025 MMGames
/// Released under the MIT License.
/// https://9cguide.appspot.com/unity_unity_asset_history.html
/// </summary>
public class UnityAssetHistory : EditorWindow
{
    /// <summary>
    /// 選択したアセットの履歴(変数名の変更に注意)
    /// </summary>
    [SerializeField]
    private List<Object> selectedAssetHistoryList = new();

    /// <summary>
    /// 現在のスクロール位置
    /// </summary>
    private Vector2 scrollPosition;

    /// <summary>
    /// アセットのみを対象とする(フォルダやシーン内オブジェクトは無視する)
    /// </summary>
    private bool IsAssetOlly = false;

    /// <summary>
    /// UnityEditorのメニューに登録
    /// </summary>
    [MenuItem("Tools/UnityAssetHistory")]
    private static void RegisterMenu()
    {
        GetWindow<UnityAssetHistory>();
    }

    /// <summary>
    /// 有効化
    /// </summary>
    private void OnEnable()
    {
        // アセット選択イベント

        Selection.selectionChanged += OnSelectionChanged;
    }

    /// <summary>
    /// 再描画
    /// </summary>
    private void OnGUI()
    {
        // SerializedObjectに変換する

        var serializedObject = new SerializedObject(this);
        serializedObject.Update();

        // 選択したアセットの履歴を表示

        scrollPosition = EditorGUILayout.BeginScrollView(scrollPosition);

        EditorGUILayout.PropertyField(serializedObject.FindProperty("selectedAssetHistoryList"), true);

        EditorGUILayout.EndScrollView();

        EditorGUILayout.Space();

        // クリーンボタン

        if (GUILayout.Button("Clean"))
        {
            OnClickCleanButton();
        }
    }

    /// <summary>
    /// アセットが選択された
    /// </summary>
    private void OnSelectionChanged()
    {
        bool isRepaint = false;

        // 選択されたオブジェクトを取得

        var selectedObjectList = Selection.objects;

        foreach (Object obj in selectedObjectList)
        {
            // アセットでなければ無視

            if (IsAssetOlly)
            {
                if (IsAsset(obj) == false) return;
            }

            // すでに履歴に存在しているか?

            var selectedAssetHistoryIndex = selectedAssetHistoryList.IndexOf(obj);

            if (selectedAssetHistoryIndex >= 0)
            {
                // 存在しているなら削除(先頭に追加されるので一番上に移動する)

                selectedAssetHistoryList.Remove(obj);
            }

            // 再描画フラグON

            isRepaint = true;

            // 先頭に追加

            selectedAssetHistoryList.Insert(0, obj);
        }

        // アクセスできなくなったアセットがあれば消しておく

        if (GCAll()) isRepaint = true;

        // 再描画

        if (isRepaint) Repaint();
    }

    /// <summary>
    /// クリーンボタンがクリックしされた
    /// </summary>
    private void OnClickCleanButton()
    {
        // 履歴がないなら何もしない

        if (selectedAssetHistoryList.Count == 0) return;

        // クリアして再描画

        selectedAssetHistoryList = new();
        Repaint();
    }

    /// <summary>
    /// アクセスできなくなったすべてのアセットを履歴から削除
    /// </summary>
    /// <returns></returns>
    private bool GCAll()
    {
        // 1つもなければ再描画不要

        if (GCSingle() == false) return false;

        // アクセスできなくなったアセットがある限り削除を繰り返す

        while (GCSingle())
        {
        }

        return true;
    }

    /// <summary>
    /// アクセスできなくなったアセットを1つだけ履歴から削除
    /// </summary>
    /// <returns></returns>
    private bool GCSingle()
    {
        // アクセスできなくなったアセットを探す

        for (int i = 0; i < selectedAssetHistoryList.Count; i++)
        {
            if (selectedAssetHistoryList[i] == null)
            {
                // 見つかったら削除して戻る

                selectedAssetHistoryList.RemoveAt(i);
                return true;
            }
        }

        // 1つもなかった

        return false;
    }

    /// <summary>
    /// 指定されたオブジェクトはアセットなのか?
    /// </summary>
    /// <param name="inObject"></param>
    /// <returns></returns>
    private bool IsAsset(Object inObject)
    {
        string assetPath = AssetDatabase.GetAssetPath(inObject);
        return (assetPath.Length > 0);
    }

    /// <summary>
    /// 解放
    /// </summary>
    private void OnDestroy()
    {
        Selection.selectionChanged -= OnSelectionChanged;
    }
}

#endif

ライセンス

MIT License となります。
商用、非商用を問わず、自由に使用、改変、再配布が可能です。

今後の予定

個人的な要件は現状で完全に満たしているので、バージョンアップの予定はありません。
より高度な機能が欲しい人は、各自でカスタマイズしていただければと思います。

Discussion