Odin Validatorのススメ

Unityでのゲーム開発では、多くのGameObjectやPrefabそしてAssetが必要で、それらは他のGameObjectやAssetへの参照を持っています。その参照関係を正しく管理・運用するのは大規模なゲーム・長期間のプロジェクトではとても難しいです。「間違って使っているAssetを削除してしまい、それを参照している部分が原因で不具合が発生した」という経験はありませんか。このような不具合を、どうやったら防げるでしょうか？

自分のおすすめは、`Odin Validator`を用いたプロジェクト全体の自動検証です。`Odi Validator`を用いれば「ここは必ず何かしらのAssetを参照しないといけないと設定する。もし参照していない場合は、エラーを表示する」という設定が実現できます。「間違って使っているAssetを削除してしまい、それを参照している部分が原因で不具合が発生した」という不具合を検知し、事前に防ぐことができます。

この投稿では、`Odin Validator`を用いた、プロジェクトの自動検証を紹介します。

# 参照しているやつがなくなってしまった！

Unityでのゲーム開発では、多くのGameObjectやPrefabそしてAssetが必要で、それらは他のGameObjectやAssetへの参照を持っています。その参照関係を正しく管理・運用するのはとても難しいです。特に、大規模なゲームで長期間のプロジェクトとなれば、なおさらでしょう。

たとえば、次のようなScriptableObjectのクラスがあるとします。


```csharp
using UnityEngine;

[CreateAssetMenu]
public class Enemy : ScriptableObject
{
    [SerializeField] private string enemyName;
    public string EnemyName => enemyName;

    [SerializeField] private Sprite sprite;
    public Sprite Sprite => sprite;

    [SerializeField] private GameObject model;
    public GameObject Model => model;

    // 略
}
```

次のようにEnemy000というScriptableObjectを作成し、Inspectorから参照を設定したとしましょう。

![odin_scriptable_object_enemy_normal.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4125/50530a7b-abea-1323-dcfe-58186c277f60.png)

さて、プロジェクトがすすみ、Assetも多くなってきて管理が難しくなってきたとします。本当は必要だったAsset（EnemySprite0）を誰かがうっかりプロジェクトから消してしまいました。その場合、次のようにSpriteの参照が「None」という状態になってしまいます。

![odin_scriptable_object_enemy_none.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4125/6493d655-c72e-899b-4f11-ea594b2faf4e.png)


もしこの状態で、ビルド・実行したらおそらく表示すべきSpriteが表示されない不具合が発生してしまいます。

このような「間違って使っているAssetを削除してしまい、それを参照している部分が原因で不具合が発生した」という不具合を経験した方も多いのではないでしょうか？ここで紹介した例は「Spriteが表示されない」でしたが、ゲームが進行不可能になったり、課金関連の不具合が発生したりと、もっと大きは不具合が発生してしまうことも考えられます。

このような不具合に、対応するために自分は、`Odin Validator`を用いたプロジェクト全体の自動検証を導入することをおすすめします。

# Odin Validatorとは？

`Odin Validator`の前に、まず`Odin`を紹介します。


