【Unity】Unity内で魚を泳がせよう UniAquariumというエディター拡張をつくってみた

# つくったもの
![1a3ce99dbad557ae2e9e3f61f785d4b7.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/704747/25419ae9-9db6-d556-5bc0-5bcb07917bb9.gif)

UnityのVisualElement下で動く水族館を作ってみました。
HtmlのCanvas風に描画できる`Painter2D`というものを使用しています。

完成品はこちら

https://github.com/Garume/UniAquarium

また、今回作成したものは[[ネタ]VSCodeで魚を飼って仕事中に癒されることにした](https://qiita.com/le-nn/items/244c638b5ecebc54868b)をほとんど参考にしています。

# 使い方
### 要件

- Unity 2022.1 or later

### インストール
1.`Window > Package Manager`からPackage Managerを開く
2.`「+」ボタン > Add package from git URL`をクリック
3. 以下を入力してインストール

```
https://github.com/Garume/UniAquarium.git?path=/Assets/UniAquarium
```

### 開き方
ツールバーから `Window > Aquarium`でアクアリウムウィンドウを開けます。

# 機能
### 餌あげ

ウィンドウ上の任意の場所をクリックすると餌をあげることができます。
魚が餌を食べに行く様子をみると癒されますね。
![6cc0eade3a232596be1dc028a2d34e78.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/704747/e1d39219-94ae-8f7e-b5a4-f2793b48abcd.gif)

### 衝撃波
ウィンドウ上の任意の場所をクリックすると餌をあげることができます。
魚が衝撃波をよけます。かわいい。
![756f394d0574921db130b7615f0cd055.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/704747/d6ed6fa0-e48a-87d4-f809-dd55b323a950.gif)

### 魚が群れを成して動く
[本家](https://qiita.com/le-nn/items/244c638b5ecebc54868b)でも説明されていますが、群れを再現するために `Boids Algorithm` というアルゴリズムを採用しています。

詳しくは以下の記事を読んでみてください。
[Wikipedia](https://en.wikipedia.org/wiki/Boids)
[水族館を作ろう:BOIDの基本](https://t-pot.com/program/101_boid1/index.html)

### カスタム設定
デフォルト状態では魚やクラゲの数が決まっていますが、これらの設定を独自にカスタマイズできます。
設定用の`Scriptable Object`を提供しており、これを使用します。

ツールバーの`Assets > Create > UniAquarium Settings` から作成することができます。
Assets以下であればどこに保存しても大丈夫です。

![a8b2fd80d8bc89cb5d27a8791be0b4bd.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/704747/77f9a35b-1146-b60e-3470-f5582c8a9f06.png)

`Fish Settings`の要素数が1のときは通常通り。
1以上の場合自動的に`Boid`が適用されて群れを成して動くようになります。

自分好みに設定したら、`Aquarium Window`をリロードしましょう。
リロードはウィンドウ右上の`3点リーダー > Reload`からできます。

![409c10bd298d7723b2fe670bcc0dc503.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/704747/50266893-5b5a-264a-0c23-4cfe86e52154.png)


### Aquarium Component

Window以外にもアクアリウムを描画できるように`VisualElement`を継承した`Aquarium Component`を用意しています。

このComponentを任意のコンテナに追加することで好きな場所にアクアリウムを表示させることができます。

試しにヒエラルキーウィンドウへ描画させてみましょう。

以下のコードをAssets以下の任意のスクリプトに記述してください。

アセンブリが切られているため、参照を追加する必要があることに注意。

```csharp
using System.Reflection;
using UniAquarium.Aquarium;
using UnityEditor;
using UnityEngine.UIElements;

[InitializeOnLoad]
public class HierarchyWindowHook
{
    private static VisualElement _rootVisualElement;

    static HierarchyWindowHook()
    {
        EditorApplication.update += Update;
    }

    private static void Update()
    {
        if (_rootVisualElement != null) return;
        
        // get SceneHierarchyWindow.
        var hierarchyWindowType = typeof(Editor).Assembly.GetType("UnityEditor.SceneHierarchyWindow");
        var hierarchyWindow = EditorWindow.GetWindow(hierarchyWindowType);

        // get VisualElement using reflection.
        if (hierarchyWindow == null) return;
        var fieldInfo = hierarchyWindowType.GetField("m_SceneHierarchy",
            BindingFlags.NonPublic | BindingFlags.Instance);
        if (fieldInfo == null) return;
        var sceneHierarchy = fieldInfo.GetValue(hierarchyWindow);
        var sceneHierarchyType = sceneHierarchy.GetType();
        var visualElementFieldInfo = sceneHierarchyType.GetField("m_EditorWindow",
            BindingFlags.NonPublic | BindingFlags.Instance);
        if (visualElementFieldInfo == null) return;

        var treeView = visualElementFieldInfo.GetValue(sceneHierarchy);
        var root = treeView as EditorWindow;

        _rootVisualElement = root.rootVisualElement;
        
        // Setting it to false disables clicks and other actions.
        var aquariumComponent = new AquariumComponent(false);
        _rootVisualElement.Add(aquariumComponent);
        aquariumComponent.Enable();
    }
}
```

保存したらヒエラルキーウィンドウを見てみましょう。
![974dc08edc6087847922347b928b8745.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/704747/be484c64-7f36-973d-216b-016e01f7e53c.gif)

いい感じに表示できていますね！

リフレクションを使用しているためやたら長いですが、重要なのは次の3行です。

```csharp
var aquariumComponent = new AquariumComponent(false);
_rootVisualElement.Add(aquariumComponent);
aquariumComponent.Enable();
```

`AquariumComponent`をインスタンス化して、`Visual Element`に追加、Enable関数を呼んでいるだけです。
インスタンス化の際にfalseを引数にとっているのは、クリック等のインタラクティブを無効化するためです。
これをしないと、本来表示されていたヒエラルキー内のアイテムを触ることができません。

内部的には、

```csharp
pickingMode = PickingMode.Ignore;
```
https://github.com/Garume/UniAquarium/blob/5efb13763d22f10a0c9a7ea32a5503a98f0cc16c/Assets/UniAquarium/Editor/Core/Components/CanvasSceneComponent.cs#L41

を指定しているだけです。

## さいごに

Painter2Dを使用してアクアリウムを作成してみました。

描画しているものはがっつり[本家](https://qiita.com/le-nn/items/244c638b5ecebc54868b)になってしまいましたが、設計面ではかなり考えさせられるものが多かったです。


よかったら⭐をいただけると嬉しいです。

https://github.com/Garume/UniAquarium/tree/master