はじめに
Unity2021.2からSourceGeneratorが動作するようになりました。
これを使うことでコンパイル時にソースコードを自動生成することができます。
「面倒なボイラープレートを書きたくない!」
そんな面倒くさがりなそこのあなた!
「デバッグコマンドやオレオレスクリプトを実装するのにリフレクションは使いたくない!
だけどコマンドの追加のたびに呼び出しコードを書きたくないし式木のコンパイル時間も嫌だしIL2CPPの恩恵も受けたい!」
そんな欲張りさんなあなたももう大丈夫!
SourceGeneratorを使えるようになればクラスや関数にアトリビュートをつけるだけで必要なコードを生成したり出来ちゃうんです!
あらゆるコードが生成できちゃう!
InterfaceやGenericsといったC#の言語仕様上の限界に囚われない圧倒的自由!
コンパイル時だからIL2CPPの恩恵も受けで実行時コストも少ない!
まさに無敵の力!
どうです? 試したくなってきたんじゃありませんか?
そんな力の片鱗を味わってみたい方は先へお進みください。
1.プロジェクトの作成
SourceGeneratorはUnityのプロジェクトとは独立したC#クラスライブラリを作成し、
バッチビルドしたDLLをUnityにインポートして使う形になります。
ということでまずはUnityプロジェクトとは別にVisualStudioでC#のクラスライブラリを作成します。
新しいプロジェクトを作成

C#クラスライブラリを選択
プロジェクト名はSourceGeneratorExampleとしました。

フレームワークは.NET Standard2.0を選択します。

プロジェクトが作成できたらツールバーからプロジェクト→NuGetパッケージの管理と進んでください。
「ぬーげっと」ってなんかユルくておもしろいですよね。

NuGetパッケ-ジマネージャが開いたら参照タブに切り替え、検索窓でMicrosoft.CodeAnalysisと検索してください。

Microsoft.CodeAnalysisとMicrosoft.CodeAnalysis.Commonの二つをインストールします。
二つともバージョンは3.9.0を選択してください。

OK

I Accept

ひとまずプロジェクトのセットアップはここまでです。
2.下準備
ここからコードです。
ソリューションエクスプローラーでプロジェクトを右クリック→追加→新しい項目と進んでください。

C#クラスを選択し、名前はCodeBuilderとします。

作成したファイルの中身をいったん削除してこちらのリポジトリからCodeBuilder.csをコピペしてください。
CodeBuilderはコード生成を少し楽にするためのクラスになります。
3.コード生成
いよいよコード生成の本体に入っていきます。
最初から作成されているClass1を使いますが、ダサいのでリネームしておきましょう。
ひとまずExampleClassGeneratorにしておきます。
先にコード全文を貼っておきますのでこれをリネームしたExampleClassGeneratorの中に貼り付けてください。
(もともと書かれているコードは削除してください。)
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上で動作させましょう。
ソリューションエクスプローラーでソリューション名を右クリックし、「バッチビルド」を選択してください。

エクスプローラーでソリューションファイルを作成したフォルダを開き、
ソリューション名と同名のフォルダ→bin→Release→netstandard2.0と進んでください。




netstandard2.0フォルダ内の.dllファイルをUnityのAssetsフォルダ内の好きな場所にインポートします。

インポートしたDLLを選択し、InspectorでAnyPlatformのチェックを外し、IncludePlatformsもすべてチェックを外します。

欲しいのはSourceGeneratorの生成したコードだけであり、SourceGenerator自体がアセンブリに含まれてしまうと邪魔になるのでこのように設定する必要があります。
次にAssetLabelを追加します。
Inspectorの右下のアイコンをクリックし、入力欄にRoslynAnalyzerと入力してください。
「ろずりん」ってかわいくない?

VisualStudioでUnity側のC#ソリューションを開き,Assembly-CSharp→参照→SourceGeneratorExampleの中にExampleGeneratorClass_g.csが生成されていれば成功です。


このようにMonoBehaviourからもアクセスできるはずです。

コードの解説
SourceGeneratorExampleのソリューションに戻ってコードの中身を解説していきます。
[Generator]
public class ExampleGenerator : ISourceGenerator
[Generator]属性とISourceGeneratorインターフェースを使用することでソース生成を行うクラスを定義できます。
ISourceGeneratorには以下の二つの関数が宣言されているのでこれらを実装します。
public void Execute(GeneratorExecutionContext context)
public void Initialize(GeneratorInitializationContext context)
今回はInitializeのみ利用します。
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です。
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を指定しています。
今回のコードの全文は以下のリポジトリにあります。
まとめ
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側でクラスや関数に付与した属性を読み取ってそれに合わせたコード生成を行う方法を解説したいと思います。



