Unityエディターそのものを拡張する　第1回タイトルバー拡張編

# はじめに
この記事は「Unityエディターそのものを拡張して作業を効率化する」から派生している全3回のうち、第1回目の記事です。
他の記事も気になる場合は[こちら](https://qiita.com/marv_nakazawaka/items/cf9129002420b4a014c9)からご覧ください。

この記事ではUnityエディターのタイトルバーを拡張して、独自の情報を表示させるための方法と、実際にリポジトリのパス名とGitでチェックアウトしているブランチ名を表示させた例を紹介します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118525/d27bc2ef-b170-8171-f5b0-272fc03a5f1c.png)

ここで紹介しているツールはUnity2021.3.9f1を使用しています。
（検証していませんが、おそらくUIElementsがエディターで導入された2019.x以降であれば動作すると思います。）

# どんな場面で役に立つか
## リポジトリのパス名の表示
チームでゲームを作る場合、差し込みタスクへの対応やコードのレビュー待ちなど、平行して作業を進めたいタイミングがしばしばあります。その時のために**ローカルに複数のリポジトリ**をクローンしている人も多いのではないでしょうか？
そうして複数のリポジトリのUnityを開いていると起こりがちな問題として、**どのUnityエディターがどのリポジトリのものなのかわからなくなる**、下手をすると**途中まで作業を進めてから目的の違うリポジトリで作業してたことに気が付く**なんていうことも起こりえます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3118525/9734dbc3-1b83-055d-02e0-087ff4722d1d.png)
プラットフォームの設定が違う場合はタイトルバーに書かれているので見分けがつきますが、そうでない場合は特徴的な作業途中などでなければ見た目での判別は不可能なので、Projectウィンドウのコンテキストメニューから「Show in Explorer」を選んで開かれたエクスプローラーのパスを確認するなどといったことをする必要があり、積み重なるとけっこうな手間になります。**パス名を表示することでタイトルバーの文字列を読めば１目でどのリポジトリのUnityエディターなのかがわかる**ようにできます。
また些細ではありますが**どのUnityエディターがどのリポジトリか覚えておかなくていいので、本来の作業により頭のメモリを割くことが出来る**ようになるのも地味にうれしいです。
## ブランチ名の表示
やがて開発が進みQA期間に入ると小粒な不具合の修正などでQA1、QA2、QA3...などといった同じ目的のリポジトリを複数用意し、それらのリポジトリもそれぞれ行っている作業が頻繁に変わるようなことも起こりえます。
こうなるともはや**パス名を表示しているだけでは、そのリポジトリが何の作業のためのものだったのかはわからなくなってしまいます。**（このチケットを対応しようと思っていたのはQA1だっけ？それとも3だったっけ？みたいなことになります。）

ブランチ名を表示する前はその度にSourceTreeに切り替え、どのリポジトリがどんなブランチにチェックアウトしていたか確認しに行っていました。
そこでブランチ名もタイトルバーに表示させることで、**そのUnityエディターが何の作業のためなのかわかりやすくなり、再び余計なことを気にせず効率よくタスクを並行して進められました。**