`Odin`（`Odin - Inspector and Serializer`）はUnity向けのツール・ユーティリティカテゴリのライブラリです。非常に強力で豊富な機能を持っている人気のライブラリです。主な機能は次のとおりです。Inspectorの強化をはじめ、エディター拡張のユーティリティの提供します。`Odin - Inspector and Serializer`は、[Asset Store](https://assetstore.unity.com/packages/tools/utilities/odin-inspector-and-serializer-89041?locale=ja-JP)・もしくは[Odinの公式ページ](https://odininspector.com/)から購入が可能です。

![odin_asset_store_screenshot.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4125/16e90281-8df9-de7a-0544-92ac6ee80b34.png)

自分の、`Odin`のおすすめ機能は「Required属性」です。Required属性がついたSerialize対象のフィールドは、「必ず参照や値を設定する必要がある」という条件が追加されます。もし参照や値を設定していない場合、次のようにInspectorに設定されていないことをわかりやすく表示してくれます。

![odin_scriptable_object_enemy_required_error.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4125/a9767b1f-aa4e-e938-e7bc-81e6422bcfab.png)

# Odinを使って制約や条件を追加してみよう

`Odin - Inspector and Serializer`では、多くの属性とそれに対応するValidatorを提供しています。これらの属性が付与されたフィールドは、ValidatorによりInspectorからの入力に条件や制約を加えることができます。

`Odin Project Validator`では、これらの属性を用いて付与された条件や制約を検証できます。この節では、`Odin - Inspector and Serializer`で提供されている属性とValidatorの使い方の一部を紹介します。

## Required

Required属性をつけることで、参照の設定・値の入力を必須にできます。

次のようなScriptableObjectのクラスがあります。

```csharp
using UnityEngine;
using Sirenix.OdinInspector;

[CreateAssetMenu]
public class ExampleRequire : ScriptableObject
{
    // 本当はプロパティを準備したほうがいいが、サンプルのためpublicフィールド
    [Required] public string stringValue;
    [Required] public GameObject gameObjectReference;
    [Required] public Transform transformReference;
    
    [Required] public int intValue;
    [Required] public double doubleValue;
}
```

このScriptableObjectのInspectorは次のようになります。stringValue、gameObjectReference、transformReferenceの上部に「stringValue is Required」などのエラーメッセージが表示されていることに注目してください。Required属性を付与することで、そのフィールドの参照・値を設定していない箇所をエラー表示できます。

![odin_example_require_error.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4125/906b36eb-75d1-4446-4f17-b55cceb6f060.png)

stringValueなどに参照・値を設定すると次のようにエラー表示が解消されます。

![odin_example_require_valid.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4125/680ed82f-0ef3-cdb9-611a-bbacca6c1f4d.png)

intやdoubleなどのフィールドには、Requiredをつけてもエラーメッセージが表示されないことに注意してください。

## ValidateInput

ValidateInput属性をつけることで、指定したstaticメソッドを用いて検証できます。たとえば、次のコードは「List<GameObject>なtargetAssetsが要素を1つ以上持っているか」を検証します。

```csharp
using System.Collections.Generic;
using Sirenix.OdinInspector;
using UnityEngine;

[CreateAssetMenu]
public class ExampleValidateInput : ScriptableObject
{
    [Required] [ValidateInput(nameof(ValidateNotEmpty))]
    public List<GameObject> targetAssets;

    private static bool ValidateNotEmpty(
        List<GameObject> targetAssets,
        ref string message
    )
    {
        if (targetAssets.Count == 0)
        {
            message = "List is empty.";
            return false;
        }

        return true;
    }
}
```

このScriptableObjectのInspectorは次のようになります。targetAssetsは要素を1つも持っていないので、上部に「List is empty.」というエラーメッセージが表示されています。

![odin_example_validate_input_error.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4125/fbab36e8-0f29-42d5-6934-df1cda91eda4.png)


targetAssetsが要素を1つ以上もつようになると、エラーメッセージが消えます。

![odin_example_validate_input_valid.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4125/f5942e5d-14b2-4366-351e-f7abf632d26e.png)


このようにValidateInputを使うと、自分で指定したstaticメソッドにより、独自の検証を行うことができます。

## AssetsOnly と SceneObjectsOnly と ChildGameObjectsOnly

AssetsOnly、SceneObjectsOnly、ChildGameObjectsOnly属性をつけることで、参照の設定を次のように条件づけできます。

 * AssetsOnly・・・プロジェクトのAsset限定
 * SceneObjectsOnly・・・シーン上のGameObjectやComponent限定
 * ChildGameObjectsOnly・・・シーン上の自分の子GameObject限定

AssetsOnlyを指定しているフィールドに、シーン上のGameObjectやComponentは参照設定できません。SceneObjectsOnlyを指定しているフィールドに、プロジェクト上のAssetは参照設定できません。ChildGameObjectsOnlyはIncludeSelfという引数があり、自身も許可するかどうかを指定できます。

次のコードのtargetは、プロジェクト中のPrefabもシーン上のGameObjectも、どちらも参照設定できてしまいます。本来はどちらかしか設定できるべきではありません。

```csharp
using UnityEngine;

public class Example : MonoBehaviour
{

    // プロジェクト中のPrefabもシーン上のGameObjectも
    // どちらも参照設定できてしまう
    [SerializeField] GameObject target;

    // プロジェクト中のAsset（Prefab）を指定すべき場合は、AssetsOnlyを
    // [SerializeField, AssetsOnly] GameObject target;

    // シーン上のGameObjectを指定すべき場合はSceneObjectsOnlyを
    // [SerializeField, AssetsOnly] GameObject target;
}
```

プロジェクト中のAsset（Prefab）を指定すべき場合はAssetsOnlyを、シーン上のGameObjectを指定すべき場合はSceneObjectsOnlyをつけることをおすすめします。

# Odin Validatorを使ってみよう

RequiredやValidateInput、AssetsOnlyなど`Odin - Inspector and Serializer`の属性を使うことで、入力に条件や制約を加えそれを満たさなかった場合、Inspectorにエラー表示できます。しかしそのエラー表示に気がつかなかったとしたら、結局不具合が発生してしまいます。しかし、せっかくのわかりやすいエラー表示も、みのがしてしまったら意味がありませんね。そこで`Odin Validator`の出番です。

`Odin Validator`は、`Odin - Inspector and Serializer`のAddonです。`Odin Validator`を用いると、Required属性などの条件を満たしていないAsestやPrefab、GameObjectが存在しないか検証し、その結果を次のようにわかりやすく確認できます。`Odin Validator`は、公式サイトから購入することができます。

![odin_project_validator_error_example.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4125/c394283b-bc90-5673-ea40-9e7b4c18d965.png)

検証する範囲も、「今開いてるシーン」や「プロジェクト全体」など指定できます。また、その検証のタイミングをゲームプレイ実行時、ビルド実行時など指定できます。`Odin Validator`を用いることで、「間違って使っているAssetを削除してしまい、それを参照している部分がきっかけでシーンが壊れてしまった」という不具合を回避できます。

`Odin Validator`はOdin公式ページから購入が可能です。この投稿の執筆時点では、2019年5月28日より前にOdinを購入したユーザーは`Odi Validator`を無料でダウンロードできるようです。価格・購入については、最新の公式情報を参照してください。


変更を加えるたびに、いちいちプロジェクトにあるSceneやPrefab、ScriptableObjectすべてにエラー表示がないかを確認することは、現実的ではありません。Requiredなどを使っていたとしても、「使っていないと思ったAssetを削除したら、最近まったく更新していないシーンで使っていて、それが原因で不具合が発生してしまった」ということは起きてしまいます。

先に説明した通り、このような不具合は、`Odin Validator`使うことで回避できます。
@<b>{Odin Project Validator}を使えば、RequiredやValidateInput、AssetsOnlyなどの属性がついている部分を自動で検証し、満たしていない箇所をわかりやすく表示してくれます。
いちいちプロジェクト全体を目視で確認する必要はありません。

`Odin`と`Odin Validator`をプロジェクトに導入し、メニューの「Tools > Odin Project Validator」を選択すると次のようなウィンドウが開きます。

![odin_project_validator_top.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4125/34a4cdab-9ff5-40f1-d2db-baa62b06eebe.png)

一番上の「Scan Entire Project」を押すと次のような状態に遷移します。

![odin_project_validator_entire_project.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4125/75de44a5-def0-9444-a73c-e799030f2129.png)

このウィンドウの右上にある「Run Scan Entire Project」というボタンを押下します。もしプロジェクトにRequiredやValidateInput、AssetsOnlyを満たさないものがあった場合、次のようにそれを一覧表示できます。

![odin_project_validator_result_error.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4125/0c41f8a8-3896-a748-ec2d-dbf7b4e70167.png)

検証した内容を一覧できます。また、条件を満たしていなかったAssetやGameObjectをこのウィンドウから直接編集できます。

もしプロジェクトにRequiredやValidateInput、AssetsOnlyを満たさないものがなければ、次のようになります。

![odin_project_validator_result_valid.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4125/007c5a83-9288-385e-628e-7db3cd3fb211.png)

「Scan Entire Project」はプロジェクト全体を検証します。`Odin Project Validator`では、これ以外にも次のような設定も提供しています。

|名称                            |対象                                    |
|-------------------------------|----------------------------------------|
|Scan Entire Project            | プロジェクトにあるすべてのScene・Assetを検証する |
|Scan All Assets                | プロジェクトにあるすべてのAssetを検証する       |
|Scan All Scenes                | プロジェクトにあるすべてのSceneを検証する       |
|Scan Open Scenes	        | 現在開いているSceneを検証する               |
|Scan Scenes From Build Options | ビルド対象に設定されているSceneを検証する      |

自分で設定を作ることも、既存の設定を変更することもできます。必要に応じて自分のプロジェクト運用にあった設定を試してみてください。


# Automate Validation

`Odin Validator`でプロジェクトの検証を行えば、参照や値の設定の間違いに気が付くことができます。しかし、ビルドする前に`Odin Validator`での検証を忘れてしまったら、結局不具合が発生してしまう可能性があります。`Odin Validator`による検証を忘れないために、Automate Validationを設定することをお勧めします。

メニューの「Tools > Odin Project Validator」からウィンドウを開き、右上部にある「Automate Validator」ボタンを押すと次のようなAutomate Validationウィンドウが開きます。

![odin_automate_validation.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4125/6cea5bd8-9d70-7aec-e5ed-c93e1b4d7cdd.png)

ウィンドウ内のチェックボックスにチェックをつけて、Automate Validationを有効にするとそれぞれのタイミングで、指定した検証を実行できます。

 * On Play ・・・ デバック実行
 * On Build ・・・ ビルド実行
 * On Project Startup ・・・ プロジェクトの起動

![odin_automate_validation_settings.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/4125/37b45ac6-e6d6-1fc8-e33d-60c39aec43ec.png)

また、ビルド実行時に検証を行うように設定し、もし設定した条件を満たさない箇所をみつけたら、ビルドを失敗させる、ということも可能です。こうすることで、ゲーム出荷・リリースする前に必ず条件を満たすことを保証することができます。

# まとめ

この章では、`Odin Validator`を紹介しました。

間違って使っているAssetを削除してしまい、それを参照している部分が原因で不具合が発生した」という不具合の経験がある人は多いのではないでしょうか。
こういう不具合を防ぐために、ぜひ`Odin Validator`を導入してみてください！


# 補足

各種バージョン

* Unity 2019.4.8f1
* Odin Inspector 3.0.2
* Odin Validator 3.0.2