【Unity】レイヤーを実質無限に使えるようにするライブラリの紹介【Layer2】

# はじめに
この記事は [Unity Advent Calendar 2022 (その1)](https://qiita.com/advent-calendar/2022/unity) 16日目の記事になります。

前日の記事は @anidomanta さんの 「[Tilemapを使ってキャラクターを歩かせたりジャンプさせたりする](https://qiita.com/anidomanta/items/9bfe35a03a24e2e14d5b)」 でした。

本稿では自分が個人で開発している「Unityのレイヤーを実質無限に使えるようにする」ライブラリを紹介します。

https://github.com/su10/Layer2

まだ開発途中の `v0.1.0` で今後破壊的な変更あり＆実践未投入なので、不具合など見つけたらTwitter（[@su10_dev](https://twitter.com/su10_dev)）かGitHubリポジトリのIssueまでお願いします🙏

# モチベーション
Unityにはレイヤーの機能があり、レイヤーを使用することで以下のようなことができます。

* 当たり判定の設定・管理
* オブジェクトを描画する/しないの切り替え（ `Camera` の設定）およびグループ化
* ポストプロセシングを適用するオブジェクトの切り替えおよびグループ化

少なくともUnity2021.3で利用可能なレイヤーは最大32個で、そのうち5個（ `Default` , `TransparentFX` , `Ignore Raycast` , `Water` , `UI` ）がUnityによって予約・使用されています。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27863/7cf736dc-1499-a3dc-f8df-d318172e705f.png" alt="スクリーンショット_2022_12_15_20_20.png" width="300">

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27863/b59f9a7a-c35d-ffc2-0403-083e69abb558.png" alt="スクリーンショット_2022_12_15_20_25.png" width="300">

*Unityのレイヤー名設定画面とPhysicsのコリジョン設定*

　
この制限は小規模な開発であれば問題ありませんが、中規模以上の開発やミニゲーム集のようなゲームでレイヤーを使おうとしたときに大きな障害となります。

例えば「6~20は演出（デザイナー）、21〜30は当たり判定（エンジニア）で使用する」みたいなルールで辛い経験をした結果、「今後はレイヤーを使用しない」という判断をするのも割とよくあることだと思います。

また、なるべくレイヤーを使わないために使えるAPIとして [Physics.IgnoreCollision()](https://docs.unity3d.com/jp/current/ScriptReference/Physics.IgnoreCollision.html) というものもありますが、いちいちすべてのコリジョンに対して呼び出すのは現実的ではありません（参考：[レイヤーを使わずに衝突判定をなくす方法](https://qiita.com/su10/items/48bf301bb1c6446ef3b8)）。

　
Layer2は

* 個別のアセットとしてレイヤー設定（レイヤー名・コリジョンマトリクス）をまとめる
* コンテキストに応じて複数のレイヤー設定を切り替える

ことで32個の上限を取り払い、レイヤーを実質無限に使えるようにします。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27863/a382187f-6525-7be3-d212-5144e6562ac0.png" alt="スクリーンショット 2022-12-15 20.30.24.png" width="400">

*Layer2のレイヤー設定アセット*

　
イメージとしてはUnityのレイヤー設定の上に一つ間接化のレイヤーを敷く感じです。

レイヤー設定を間接化することは単にレイヤー数の制限を取り払うだけでなく、設定への依存をコンテキスト内に局所化することでレイヤーの負債化・硬直化を緩和する効果があります。

# 簡単な実装解説
コリジョンマトリクスの設定切り替えは通常のUnityのAPI呼び出しになっていて、

* 3Dの場合： [Physics.IgnoreLayerCollision()](https://docs.unity3d.com/ScriptReference/Physics.IgnoreLayerCollision.html) をコール
* 2Dの場合： [Physics2D.IgnoreLayerCollision()](https://docs.unity3d.com/ScriptReference/Physics2D.IgnoreLayerCollision.html) をコール

しているだけです。

レイヤー名は動的に変更するAPIが提供されていないので、レイヤー名を参照したい場合は `LayerMask` の代わりに `Layer2Mask` を使用する必要があります。

# インストール
* 対応バージョン：Unity2021.3以降

0. Package Managerを開く
0. [＋▼] ボタンを押して `Add package from git URL...` を選択
0. 以下を入力してEnter
    * https://github.com/su10/Layer2.git#upm
    * 個別のバージョンを指定する場合は https://github.com/su10/Layer2.git#upm/v0.1.0 のようにする

# 使い方
## 設定ファイルを作成する
デフォルトの状態だと設定ファイルを作成できないので、必要に応じて `Samples` の `Create Asset Menu` をインポートします。

![スクリーンショット_2022_12_15_21_15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27863/bbf2e07c-0e92-13c9-37c9-a0e78c2b0643.png)

インポートが完了したら、

　`Assets` → `Create` → `Layer2` → `Layer Settings`

で設定ファイルが生成されます。

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27863/a382187f-6525-7be3-d212-5144e6562ac0.png" alt="スクリーンショット 2022-12-15 20.30.24.png" width="400">

*Layer2のレイヤー設定アセット*


<details><summary>ちなみに</summary>

 `Create Asset Menu` でインポートされるものは以下の小さなスクリプト一つです。

```LayerSettingAsset.cs
using UnityEngine;

namespace Jagapippi.Layer2
{
    [CreateAssetMenu(menuName = "Layer2/Layer Setting")]
    public partial class LayerSettingAsset
    {
    }
}
```

設定アセットを作成するメニューのパスを変更したい場合は `"Layer2/Layer Setting"` のところを適宜変更してください。

</details>

---

レイヤーは設定アセット一つにつき32個まで定義できます。

デフォルトの状態だと自身で名前を決められるのは6~31までの25個までですが、編集不可になっている `Builtin Layer` はInspectorをDebugモードにすると強制的に変更できます（使用は自己責任で）。

Collision Matrixの設定は3D/2D両方対応していますが、UnityのAPIとの兼ね合いで一つの設定に3Dと2Dの設定がどちらも含まれる形になっています。

シーンやミニゲーム毎といった適当なコンテキストにつき一つの設定を作って使うイメージです。

レイヤーは並び替え可能ですが、並び替えてもシーンやプレハブにシリアライズされているレイヤーの値は変更されないので、すでに使用されているレイヤーの並び順を変更するともろもろ壊れてしまうことに注意してください。

## レイヤー設定を切り替える

作成したレイヤー設定は `LayerSettingSelection` クラスから適用できます。

現状 `LayerSettingSelection` クラスは `internal` なので、Samplesの `LayerManager` プレハブを使用することを強くおすすめします。

また、Unityエディタ上では設定ファイルを選択したときのInspectorのヘッダーから以下の操作が行えます。

* 現在適用されているレイヤー設定の確認
* 設定を選択（エディタ用，非再生時のみ）
* 設定を適用
* 設定をコピー/ペースト
* 設定をProject Settingsからロード

![スクリーンショット_2022_12_15_21_47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27863/2a3daeed-0990-68f4-8982-4de60e9500b2.png)

ボタンのツールチップにも説明が表示されますが、**非再生時**にレイヤー設定を適用（ `Apply` ボタン）すると以下のファイルに変更が走ります。

* DynamicsManager.asset
* Physics2DSettings.asset
* TagManager.asset

レイヤー設定を切り替えるたびにファイルが変更されてほしくない場合は選択（ `Select` ボタン）を使用してください。

選択/適用されているレイヤー設定は `GameObject` を選択したときのInspectorのヘッダーのレイヤー表示に反映されます（Layer2のドロップダウンが追加される）。

![スクリーンショット_2022_12_15_21_50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27863/86bd144a-04a3-2ebc-2918-7d3f13e2d46f.png)

## その他の機能
### デフォルトのGUIシステムをUI Toolkitに変更
2022以前のUnityエディタにLayer2を導入すると、Inspectorの表示に使われるデフォルトのGUIシステムがIMGUIからUI Toolkitに切り替わります。

これによって既存のカスタムエディタに問題が起きた場合、Scripting Define Symbolsに `LAYER2_DISABLE_DEFAULT_CUSTOM_EDITOR` を追加することでデフォルトのGUIシステムをIMGUIに戻すことができます。

その場合はLayer2のクラス・コンポーネントが正しく表示されなくなるので、自分でGUIの描画処理を書く必要があります。

Unity2022からはそもそもデフォルトでUI Toolkitが使用されるので、Unity2022を使用していれば問題は起きないはずです。

### `LayerMask` から `Layer2Mask` への移行
Layer2を導入した場合、 `LayerMask` は使わずに `Layer2Mask` を使う必要があります。

プロジェクトの途中でLayer2を導入した場合は `LayerMask` 記述部分を機械的に `Layer2Mask` で置き換えればOKです。

### `Camera` のInspector表示の拡張
`Camera` コンポーネントの最上部にLayer2用のレイヤー選択ドロップボックスが追加されます。

![スクリーンショット_2022_12_15_22_36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/27863/457009c0-e322-ce1e-c5c1-0ade704e4548.png)

既存のカスタムエディタとコンフリクトする場合、Scripting Define Symbolsに `LAYER2_DISABLE_CAMERA_EDITOR_OVERRIDE` を追加することで処理を無効化できます。

# TODO
* サンプルを充実させる
* Harmonyを導入してUIをUnityエディタと統合する
    * `GameObject` 選択時のヘッダーを簡素化
    * `Camera` のレイヤー表示を簡素化
    * `Layer2Mask` を `static` 化
* 配布形態の追加
    * OpenUPM対応
    * unitypackage生成＆リリースページ作成
* GitHub Actionsでリリースの自動化

# さいごに
私事で恐縮ですが、最近父親になりました。

https://twitter.com/su10_dev/status/1589051567432687618?s=20&t=LFGC8edmeDIeXBF2Y-TEIA

育児って難しいですね。エンジニア的にいろいろ自動化・楽していきたいです。

　
◆　◆　◆

　
明日の [Unity Advent Calendar 2022](https://qiita.com/advent-calendar/2022/unity) は @yahagi_day さんの 「[UnityをGithubActionsでビルドする]()」 です。