UnityでSourceGeneratorを動かすまで

# はじめに
Unity2021.2からSourceGeneratorが動作するようになりました。
これを使うことでコンパイル時にソースコードを自動生成することができます。

「面倒なボイラープレートを書きたくない!」

そんな面倒くさがりなそこのあなた!

「デバッグコマンドやオレオレスクリプトを実装するのにリフレクションは使いたくない!
だけどコマンドの追加のたびに呼び出しコードを書きたくないし式木のコンパイル時間も嫌だしIL2CPPの恩恵も受けたい!」

そんな欲張りさんなあなたももう大丈夫!

SourceGeneratorを使えるようになればクラスや関数にアトリビュートをつけるだけで必要なコードを生成したり出来ちゃうんです!

あらゆるコードが生成できちゃう！
InterfaceやGenericsといったC#の言語仕様上の限界に囚われない圧倒的自由！
コンパイル時だからIL2CPPの恩恵も受けで実行時コストも少ない!
まさに無敵の力!

どうです?　試したくなってきたんじゃありませんか?
そんな力の片鱗を味わってみたい方は先へお進みください。
# 1.プロジェクトの作成
`SourceGenerator`はUnityのプロジェクトとは独立した`C#クラスライブラリ`を作成し、
`バッチビルド`したDLLをUnityにインポートして使う形になります。

ということでまずはUnityプロジェクトとは別に`VisualStudio`で`C#のクラスライブラリ`を作成します。

`新しいプロジェクト`を作成
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2570514/9e94b6e2-f43f-26c7-0e6d-68e6737a3733.png)
`C#クラスライブラリ`を選択
プロジェクト名は`SourceGeneratorExample`としました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2570514/bf1a33d5-1895-24d6-679c-41718fc905c7.png)
フレームワークは`.NET Standard2.0`を選択します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2570514/5eaa5a4a-3659-22e3-bd80-da68555f041b.png)
プロジェクトが作成できたらツールバーから`プロジェクト→NuGetパッケージの管理`と進んでください。
「ぬーげっと」ってなんかユルくておもしろいですよね。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2570514/ec3c8d69-e56c-009d-6302-9e3f8546fb6a.png)
`NuGetパッケ－ジマネージャ`が開いたら`参照`タブに切り替え、検索窓で`Microsoft.CodeAnalysis`と検索してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2570514/2f68f59d-cdb7-484b-25c5-1f60d41a20a3.png)
`Microsoft.CodeAnalysis`と`Microsoft.CodeAnalysis.Common`の二つをインストールします。
二つともバージョンは`3.9.0`を選択してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2570514/6a71cf2f-475e-7f2c-5654-747238e8119c.png)
`OK`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2570514/54383177-a0e7-f0bf-3a07-3df4e34ee7df.png)
`I Accept`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2570514/3a10387e-67f1-1351-8155-a3a02c83479a.png)
ひとまずプロジェクトのセットアップはここまでです。

# 2.下準備
ここからコードです。

`ソリューションエクスプローラー`で`プロジェクトを右クリック→追加→新しい項目`と進んでください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2570514/559bccee-4fae-bd7d-8f31-89aee44c3eac.png)
`C#クラス`を選択し、名前は`CodeBuilder`とします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2570514/47f3beaf-2553-0f42-c275-68c24b8f8f27.png)
作成したファイルの中身をいったん削除してこちらのリポジトリから`CodeBuilder.cs`をコピペしてください。

https://github.com/TsukuruHoshikawa/CodeBuilder/blob/main/CodeBuilder.cs

こんなかんじになっていますでしょうか。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2570514/2099dd16-7578-24e3-8364-d978ba2ffe2b.png)

`CodeBuilder`はコード生成を少し楽にするためのクラスになります。