# 具体的な実装の紹介
それではここからは具体的な実装方法について解説していきます。
## 1. タイトルバーを拡張するためのメソッドの定義
タイトルバーはWindows APIのuser32.dllの関数を呼び出すことで文字列を取得したり、設定したりできます。
- ウィンドウの取得：[GetActiveWindow](https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-getactivewindow)
- タイトルバーの文字列を設定：[SetWindowText](https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-setwindowtexta)
- タイトルバーの文字列の長さを取得：[GetWindowTextLength](https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-getwindowtextlengtha)
- タイトルバーの文字列を取得：[GetWindowText](https://learn.microsoft.com/ja-jp/windows/win32/api/winuser/nf-winuser-getwindowtexta)
```cs:TitlebarExtensions.cs
    [InitializeOnLoad]
    public class TitlebarExtensions
    {
        [DllImport("user32.dll")]
        static extern IntPtr GetActiveWindow();

        [DllImport("user32.dll", EntryPoint = "SetWindowText")]
        static extern bool SetWindowText(System.IntPtr hwnd, System.String lpString);
        
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        static extern int GetWindowTextLength(IntPtr hWnd);

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);

        private static readonly string PATH_TEXT_PREFIX = "Path : ";
        private static readonly string BRANCH_TEXT_PREFIX = "Branch : ";

        static TitlebarExtensions()
        {
            // 起動時にパスを追加するようにする。
            EditorApplication.update -= ShowPathText;
            EditorApplication.update += ShowPathText;

            // シーンを開いたときにパスを追加するようにする。
            EditorSceneManager.sceneOpened -= ShowPathText;
            EditorSceneManager.sceneOpened += ShowPathText;
        }
```
GetWindowTextで取得したタイトルバーの文字列にまだ独自の文字列（パス名やブランチ名）が追加されていなければ、パス名やブランチ名を結合したうえで、タイトルバーに設定させるというプログラムを書いています。

## 2. パス名の表示
もっとも簡単な方法は**Application.dataPath**をそのまま表示させてしまう方法です。
マーベラスでは**ProjectSettingsでdataPathからリポジトリのルートへの相対パスを設定するようにして、リポジトリのルートのパスを表示**しています。
```cs:TitlebarExtensions.cs
        private static void ShowPathText(Scene _, OpenSceneMode __)
        {
            var window = GetActiveWindow();
            if(window == IntPtr.Zero)
                return;

            // 現在のタイトルバーを取得
            var windowTextLength = GetWindowTextLength(window);
            var stringBuilder = new StringBuilder(windowTextLength);
            GetWindowText(window, stringBuilder, stringBuilder.Capacity);            
            var titleBarText = stringBuilder.ToString(); // これに現在のタイトルバーのテキストが入っている。

            // 現在のタイトルバーのテキストに独自の文字列があれば取り除く
            if(titleBarText.Contains(PATH_TEXT_PREFIX))
            {
                titleBarText = titleBarText.Substring(0, titleBarText.IndexOf(PATH_TEXT_PREFIX));
            }

            // パスの取得
            var dataPath = Application.dataPath;

            // タイトルバーに設定するテキストを組み立てるStringBuilder
            var titleBarTextBuilder = new StringBuilder();
            titleBarTextBuilder.Append(titleBarText);

            // タイトルバーにパス名を追加
            titleBarTextBuilder.Append(titleBarText);

            // 1.2 パス名を追加
            titleBarTextBuilder.Append(PATH_TEXT_PREFIX);
            titleBarTextBuilder.Append(dataPath);
```

## 3. ブランチ名の表示
ブランチ名はリポジトリのフォルダ直下に「.git」というフォルダがあるので、そのなかにある「HEAD」というファイルの中に書かれています。
例えば「main」ブランチにチェックアウトしている場合は次の通りです。
```:HEAD
ref: refs/heads/main
```
なのでこのファイルのrefs/heads/以下の文字列を追加します。
```cs:TitlebarExtensions.cs
    private static void ShowPathText(Scene _, OpenSceneMode __)
    {
        // 1.2 パス名を追加
        
        // 1.3 ブランチ名を追加
        var branchName = GetBranchName(gitPath); // gitPathはProjectSettingsで設定された相対パスから求める。
        titleBarTextBuilder.Append(BRANCH_TEXT_PREFIX);
        titleBarTextBuilder.Append(branchName);

        // タイトルバーにテキストを設定
        SetWindowText(window, titleBarTextBuilder.ToString());
    }
    
    /// <param name="gitPath">ProjectSettingsで設定された「.git」フォルダのパス</param>
    /// <returns>現在のブランチ名</returns>
    private static string GetBranchName(string gitPath)
    {
        var headPath = System.IO.Path.Combine(gitPath, "HEAD");

        using (var reader = new System.IO.StreamReader(headPath))
        {
            var refs = reader.ReadLine();

            // refs/heads以降の文字列だけ抜き出す。
            return refs.StartsWith("ref: refs/heads/") ? refs.Substring("ref: refs/heads/".Length) : "No Branch";
        }
    }
```
以上でタイトルバーにパス名やブランチ名を表示可能です。