役演亭 -Yakuentei- Roleplay with your own characters.
  1. ホーム
  2. 技術記事
  3. ゲーム開発

Unity エディター拡張スクリプトで「手作業」を減らす・無くす

公開日 (Published) : 更新日 (Modified) :

実際のゲーム開発で作成したエディター拡張スクリプトを例に自動化方法を説明します。

【Unity】エディター拡張スクリプトで「手作業」を減らす・無くす

筆者が「地底防衛軍」の開発で実際に運用しているエディター拡張スクリプト (SUEditor.cs) を紹介し、メソッド毎に説明していきます。

目次

まえがき (対象読者など)

プログラミングができる方にとっては、Unity のような GUI (マウス) で作業することに重きを置いたゲームエンジンは、かえって「面倒」で「不便」に感じる局面もあると思います。

しかし、Unity にはエディター拡張という、マクロのようなことができる API が搭載されています。つまり、C# Script で Unity Editor 上の手作業も自動化可能なのです。

別記事「Unity でプログラミングのみ (C# Script のみ) でゲームを作る方法」と組み合わせれば、あたかも Unity をゲーム開発用クラスライブラリーのように使うことも可能でしょう。

本記事では、実際に筆者がゲーム開発用に組んだエディター拡張スクリプトを例に、実際に Unity をスクリプト制御している具体的な方法を説明していきます。

実際のゲーム開発で作成したスクリプト

以下で紹介するスクリプトは、実際に筆者が「地底防衛軍」(下記) 用に自作し、Unity 2022 LTS 上で実運用 (2024年5月にリリースされた Unity 6 Preview でも動作確認済) しているソースコードです。

  • Unity 6 では 1 箇所だけ非推奨 API になった箇所があるため、後述します。

下記を実際に動かすためにはアセットも必要になりますが、さすがにアセットまで公開する訳にはいきませんので、ソースコードのみとさせていただきます。

  • どうしても動かしてみたい…という方は、Steam で「地底防衛軍」を購入していただいて、AssetStudio 等で…(ry

SUEditor.cs

メインのエディター拡張スクリプトです。やや長いので、別ファイルとしています。

エディター拡張用のスクリプトのため、Assets フォルダーの下に Editor という名前でフォルダーを掘り、その中で運用する必要があります。(注:アセンブリ設定を追加で行えば他のフォルダーで運用することも可能)

SUEditorUnicodeRange.cs

上記のソースコード (SUEditor.cs) 上で、TextMesh Pro の SDF フォント生成を自動化するために使っている Unicode 一覧の定義です。かなり長いので、別ファイルとしています。

アセットのインポート時の設定の自動化

ソースコード (SUEditor.cs) の上から順番に説明していきますので、まずはアセットのインポート時の設定を自動化している箇所からです。

実際にこれを行っているメソッドは、以下の 3 つです。

  • OnPreprocessTexture()
  • OnPreprocessAudio()
  • OnPostprocessAllAssets()

これらのメソッドは、AssetPostprocessor クラスを継承すると、Unity がしかるべきタイミング (PNG や MP3 を追加したときなど) で自動的に呼んでくれるようになります。

したがって、アセットのインポート時に、各ファイルに対して行いたい設定を自動化することができるようになります。

class SUEditor : AssetPostprocessor

難しいと感じる方は、通常の C# Script で MonoBehaviour クラスを継承すると Start() や Update() が自動的に呼ばれるのと同じような仕組みだと思って下さい。

OnPreprocessTexture() メソッド

AssetPostprocessor を継承した状態で、C# Script 上で OnPreprocessTexture() を定義すると、画像ファイル (PNG, JPG など) が Unity プロジェクト上にインポートされたときに自動的に呼ばれるようになります。

public void OnPreprocessTexture()
{
    var importer = assetImporter as TextureImporter;
    if (importer.assetPath.StartsWith(
        "Assets/SUData/App/AppCursor"))
    {
        importer.textureType = TextureImporterType.Cursor;
        importer.textureCompression =
            TextureImporterCompression.Uncompressed;
    }
    // (中略 - SUEditor.cs の中身を参照)
}

スプライト分割時の手作業は OnPreprocessTexture() で自動化すれば「一切不要」。

例えば、ソースコードの以下の部分では、上記のようにアイコンが格納されている画像ファイルのスプライト分割 (Sprite Mode = Multiple) の手作業を C# Script で自動化しています。

else if (importer.assetPath.StartsWith(
    "Assets/Resources/SUData/IIcon/"))
{
    importer.textureType = TextureImporterType.Sprite;
    importer.spriteImportMode = SpriteImportMode.Multiple;
    importer.textureCompression =
        TextureImporterCompression.CompressedHQ;

    var factory = new SpriteDataProviderFactories();
    factory.Init();
    var dataProvider =
        factory.GetSpriteEditorDataProviderFromObject(assetImporter);
    dataProvider.InitSpriteEditorDataProvider();
    dataProvider.SetSpriteRects(
        Enumerable.Range(0, IconNames.Length).Select(
        iconID => new SpriteRect()
        {
            name = IconNames[iconID],
            spriteID = GUID.Generate(),
            rect = new(24 * (iconID % 10), 24 * (iconID / 10), 24, 24)
        }).ToArray());
    dataProvider.Apply();
}

24x24 ピクセル毎に区切って、それぞれに名前を振る処理を LINQ で自動化しています。(もちろん、LINQ が難しいと感じる方は for や foreach などを使って書くこともできます)

OnPreprocessAudio() メソッド

こちらは OnPreprocessTexture() メソッドの音声ファイル版です。つまり、Unity プロジェクトに MP3 や OGG などがインポートされたときに自動的に呼ばれます。

public void OnPreprocessAudio()
{
    var importer = assetImporter as AudioImporter;
    var sampleSettings = importer.defaultSampleSettings;
    if (importer.assetPath.StartsWith("Assets/SUData/SBGM/"))
    {
        sampleSettings.loadType = AudioClipLoadType.CompressedInMemory;
    }
    else
    {
        sampleSettings.loadType = AudioClipLoadType.DecompressOnLoad;
    }
    importer.defaultSampleSettings = sampleSettings;
}

上記のソースコードでは、MP3 等がインポートされたフォルダーが BGM 用か否かで、音声アセットに対して行う設定内容を分岐しています。

BGM 用 mp3, ogg ファイルの "Compressed In Memory" "Streaming" などの設定も OnPreprocessAudio() に入れておけば、忘れないし確実。

OnPostprocessAllAssets() メソッド

すべての種類のファイルについて、Unity 側でのアセットのインポート処理が完了した後に呼ばれるメソッドです。

public static void OnPostprocessAllAssets(
    string[] importedAssets, string[] deletedAssets, string[] movedAssets,
    string[] movedFromAssetPaths)
{
    var defaultSettings = AddressableAssetSettingsDefaultObject.Settings;

    foreach (string importedAssetPath in importedAssets)
    {
        foreach (var targetPath in AddressableTargetPaths)
        {
            if (!importedAssetPath.StartsWith(targetPath)) continue;

            defaultSettings.CreateOrMoveEntry(
                AssetDatabase.AssetPathToGUID(importedAssetPath),
                defaultSettings.DefaultGroup);
        }
    }
}

例えば Addressables のように、複数種類のファイルを跨いで共通の設定処理を行いたいときに役に立ちます。

private static readonly string[] AddressableTargetPaths = new[]
{
    "Assets/SUData/IWP/",
    "Assets/SUData/SBGM/",
};

上記のように定義されているフォルダにインポートされたすべてのファイルに対して Addressables のグループ設定を機械的に行っています。

Addressables で面倒なインポートしたアセットの登録も OnPostprocessAllAssets() に書いておけば自動化できて楽。

任意のタイミングで実行したい自動化処理のメニュー化

Unity 上で自動化したい処理を記述したメソッド (引数無し、戻り値無し) の前に、以下のような記述 (属性定義) を追加することで、Unity Editor メニュー上の指定した階層に、任意の名称で「自作メニュー」として定義することができます。

[MenuItem("!!!! SUEditor !!!!/0-1. Initial Project Setup")]
[MenuItem("!!!! SUEditor !!!!/0-2. Setup App Images")]
[MenuItem("!!!! SUEditor !!!!/0-3. Import TMP Essentials")]

上記のような記述を行うことで、以下のようにユーザー定義のメニューが出現します。

任意のタイミングで実行したい自動化スクリプトは [MenuItem] 属性を記述してメニューに登録すると便利。

このように自前のメニューを登録しておけば、複雑・多岐に渡る膨大な手作業を、メニューのクリック 1 発だけで、あたかもバッチファイルのように実行できるようになります。

本記事で紹介しているスクリプト (SUEditor.cs) では、上記のメニューにもあるように、全部で 7 種類の処理を自動化しています。

0-1. Initial Project Setup

0-2. Setup App Images

これらの 2 つは、Unity のバージョンアップ等でプロジェクトを作り直したときの初期設定を自動化したメニュー項目です。

[MenuItem("!!!! SUEditor !!!!/0-1. Initial Project Setup")]
public static void InitialProjectSetup()
{
    // (中略 - SUEditor.cs を参照)
    PlayerSettings.companyName = "Yakuentei";
    PlayerSettings.productName = "UDForce";
    PlayerSettings.bundleVersion = "0.1";
    // (中略 - SUEditor.cs を参照)
}

[MenuItem("!!!! SUEditor !!!!/0-2. Setup App Images")]
public static void SetupAppImages()
{
    PlayerSettings.SetIcons(NamedBuildTarget.Unknown, new[]
    {
        AssetDatabase.LoadAssetAtPath<Texture2D>(
            "Assets/SUData/App/AppIcon.png")
    }, IconKind.Application);
    PlayerSettings.defaultCursor = AssetDatabase.LoadAssetAtPath<Texture2D>(
        "Assets/SUData/App/AppCursor.png");
}

Player Settings, Project Settings などのダイアログに設定する項目をひとまとめにして、C# Script から設定しています。

つい忘れがち・漏れがちな Project Settings の設定もダイアログを開かなくてもスクリプトで自動化可能。

なお、画像ファイル関係だけ別のメニュー (メソッド) に分割している理由は、InitialProjectSetup() 側で画像ファイルのインポート処理に影響する設定項目 (具体的には Player Settings の Color Space) を変えているからです。

  • 筆者の方では、「0-1. Initial Project Setup」→「各アセットのインポート」→「0-2. Setup App Images」の順に実施する運用を行っています。

0-3. Import TMP Essentials

こちらは、自動化するメリットはあまり無いのですが、「手順そのものを忘れずに行うようにするため」に入れています。

[MenuItem("!!!! SUEditor !!!!/0-3. Import TMP Essentials")]
public static void ImportTMPEssentials()
{
    TMP_PackageUtilities.ImportProjectResourcesMenu();
}

TextMesh Pro の機能をプロジェクトで初めて使ったときに聞かれる「Import TMP Essentials」の設定を呼び出しています。

プロジェクトで TextMesh Pro の機能を使用するたびに毎回聞かれる Import TMP Essentials も自前のメニューに登録しておけば利便性も上がる。

0-4. Generate Japanese Font

TextMesh Pro で最も面倒な作業と言っても過言ではない SDF フォントの生成手順 (TTF フォントを読み込ませて変換) を自動化したものです。

    [MenuItem("!!!! SUEditor !!!!/0-4. Generate Japanese Font")]
    public static void GenerateJapaneseFont()
    {
        var srcFont = AssetDatabase.LoadAssetAtPath<Font>(FontSrcPath);
        var destFont = TMP_FontAsset.CreateFontAsset(
            srcFont, 40, 4, GlyphRenderMode.SDFAA_HINTED,
            4096, 4096, AtlasPopulationMode.Dynamic, false);

        if (destFont.TryAddCharacters(
            new SUEditorUnicodeRange().UnicodesForTMPro, false))
        {
            // (中略 - SUEditor.cs を参照)
        }
        else
        {
            Debug.LogError("[SUEditor] Could not generate SDF.");
        }
    }

詳細は、別記事「Unity の文字列描画 (TextMesh Pro) を C# Script のみで行う方法」で説明していますので、興味のある方はご一読下さい。

  • ただし、「地底防衛軍」の場合は、文字の太さや、テキスト中にアイコン (スプライト) を埋め込んだ際の位置の微調整のため、上記の記事には無い設定も行っています。

TextMesh Pro で最も面倒な作業の 1 つである SDF フォントの生成もスクリプトで自動化可能。

0-5. Initial Scene Setup

Unity のシーン (Scene) へのオブジェクト配置は手作業で行うことが多いと思いますが、これを自動化したものとなります。

[MenuItem("!!!! SUEditor !!!!/0-5. Initial Scene Setup")]
public static void InitialSceneSetup()
{
    foreach (var gameObject in Object.FindObjectsOfType<GameObject>())
    {
        Object.DestroyImmediate(gameObject);
    }
    // (中略 - SUEditor.cs を参照)
}

地底防衛軍」の場合、プロジェクト作成直後に、以下の 3 つのオブジェクトの配置とパラメーター設定を自動的に行うようにしています。

  • メインカメラ (ゲーム画面用)
  • バックカメラ (キャラメイクしたキャラクターの撮影用)
  • InputField (キャラメイク時の名前入力用)

このうち、特に InputField 関連は、TextMesh Pro や Event System も絡んでおり、設定すべき項目が非常に多く、プロジェクトを作り直すたびに手作業で配置していては大変なので、頑張って自動化しました。

ゲームを実行する「前」にあらかじめシーン (Scene) に配置しておきたいオブジェクトもスクリプトから事前に追加可能。

ちなみに筆者は、ゲームオブジェクトの大半を「実行時」に (=ランタイムで) C# Script から生成する方法でゲームを作っていることに加え、独自にシーンマネージャーに相当するコードを自作しているため、シーンは 1 つしか作らず、実行前に配置するオブジェクトも少なめです。

このあたりのやり方の概要は、別記事「Unity でプログラミングのみ (C# Script のみ) でゲームを作る方法」で説明していますので、特にプログラミング主体で Unity ゲーム開発を行いたいと考えている方には役に立つと思います。

Unity 6 で運用する場合の注意

Unity 6 では、FindObjectsOfType() が非推奨 API (=将来廃止予定) となりました。

foreach (var gameObject in Object.FindObjectsOfType<GameObject>())
{
    Object.DestroyImmediate(gameObject);
}

代わりに、微妙にメソッド名や引数の仕様が異なる FindObjectsByType() に置き換えることが推奨されています。

foreach (var gameObject in Object.FindObjectsByType<GameObject>(
    FindObjectsSortMode.None))
{
    Object.DestroyImmediate(gameObject);
}

本事例では、あらかじめシーン内に配置されているゲームオブジェクト (デフォルトのカメラなど) をすべて削除するのが目的であり、オブジェクトの並び順は関係ないため、None (=並び替えしない) を指定しています。

1-1. Build Addressable Assets

筆者は、Addressables の動作を、実際に exe としてビルドしたオブジェクトの動作に近づけるため、Play Mode Script = Use Existing Build の設定で開発を行っています。

Addressables に詳しい方はご存じだと思いますが、上記の設定 (=本番系に最も近い) でゲーム開発を行う場合、Addressables に登録するアセットをプロジェクトに追加するたびにアセットバンドルの再ビルドが必要になるため、この手順を自動化しています。

[MenuItem("!!!! SUEditor !!!!/1-1. Build Addressable Assets")]
public static void BuildAddressableAssets()
{
    // (中略 - SUEditor.cs を参照)

    AddressableAssetSettings.CleanPlayerContent();
    AddressableAssetSettings.BuildPlayerContent();

    Debug.Log("[SUEditor] Selected Builder for Play Mode = " +
        defaultSettings.DataBuilders[selectedIndex].name +
        " (Index = " + selectedIndex + ")");
}

Addressables そのものの説明については、色々と複雑なので、本記事では割愛します。(結構奥が深い世界なので、いずれ、別途記事を執筆するかもしれません)

Addressables に登録したアセットバンドルのビルドも自前のメニューから 1 アクションで実行可能。

1-2. Build TMP Sprite Assets

TextMesh Pro で表示するテキストの文中にアイコンを埋め込みたい場合、TMP Sprite Asset を作成する必要がありますが、この手順を自動化したメニューとなります。

[MenuItem("!!!! SUEditor !!!!/1-2. Build TMP Sprite Assets")]
public static void BuildTMPSpriteAssets()
{
    const string srcAssetPath =
        "Assets/Resources/SUData/IIcon/IIcon.png";
    const string destAssetPath =
        "Assets/Resources/SUData/IIcon/IIcon.asset";
    const int fontSizeRef = 24;

    // (中略 - SUEditor.cs を参照)

    EditorUtility.SetDirty(spriteAsset);
    AssetDatabase.SaveAssets();
    AssetDatabase.Refresh();
}

アセットのインポート時の設定の自動化」の節で作成したアイコン用の Sprite Asset をもとに、TextMesh Pro の <sprite name="xxx"> タグで文中に埋め込む際のサイズ、オフセット位置などの設定を追加で行い、TMP Sprite Asset の作成を自動化しています。

TextMesh Pro でテキスト中にアイコンを挿入するときに必要な TMP Sprite Asset の面倒な作成・設定作業もスクリプトで自動化可能。

おわりに

本記事で紹介した自動化例は、筆者が実際に「地底防衛軍」のプロジェクトで自動化したものだけに限られますが、本記事の内容だけでも Unity Editor 上で行う手作業の多くを C# Script 化できそうだということは伝わったと思います。

本記事の SUEditor.cs 上に出てくる Unity のクラス名、メソッド名、プロパティ名などをもとに、他の方が公開しているエディター拡張に関する記事や、Unity 公式のスクリプトリファレンスなどを検索すれば、読者の方で自動化したいと考えているトピックに関する情報もきっと得られると思います。

プログラミングが得意な方はもちろん、苦手な方であっても、簡単にスクリプト化できそうなところだけでも自動化すれば利便性が上がると思いますので、この記事をきっかけに Unity のエディター拡張 API の運用にチャレンジしていただけたら幸いです。

主な更新履歴

  • 2024-05-05
    • Unity 6 で動作することが確認できたため更新。
      (1 箇所だけ非推奨 API になったため、置換方法も追記)
  • 2024-04-21
    • 初版

「ゲーム開発」一覧

Unity でプログラミングのみ (C# Script のみ) でゲームを作る方法
プログラマーの方向けに、Unity で極力デザイナに頼らずゲームを作る方法を紹介します。
Unity の文字列描画 (TextMesh Pro) を C# Script のみで行う方法
Unity で極力デザイナに頼らず TextMesh Pro で文字列を描画する方法を紹介します。
Unity エディター拡張スクリプトで「手作業」を減らす・無くす
(現在開いているページです)
Unity の C# Script で「無」から音を産み出す方法
音声ファイル無し、スクリプトのみでゼロから音を作って鳴らす方法を紹介します。
Unity アセット暗号化 実例付き入門
AssetStudio 等のツールで素材をぶっこ抜かれないように保護する方法の基本説明です。
Unity アセットバンドル (AssetBundle) 実例付き入門
外部のアセットファイルを読み込み可能にする「アセットバンドル」機能の説明です。
Unity の Time.deltaTime で測った時間がおかしい? (float 型に注意)
Unity で Time.deltaTime をもちいて時間計測を行う際の注意点を解説します。
【暫定】RPG Maker Unite のアドオンからゲーム動作に介入する方法
従来ツクールのように、既存ゲーム動作に介入するための手法を調べた暫定記事です。
【IL2CPP対応】RPG Maker Unite のアドオンからメソッド差替する方法
iOS など IL2CPP でのビルドが必要な場合でも使えるメソッド差替方法を紹介します。