# 3.コード生成
いよいよコード生成の本体に入っていきます。
最初から作成されている`Class1`を使いますが、ダサいのでリネームしておきましょう。
ひとまず`ExampleClassGenerator`にしておきます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2570514/68939674-193d-0467-73f2-912a5eba1052.png)

先にコード全文を貼っておきますのでこれをリネームした`ExampleClassGenerator`の中に貼り付けてください。
(もともと書かれているコードは削除してください。)
```C#:ExampleClassGenerator.cs
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Text;
using System.Text;
using Tsukuru;

namespace SourceGeneratorExample
{
    [Generator]
    public class ExampleGenerator : ISourceGenerator
    {
        public void Execute(GeneratorExecutionContext context)
        {
        }

        public void Initialize(GeneratorInitializationContext context)
        {
            var cb = new CodeBuilder();
            cb.AddCode("//Auto Generated Class");
            cb.NewLine("using UnityEngine;");

            using (new BlockScope(cb, "internal class ExampleGeneratedClass"))
            {
                using (new BlockScope(cb, "internal static void ExampleGeneratedFunction()"))
                {
                    cb.NewLine("Debug.Log(\"I am auto generated class!!\");");
                }
            }

            context.RegisterForPostInitialization(_ =>
            {
                _.AddSource("ExampleGeneratedClass_g.cs", SourceText.From(cb.ToString(), Encoding.UTF8));
            });
        }
    }
}

```
# ビルド
コードの解説はいったん後にしてUnity上で動作させましょう。

ソリューションエクスプローラーでソリューション名を右クリックし、「バッチビルド」を選択してください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2570514/02f5228f-62b0-e9de-5018-9a3477577895.png)

リリースにチェックを入れてビルドを押します
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2570514/747e0afc-bf21-8bb9-5e53-615b3d5d0a9a.png)

エクスプローラーでソリューションファイルを作成したフォルダを開き、
`ソリューション名と同名のフォルダ`→`bin`→`Release→netstandard2.0`と進んでください。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2570514/5723a7f7-3da3-25d4-3559-0b4e2e336622.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2570514/aceed148-26d0-29cc-0a21-1f867b64d2aa.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2570514/7c6b5f62-77dd-36f5-ad85-195349aeeb09.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2570514/ea1d5c50-0bf9-8849-1456-861e6fc8b96a.png)

`netstandard2.0`フォルダ内の`.dll`ファイルをUnityの`Assets`フォルダ内の好きな場所にインポートします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2570514/c5a74f6a-5a9e-56d0-a8f0-2f1eaff1241f.png)

インポートした`DLL`を選択し、`Inspector`で`AnyPlatform`のチェックを外し、`IncludePlatforms`もすべてチェックを外します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2570514/36154abb-3ce3-1632-3587-c68f21bd4a93.png)
欲しいのは`SourceGeneratorの生成したコード`だけであり、`SourceGenerator自体`がアセンブリに含まれてしまうと邪魔になるのでこのように設定する必要があります。

変更したら`Apply`を押して適用します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2570514/7bd48f86-455c-e6d3-6a8d-3ccd2c083a65.png)

次に`AssetLabel`を追加します。
`Inspector`の右下のアイコンをクリックし、入力欄に`RoslynAnalyzer`と入力してください。
「ろずりん」ってかわいくない?
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2570514/104f14f7-501a-5492-d041-8af5bd47abbd.png)

VisualStudioでUnity側のC#ソリューションを開き,`Assembly-CSharp`→`参照`→`SourceGeneratorExample`の中に`ExampleGeneratorClass_g.cs`が生成されていれば成功です。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2570514/d975eaf8-92d3-373b-cc11-f62386ce9f21.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2570514/67ee9109-19fa-ae52-5499-af8b8fe5ad17.png)
このように`MonoBehaviour`からもアクセスできるはずです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2570514/988d2482-3498-4bd7-2a8c-d73bb0333dba.png)

# コードの解説
`SourceGeneratorExample`のソリューションに戻ってコードの中身を解説していきます。

