【Unity】Collider のアタッチされた Prefab をリストアップするツール

はじめに

画像のように Collider のアタッチされた Prefab をリストアップします。
子 GameObject にアタッチされている場合は, Root の GameObject からのパスを表示します。

ソースコード

public class ColliderListWindow : ComponentListWindow<Collider>
{
    [MenuItem("EditorWindow/ColliderListWindow")]
    private static void Init()
    {
        var window = GetWindow(typeof(ColliderListWindow));
        window.titleContent = new GUIContent(nameof(ColliderListWindow));
        window.Show();
        window.minSize = new Vector2(330, 400);
    }
}

public class ComponentListWindow<T> : EditorWindow where T : Component
{
    private SingleTreeView m_TreeView;
    private TreeViewState m_TreeViewState;
    private List<UnityObjectModel> m_ModelList = new();

    private void OnEnable()
    {
        m_TreeViewState = new TreeViewState();
        m_TreeView = new SingleTreeView(m_TreeViewState);
        if (m_ModelList.Count == 0)
        {
            m_ModelList.Add(UnityObjectModelFactory.instance.CreateEmpty());
        }

        m_TreeView.Setup(m_ModelList);
    }

    private void OnGUI()
    {
        if (GUILayout.Button("実行"))
        {
            m_ModelList.Clear();

            var paths = AssetDatabase.FindAssets("a: assets t:Prefab")
                .Select(guid => AssetDatabase.GUIDToAssetPath(guid))
                .ToList();

            for (int i = 0; i < paths.Count(); i++)
            {
                var path = paths[i];
                var asset = AssetDatabase.LoadAssetAtPath<GameObject>(path);
                foreach (var child in asset.GetComponentsInChildren<T>())
                {
                    var name = GetPath(child.transform);
                    var model = UnityObjectModelFactory.instance.CreateModel(name, child);
                    m_ModelList.Add(model);
                }
            }

            m_TreeView.Setup(m_ModelList);
        }

        var rect = EditorGUILayout.GetControlRect(false, GUILayout.ExpandHeight(true));
        m_TreeView.OnGUI(rect);
    }

    public static string GetPath(Transform transform)
    {
        var result = transform.name;
        var parent = transform.parent;
        while (parent != null)
        {
            result = parent.name + "/" + result;
            parent = parent.parent;
        }

        return result;
    }
}

public class SingleTreeView : TreeView
{
    private List<UnityObjectModel> m_ModelList;

    public SingleTreeView(TreeViewState treeViewState) : base(treeViewState)
    {
        showAlternatingRowBackgrounds = true;
    }

    public void Setup(List<UnityObjectModel> modelList)
    {
        m_ModelList = modelList;
        Reload();
    }

    protected override TreeViewItem BuildRoot()
    {
        var root = new TreeViewItem { id = 0, depth = -1, displayName = "Root" };
        foreach (var model in m_ModelList)
        {
            var baseItem = new TreeViewItem { id = model.Id, displayName = model.Name, icon = model.Icon };
            root.AddChild(baseItem);
        }

        SetupDepthsFromParentsAndChildren(root);
        return root;
    }
}

public class UnityObjectModelFactory : ScriptableSingleton<UnityObjectModelFactory>
{
    [SerializeField]
    private int m_IdCounter;

    public UnityObjectModel CreateModel(string name, Object unityObject)
    {
        m_IdCounter++;
        var model = new UnityObjectModel(m_IdCounter, name, unityObject);
        return model;
    }

    public UnityObjectModel CreateEmpty()
    {
        m_IdCounter++;
        return new UnityObjectModel(m_IdCounter, "なし", null);
    }
}

public class UnityObjectModel
{
    private int m_Id;
    private string m_Name;
    private List<UnityObjectModel> m_Children = new();
    private Object m_UnityObject;
    private Texture2D m_Icon;

    public int Id => m_Id;
    public string Name => m_Name;
    public Object UnityObject => m_UnityObject;
    public Texture2D Icon => m_Icon;
    public List<UnityObjectModel> Children => m_Children;

