[Unity] Addressable のパスや Scene / Tag / Layer 一覧を生成するコードジェネレータ [ゲーム開発ログ 2020-12-12]

2020-12-12

ゲーム実装時の各種識別子

ゲームのプログラムから何か特定のリソースを指定するには、そのリソースの識別子が必要になる。 移動先のシーン名とか、動的に読み込むリソースのパスとか。

例えば Unity でスクリプトからシーン遷移しようと思った場合は以下のようなコードを書く:

SceneManager.LoadScene("TitleScene");

ここで言う "TitleScene" がシーンを示す識別子。そして、これは素朴な 文字列 だ。

コード上で文字列リテラルをそのまま書いていると、

  • シーン名が変わった時の修正が面倒 (複数箇所で同じ文字列を書いていた場合は特に)
  • ミスタイプしていた場合、コンパイル時点でミスに気づけない (ランタイムで動かすまでわからない)

という不都合があるので、じゃあどこかに定数定義して使うか、 となって以下のようなコードを書いたりする:

namespace MyGame
{
    public static class SceneName
    {
        public const string BootScene  = "BootScene";
        public const string TitleScene = "TitleScene";
        public const string LevelScene = "LevelScene";
        ...
    }
}

...

SceneManager.LoadScene(MyGame.SceneName.TitleScene);

ここではシーン名を例として挙げたが、こうした文字列の識別子は他にもある。 Unity の場合、

  • Addressable Asset のアドレス
  • Layer 定義
  • Tag 定義

なんかがそうだ。

これらの定数定義をその都度手で書くのは面倒なので、 これを自動生成するコードジェネレータを用意するか、という発想が生まれる。

Unity 識別子の定数クラスのコードジェネレータを作る

こういうちょっとしたコードを自動生成するというのはソフトウェア開発では割とよくある話で、 過去の仕事の現場でも似たようなことはよくやられていた。 Unity で同じことをすでにやっている人たちもいる:

今回は個人開発で、 ゲーム開発に使う汎用的なコードは一通り自分で実装して自分のライブラリとしてまとめておきたいという思いがあったため、 自分で書くことにした。

で、実際の実装がこんな感じ:

使い方

この手のライブラリは、 「プロジェクトごとにカスタムもできるけど基本的に持ってくればそのまま使える」 というのが使いやすくてよい。が、どこのフォルダに生成するのかとか、namespace はどうするのかとか、 どうしてもプロジェクト固有の設定をしたくなる部分はある。

設定ファイル的なものを用意するアプローチもあるが、 今回はシンプルに「呼び出すコードは自分で書いてもらって、固有パラメータはそこで指定する」という方法をとった。

使うときは、以下のようなコードを書いて適当な Editor フォルダ以下に置く:

using AltoLib;
using UnityEditor;

namespace MyGame.Editor
{
    public class GenerateSymbolCode
    {
        const string OutputDirPath = "Assets/MyAssets/Scripts/Symbol";
        const string MyNamespace   = "MyGame";

        [MenuItem("Tools/Generate Code/Asset Address & Label")]
        static void GenerateAssetAddressCode()
        {
            Generate(new AssetAddressCodeGenerator());
            Generate(new AssetLabelCodeGenerator());
        }

        [MenuItem("Tools/Generate Code/Scene Name")]
        static void GenerateSceneNameCode()
        {
            Generate(new SceneNameCodeGenerator());
        }

        [MenuItem("Tools/Generate Code/Tag")]
        static void GenerateTagCode()
        {
            Generate(new TagCodeGenerator());
        }

        [MenuItem("Tools/Generate Code/Layer & Layer Mask")]
        static void GenerateLayerCode()
        {
            Generate(new LayerNameCodeGenerator());
            Generate(new LayerMaskCodeGenerator());
        }

        static void Generate(CodeGenerator generator)
        {
            generator.outputDirPath = OutputDirPath;
            generator.namespaceName = MyNamespace;
            generator.Generate();
        }
    }
}

これで Unity のメニューから Tools → Generate Code → Tag などを選ぶと指定したフォルダに指定した namespace で、 以下のようなコードが生成される:

//------------------------------------------------------------------------------
// This file is AUTO-GENERATED by AltoLib.TagCodeGenerator.
//------------------------------------------------------------------------------
namespace MyGame
{
    public static class Tag
    {
        public const string EditorOnly     = "EditorOnly";
        public const string Finish         = "Finish";
        public const string GameController = "GameController";
        public const string MainCamera     = "MainCamera";
        public const string Player         = "Player";
        public const string Respawn        = "Respawn";
        public const string Untagged       = "Untagged";
    }
}

手動で実行するのではなく、アセットの更新を検知して自動更新するのもよくやる手だが、 自分は個人的に裏で勝手に動くプロセスを増やしたくなかったり、 アセット更新時の処理負荷を増やしたくないといった理由で手動にして使っている。 それでも手でコードを書くよりは楽だ。


以上、よくあるちょっとしたワークフロー改善系の話でした。