😎

【Unity】ゲーム起動時に順番に処理可能なエントリーポイントを作る

に公開

はじめに

Unityでのゲーム開発では、ゲームが始まる前に様々な初期化処理(データの読み込み、外部SDKのセットアップなど)を行う必要があります。これらの処理は、プロジェクトが大きくなるにつれて複雑になりがちです。この記事では、RuntimeInitializeOnLoadMethodリフレクション を活用して、初期化処理を自動的に、かつ柔軟に管理するエントリーポイントシステムの構築方法を解説します。

RuntimeInitializeOnLoadMethod とは?

[RuntimeInitializeOnLoadMethod] は、Unityが提供する強力なアトリビュート(属性)です。これを static メソッドに付けることで、シーンがロードされる前や、ゲームが開始される前に、そのメソッドを自動的に実行させることができます。

sample.cs
using UnityEngine;

public static class Sample
{
    [RuntimeInitializeOnLoadMethod]
    public static void Hello() => Debug.Log("Hello"); // UnityEditorでPlayすると"Helloと表示される"
}

これにより、以下のようなメリットが得られます。

  • シーンに依存しない: シーン内に初期化用のゲームオブジェクトを配置する必要がなくなります。
  • MonoBehaviourのライフサイクル外で実行: Awake()Start() メソッドよりも早い段階で処理を実行できます。
  • 自動実行: ゲーム起動時に確実に処理を実行させることができます。

エントリーポイントの基盤となる抽象クラス

まず、初期化処理を定義するための基本となる抽象クラス EntryPoint を作成します。

using System;
using Cysharp.Threading.Tasks;

namespace EntryPoints
{
    public abstract class EntryPoint : IComparable
    {
        // 外部から初期化処理を呼び出すための抽象メソッド
        public abstract UniTask Initialize(string[] args);

        // 実行順序を指定するためのプロパティ。小さい値ほど先に実行される。
        public virtual int ExecuteOrder { get; } = 1000;

        // IComaparableインターフェースの実装
        // このメソッドがあることで、エントリーポイントのリストをソートできるようになる
        public int CompareTo(object obj)
        {
            if (obj is EntryPoint other)
            {
                return ExecuteOrder.CompareTo(other.ExecuteOrder);
            }
            return 0;
        }
    }
}

このクラスのポイントは、IComparable インターフェースを実装している点です。これにより、ExecuteOrder プロパティの値を基準にして、複数のエントリーポイントの実行順序を自由に制御できるようになります。

エントリーポイントを動的に検出・実行する Initializer

次に、ゲーム起動時に先ほどの EntryPoint クラスを継承したすべてのクラスを見つけ出し、順番に実行する Initializer クラスを作成します。

using System;
using System.Linq;
using System.Reflection;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;

namespace EntryPoints
{
    public static class Initializer
    {
        [RuntimeInitializeOnLoadMethod]
        public static async void RuntimeInitializeOnLoad()
        {
            try
            {
                var entryPointType = typeof(EntryPoint);

                var current = Assembly.GetExecutingAssembly();

                var entryPoints = current.GetTypes()
                    .Where(type => type.IsSubclassOf(entryPointType))
                    .Select(type => (EntryPoint)Activator.CreateInstance(type))
                    .ToList();

                entryPoints.Sort();

                var args = Environment.GetCommandLineArgs();

                foreach (var entryPoint in entryPoints)
                {
                    await entryPoint.Initialize(args);
                }
            }
            catch (Exception e)
            {
                Debug.LogError($"初期化中にエラーが発生しました: {e.Message}\n{e.StackTrace}");

                Quit();
            }
        }

        private static void Quit()
        {
#if UNITY_EDITOR
            EditorApplication.isPlaying = false;
#else
            Application.Quit();
#endif
        }
    }
}

このコードの重要なポイントは以下の通りです。

  • Assembly.GetExecutingAssembly().GetTypes(): リフレクションという技術を使って、実行中のプロジェクト内のすべてのクラス情報を取得します。
  • Where(type => type.IsSubclassOf(entryPointType)): 取得したクラスの中から、EntryPoint を継承しているクラスだけを抽出します。
  • entryPoints.Sort(): IComparable を実装しているおかげで、ExecuteOrder の値に基づいて実行順序が自動的に決定されます。
  • async voidawait: 各エントリーポイントが非同期処理 (UniTask) を持つ場合でも、順番に実行されることを保証します。

実際にエントリーポイントを作成する

このシステムを使って、実際に初期化処理を行うクラスを作成してみましょう。
例えば、ゲームの設定を読み込むエントリーポイントは次のようになります。

using Cysharp.Threading.Tasks;
using UnityEngine;
using EntryPoints; // EntryPointクラスをインポート

public class SettingsInitializer : EntryPoint
{
    // ExecuteOrderプロパティをオーバーライドして、実行順序を明示的に指定
    public override int ExecuteOrder => 10;

    public override async UniTask Initialize(string[] args)
    {
        Debug.Log("設定ファイルの読み込みを開始します...");
        await UniTask.Delay(TimeSpan.FromSeconds(0.5f)); // 擬似的な非同期処理
        Debug.Log("設定ファイルの読み込みが完了しました。");
    }
}

このクラスを作成して保存するだけで、ゲーム起動時に自動的に実行されます。ExecuteOrder10 に設定しているため、他のエントリーポイントより先に実行させたい場合に役立ちます。

まとめ

このエントリーポイントシステムは、Unityプロジェクトの初期化処理をシンプルかつ強力に管理するための設計パターンです。新しい初期化処理が必要になった場合でも、EntryPoint を継承したクラスを追加するだけで、自動的にシステムに組み込まれるため、メンテナンス性が非常に高くなります。

ぜひあなたのプロジェクトに導入して、よりクリーンで管理しやすいコードベースを目指してみてください。

Discussion