20
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

UnityでSourceGeneratorを動かすまで

Last updated at Posted at 2023-02-21

はじめに

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

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

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

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

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

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

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

どうです? 試したくなってきたんじゃありませんか?
そんな力の片鱗を味わってみたい方は先へお進みください。

1.プロジェクトの作成

SourceGeneratorはUnityのプロジェクトとは独立したC#クラスライブラリを作成し、
バッチビルドしたDLLをUnityにインポートして使う形になります。

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

新しいプロジェクトを作成
image.png
C#クラスライブラリを選択
プロジェクト名はSourceGeneratorExampleとしました。
image.png
フレームワークは.NET Standard2.0を選択します。
image.png
プロジェクトが作成できたらツールバーからプロジェクト→NuGetパッケージの管理と進んでください。
「ぬーげっと」ってなんかユルくておもしろいですよね。
image.png
NuGetパッケ-ジマネージャが開いたら参照タブに切り替え、検索窓でMicrosoft.CodeAnalysisと検索してください。
image.png
Microsoft.CodeAnalysisMicrosoft.CodeAnalysis.Commonの二つをインストールします。
二つともバージョンは3.9.0を選択してください。
image.png
OK
image.png
I Accept
image.png
ひとまずプロジェクトのセットアップはここまでです。

2.下準備

ここからコードです。

ソリューションエクスプローラープロジェクトを右クリック→追加→新しい項目と進んでください。
image.png
C#クラスを選択し、名前はCodeBuilderとします。
image.png
作成したファイルの中身をいったん削除してこちらのリポジトリからCodeBuilder.csをコピペしてください。

こんなかんじになっていますでしょうか。
image.png

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

3.コード生成

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

image.png

先にコード全文を貼っておきますのでこれをリネームしたExampleClassGeneratorの中に貼り付けてください。
(もともと書かれているコードは削除してください。)

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

リリースにチェックを入れてビルドを押します
image.png

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

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

インポートしたDLLを選択し、InspectorAnyPlatformのチェックを外し、IncludePlatformsもすべてチェックを外します。
image.png
欲しいのはSourceGeneratorの生成したコードだけであり、SourceGenerator自体がアセンブリに含まれてしまうと邪魔になるのでこのように設定する必要があります。

変更したらApplyを押して適用します。
image.png

次にAssetLabelを追加します。
Inspectorの右下のアイコンをクリックし、入力欄にRoslynAnalyzerと入力してください。
「ろずりん」ってかわいくない?
image.png

VisualStudioでUnity側のC#ソリューションを開き,Assembly-CSharp参照SourceGeneratorExampleの中にExampleGeneratorClass_g.csが生成されていれば成功です。
image.png
image.png
このようにMonoBehaviourからもアクセスできるはずです。
image.png

コードの解説

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を指定しています。

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

まとめ

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

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

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

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

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

20
18
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
18

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?