【Unity】スペースキーでGameObjectのアクティブ状態をトグル（on/off）できるようにする【エディタ拡張】

# はじめに
前は `GameObject` のon/off切り替え用に [QuickToggle](https://jeiel.itch.io/unity-quick-toggle) を使ってHierarchy上に☑を表示してたのですが、「 `GameObject` を選択してスペースキーで切り替えできれば☑のUIいらないのでは？」と思ったので作ってみました。思いの外つまづきポイントが多かったのでメモ。

ちなみに

* 非再生時も動作
* Shortcut Managerからショートカットキーを変更可能
    * スペースキー以外でトグルできる
* Git管理でチームメンバーと機能を共有可能

です。

# TL;DR

1. 適当なディレクトリを作る
    * `Assets/Scripts/Editor/ToggleActiveStateShortcut` とか
1. ↑のディレクトリに `ForInternalAccess.asmref` を追加
    * 作り方は `*.asmdef` とほぼ同じ
    * Projectウィンドウ右クリック → `Create` → `Assembly Definition Reference`
1. `ForInternalAccess.asmref` の `Assembly Definition` に `UnityEditor.UI` を指定する
1. `ForInternalAccess.asmref` と同じディレクトリに以下のスクリプトを追加する。

```ToggleActiveStateShortcut.cs
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.ShortcutManagement;
using UnityEngine;

class ToggleActiveStateShortcut
{
    [Shortcut("Hierarchy/Toggle Active State", typeof(SceneHierarchyWindow), KeyCode.Space)]
    private static void ToggleActiveState()
    {
        EditorApplication.ExecuteMenuItem("GameObject/Toggle Active State");
    }
}
#endif
```

結果：
![スクリーンショット 2021-09-21 20.36.29.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27863/162efb03-b539-2a0a-69cc-4b86d84200e0.png)


# 解説

## `*.asmref` ファイルについて
`*.asmref` の `Assembly Definition` に `UnityEditor.UI` を指定することでUnityの `internal` クラスにアクセスできるようにします。これをやらないと `typeof(SceneHierarchyWindow)` でコンパイルエラーになります。

`*.asmref` について詳しくは以下の記事を参照のこと。

* [【Unity】asmref（asmdefではない）を使うとinternalアクセスできて便利【Assembly Definition Reference Files】](https://qiita.com/su10/items/7773715c8e43058e533c)

`SceneHierarchyWindow` を参照できるなら `*.asmref` を使わないやり方でもOK。

## `ShortcutAttribute` について

### `MenuItemAttribute`, `[MenuItem(..)]` でいいのでは？
`MenuItemAttribute`, `[MenuItem(..)]` を使えばわざわざ `internal` アクセスする必要がないのですが、試しに以下のスクリプトを配置してみると、

```cs
[MenuItem("Tools/Toggle Active State _SPACE")]
private static void ToggleActive()
{
    // 処理
}
```

* `Animation/Play Animation`
* `Timeline/Play`

の２つとコンフリクトすることがわかります。

ちなみに `[MenuItem]` でスペースキーを指定したい場合は

```cs
[MenuItem("Tools/FooBar _SPACE")]
```

みたいに `_SPACE` を指定します。以下はどれもうまくいきません。

* `space`
* `Space`
* `SPACE`
* `_space`
* `_Space`

最初これでやろうとしてググってもやり方がヒットしなくて苦労しました。

### `Main Menu/GameObject/Toggle Active State` でいいのでは？
もともとUnityには選択した `GameObject` のオンオフを切り替えるショートカットがあり、Macだと `option + shift + A` が割り当てられています。

![スクリーンショット_2021-09-21_19_26_43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27863/78c32911-5f00-2d6c-dc35-8f25dbb91e58.png)

チームメンバーと共有しない場合はShortcut Managerからこのショートカットにスペースキーを割り当てればやりたいことは実現できます。コンフリクトする旨が表示されますが `Create Conflict` を選べばOK。

![スクリーンショット 2021-09-21 19.31.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27863/ac05fc8f-6c9f-9dd7-78dc-a48f62318ad4.png)

ちなみに最初に載せたコードでは最終的にはUnityにもとからある `Main Menu/GameObject/Toggle Active State` を呼び出しています。

`ShortcutAttribute`, `[Shortcut(..)]` については「AnimationとTimelineが同じスペースキーでコンフリクトしないなら方法があるはずだ」と思ってTimelineのソースコード読んでたら見つけました。

* [ShortcutAttribute](https://docs.unity3d.com/ScriptReference/ShortcutManagement.ShortcutAttribute.html)（Unity公式リファレンス）
* [【Unity】エディタ拡張でショートカットキーから関数を呼び出せるようにする「ShortcutAttribute」](https://baba-s.hatenablog.com/entry/2020/10/05/090000)（コガネブログ様）

### `[MenuItem(..)]` と `[Shortcut(..)]` の違い

#### `[MenuItem]`

* メニューに項目が追加される
* ショートカットがグローバルに定義される
    * 他で同じキーが割り当てられてるとコンフリクトする

#### `[Shortcut]`

* メニューに項目が追加されない
* ショートカットがウィンドウのローカル内に定義される（たぶん）
    * 他のウィンドウで同じキーが割り当てられててもコンフリクトしない

# 余談
Shortcutsウィンドウの上のほうに表示されてるキーボードにもいろいろ機能があって、下のコマンドをキーボードにDrag&Dropでショートカットキーを設定できたりしてビビった。思ったより高機能。