```C#
[Generator]
public class ExampleGenerator : ISourceGenerator
```

`[Generator]`属性と`ISourceGenerator`インターフェースを使用することでソース生成を行うクラスを定義できます。

`ISourceGenerator`には以下の二つの関数が宣言されているのでこれらを実装します。

```C#
public void Execute(GeneratorExecutionContext context)
public void Initialize(GeneratorInitializationContext context)
```

今回は`Initialize`のみ利用します。

```C#
var cb = new CodeBuilder();
cb.AddCode("//Auto Generated Class");
cb.NewLine("using UnityEngine;");

using (new BlockScope(cb, "internal class ExampleGeneratedClass"))
{
    using (new BlockScope(cb, "internal static void ExampleGeneratedFunction()"))
    {
        cb.NewLine("Debug.Log(\"I am auto generated class!!\");");
    }
}
```
Initialize関数の前半では先ほどのCodeBuilderクラスを使用してソースコードの文字列を生成しています。
この部分は文字列さえ生成できればどんな形で記述してもOKです。


```C#
context.RegisterForPostInitialization(_ =>
{
    _.AddSource("ExampleGeneratedClass_g.cs", SourceText.From(cb.ToString(), Encoding.UTF8));
});
```

`Initialize`関数の引数には`GeneratorInitializeContext`が渡されてくるので、
`RegisterForPostInitialization`を呼び出してコールバックを渡すことで`SourceGenerator`が読み込まれた直後の処理を設定できます。

生成結果が常に同じ内容(アトリビュートクラスの生成など)の場合にはこれを利用するのが良いでしょう。

コールバックの引数には`GeneratorPostInitializationContext`が渡されますので、
`AddSource`を呼び出すことでソースコードの生成を行うことができます。

**第一引数にはファイル名を渡します。**
自動生成されるソースのファイル名にはサフィックスとして`_g` `.g` `.generated`等をつける慣習があるっぽいです。

**第二引数にはソースコードの文字列を渡します。**
UnityとVisualStudioで正しく日本語を表示するためには`UTF8`で保存する必要があるため、`SourceText.From`を利用して`UTF8`を指定しています。

今回のコードの全文は以下のリポジトリにあります。

https://github.com/TsukuruHoshikawa/SourceGeneratorExample

# まとめ
`Unity`で`SourceGenerator`を使う手順は
・`.netstandard2.0`の`C#クラスライブラリ`を作成する
・`Microsoft.CodeAnalysis(3.9)`と`Microsoft.CodeAnalysis.Common(3.9)`をインストールする
・`ISourceGenerator`を継承し、`[Generator]`属性を付与したクラスを作る。
・`バッチビルド`を行い、出力された`DLL`を`Unity`にインポートする
・`DLL`の`ImportSettings`で`すべてのプラットフォームを除外`する。
・`DLL`の`ImportSettings`で`AssetLabel`に`RoslynAnalyzer`を付与する。

コード生成やメタプログラミングと聞くと難しく思えるかもしれませんが、実際に試してみると手順も簡単でおまじない的なコードも殆ど無いので気軽に遊べます。

`コンパイル時コード生成`という響きだけでもなんかカッコいいですし、自分が全知全能の無敵になった感覚が味わえます。
これから盛り上がっていく最先端コンテンツの一つでもあるので周囲から`Unityめっちゃできる人`だと思われたい人にもうってつけです！
皆`SourceGenerator`を始めてこの圧倒的なC#新時代の力に酔いしれましょう!

今回はとりあえず導入編ということで決め打ちのソースコードを生成するだけにとどめましたが、実際にはUnityプロジェクト内のクラスの解析結果を受け取ってそれに合わせたコード生成をリアルタイムに行うことで真価を発揮します。

ということで次回はUnity側でクラスや関数に付与した属性を読み取ってそれに合わせたコード生成を行う方法を解説したいと思います。