    public UnityObjectModel(int id, string name, Object unityObject)
    {
        m_Id = id;
        m_Name = name;
        m_UnityObject = unityObject;
        if (m_UnityObject != null)
        {
            m_Icon = (Texture2D)EditorGUIUtility.ObjectContent(m_UnityObject, m_UnityObject.GetType()).image;
        }
    }

    public void AddChild(UnityObjectModel singleTreeViewModel) => m_Children.Add(singleTreeViewModel);
}

【Unity】AssetDatabase.FindAssets で使用できる検索フィルタ

はじめに

AssetDatabase.FindAssets() を使用することで,
Project に存在するアセットのGUIDをリスト取得することができます。

この Api は第一引数に string で検索フィルタを指定し,
一覧取得する対象アセットを絞りこむことが可能です。
docs.unity3d.com

使用可能なシンタックス

第一引数に渡す検索フィルタは t:ScriptableObject のように
指定することで ScriptableObject だけ取得, のような型指定で絞り込むこともできます。

t: 以外は以下のものがあるようでした。

構文 説明
t:type t:Texture2D は Texture2D オブジェクトを表示する
l:assetlabel l:architecture は AssetLabel 'architecture' を持つアセットを表示する
ref[:id]:path ref:1234 は instanceID 1234 のオブジェクトを参照するオブジェクトを表示する
v:versionState v:modified はローカルで変更されたオブジェクトを表示する
s:softLockState s:inprogress は(あなたを除く)誰によっても変更されたオブジェクトを表示する
a:area a:all は全てのアセットを検索し、a:assets はassetsフォルダのみを検索し、a:packages はpackagesフォルダのみを検索する
glob:path glob:Assets/**/*.{png|PNG} は.pngまたは.PNGで終わる名前を持つサブフォルダ内のオブジェクトを表示する

以下に記載されています。 github.com

「ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本」を読んだ

ドメイン駆動設計入門 ボトムアップでわかる! ドメイン駆動設計の基本」を読みました。
Kindle版がリフロー型だったのでKindleで購入しました。
https://www.amazon.co.jp/dp/479815072X?ref_=cm_sw_r_cp_ud_dp_ZM13Z8NF1D2JCZG1CFS3www.amazon.co.jp

【Git】コンフリクト状態のままコミットされるのを防ぐHook

概要

git の pre-commit hook で,
コンフリクトマーカーが含まれているものを検出するスクリプトの紹介です。
gist.github.com

使い方

.git/hooks/ 以下に リンク先のファイルを pre-commit という名前で配置します。
コンフリクト未解決のファイルに対してコミットを
行おうとすると弾かれるようになります。

$ git commit

Unresolved merge conflicts in these files:
conflict.txt

【git】remrege-diff オプション とは

Git 2.36 で実装されたオプションで,
どのようにコンフリクトを解決したのかをより簡単に見るためのオプションのようです. github.blog

実際にコンフリクト解決したマージコミットに対して,
git show --remerge-diff を実行したところ以下のような出力になります.
コンフリクトマーカ(の削除差分)が表示され,
どちらを優先解決したかが確認できます.

$ git show --remerge-diff ebefc98
commit ebefc985fe78411e6339c0db7f5b1d3036730f0a (HEAD -> master)
Merge: 3cada90 7f25d21
Author: 
Date:   Sun Feb 4 18:52:25 2024 +0900

    Merge branch 'develop'

diff --git a/test.txt b/test.txt
remerge CONFLICT (content): Merge conflict in test.txt
index 2968fae..add24f2 100644
--- a/test.txt
+++ b/test.txt
@@ -1,7 +1,3 @@
 katsuragi
-<<<<<<< 3cada90 (冬月→式波)
-shikinami
-=======
 ayanami
->>>>>>> 7f25d21 (冬月→綾波)
 ikari

【WSL2】zsh セットアップ

Ubuntu バージョン

$ cat /etc/os-release
PRETTY_NAME="Ubuntu 22.04.2 LTS"
NAME="Ubuntu"
VERSION_ID="22.04"
VERSION="22.04.2 LTS (Jammy Jellyfish)"
VERSION_CODENAME=jammy
ID=ubuntu
ID_LIKE=debian
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
UBUNTU_CODENAME=jammy

zsh をインストール

現在のシェルを確認

$ echo $SHELL

インストール済みか確認

$ zsh --version
Command 'zsh' not found, but can be installed with:

インストール

$ sudo apt update && sudo apt install zsh

zsh の場所を確認

$ which zsh
/usr/bin/zsh

デフォルトのシェルを変更

$ chsh -s /usr/bin/zsh

コンソールを立ち上げ直して適用されていることを確認

$ echo $SHELL
/usr/bin/zsh

OhMyZsh をインストール

$ sh -c "$(curl -fsSL https://raw.github.com/ohmyzsh/ohmyzsh/master/tools/install.sh)"

git のインストール

後続の手順でgit が必要なのでインストールします。
新しいバージョンを入れておきたかったのでgit公式の手順に従ってPPAを使用します。
(PPA=Ubuntu非公式レポジトリ)
git-scm.com

$ sudo add-apt-repository ppa:git-core/ppa
$ sudo apt update
$ apt install git

zsh-autocomplete をインストール

zsh-autocomplete は zsh の自動補完機能を提供するプラグインです。
リンク先の Installing & Updating に従ってインストールをします。 github.com

~/Repos を作成して cd します。

$ mkdir ~/Repos && cd $_

リポジトリをクローン

$ git clone --depth 1 -- https://github.com/marlonrichert/zsh-autocomplete.git

ls -a ~/.zshrc でファイルが無ければ ~/.zshrc を作成する

$ touch ~/.zshrc

vim で.zshrcを開き source ~/Repos/zsh-autocomplete/zsh-autocomplete.plugin.zsh を追記

$ vim ~/.zshrc

シェルをリロード

$ exec $SHELL -l

「良いコード/悪いコードで学ぶ設計入門」を読んだ

Amazon.co.jp: 良いコード/悪いコードで学ぶ設計入門―保守しやすい 成長し続けるコードの書き方 eBook : 仙塲 大也: Kindleストア

メモ

  • インスタンス変数の上書きは理解を難しくする
  • 設計パターン
    • 完全コンストラク
    • 値オブジェクト
    • ストラテジ
    • ポリシー
    • ファーストクラスコレクション
      • コレクション型インスタンスを不正状態から防御できる
      • コレクションの操作処理をクラスに集約することができる
    • スプラウトクラス
  • static メソッドは低凝集な構造になりやすい
  • ファクトリメソッド
    • 初期化ロジックの分散を防ぐことができる
    • インスタンスの利用側クラスで, インスタンス生成ロジックが多くなり, 生成以外のロジックが希薄なることを防ぐことも可能
  • 多すぎる引数
    • プリミティブ型執着
      • 関数を汎用化しようと意識し過ぎると陥りがちなイメージ
  • 単一責任の原則
    • 同じ条件式を複数書かず、一箇所にまとめる
  • switch 文を書く前に, interface で switch を書かずに実現できないか検討する
    • inteface 実装クラスの型を調べて分岐するのであれば意味がない
      • リスコフの置換原則に違反している
  • フラグ引数
    • メソッドの機能を切り替える bool 型引数のこと
      • 関数利用者がフラグにより何が起こるか内部ロジックを見る必要が出るケースが発生する
  • 密結合
    • 責務が考慮されていないと密結合が発生しやすくなり, デバッグや変更が難しくなる
    • 似ているロジックであっても概念が違えばDRYにするべきではない
    • 継承
  • private メソッドの多いクラスは単一責任ではなく, 多くの責任を持っている可能性が高い
  • 疎結合で高凝集を意識する
    • 高凝集だけを意図して関係していそうなロジックを一箇所にまとめ上げたものの結果として密結合に陥っているケース
  • トランザクションスクリプトパターン
    • データクラスとデータを処理するクラスで分けている場合に頻繁に実装される
    • 低凝集密結合
  • null 例外や null チェックを避けるために null は返さず, 渡さないようにすることが大事