Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
UnityInjectorとComponentの動作メモ

UnityInjectorの動作メモ

Unityを分かっていない人(主に筆者)向けのメモです

AddModsSliderへのプルリクエストの検討時にCM3D2-01さんから教わりました。

UnityInjectorの動作のおさらい

  • ReiPatcherでUnityInjector.Patcherを適用すると
    • Assembly-CSharp.dll/SceneLogo.Start の先頭に CM3D2x64_Data/Managed 内に配置した UnityInjector.dll/UnityInjector.UnityInjector.PluginManager.Init への呼び出しが書き込まれる
  • ゲームを起動すると
    • Assembly-CSharp.dll/SceneLogo.Start の実行時に UnityInjector.dll/UnityInjector.PluginManager.Init が呼び出される
    • InitはプラグインDLLを列挙して、その中から UnityInjector.PluginBase を継承しているプラグインクラスを全て探す
    • 見つかったプラグインクラスは PluginManager が登録した GameObject("UnityInjector") のコンポーネントとして GameObject.AddComponent を使って追加される

Componentの挙動メモ

  • Component.nameは素朴な変数ではなくプロパティ
    • ComponentのnameプロパティはアタッチされたGameObjectとその中の全コンポーネント間で共有される ("Components share the same name with the game object and all attached components.")
    • よって、基本的にコンポーネントが name を変えてはいけない。GameObjectに任せるのが無難
      • UnityInjectorの場合は UnityInjector.PluginManager.Init で "UnityInjector" という name が与えられているので、そのままにしておくのが良い
    • ユニーク名 (クラス名) は Component.Name に入っている
  • Component.SendMessageは、コンポーネントが属しているGameObject内の全MonoBehaviour(≒コンポーネント)に対して呼び出しを行う ("Calls the method named methodName on every MonoBehaviour in this game object.")
    • よって、Component.SendMessageは特定のコンポーネントのみを呼び出す用途には使えない
    • また、コンポーネントから呼び出した場合は自分自身も呼び出される
    • 特定のコンポーネントのみを呼び出したい場合は以下のようにするしかない(? もっと良い方法があるのかも)
var ps = FindObjectsOfType(typeof(UnityInjector.PluginBase)) as UnityInjector.PluginBase[];
UnityInjector.PluginBase o = ps.FirstOrDefault((p) => p.Name == "プラグインのクラス名");
MethodInfo mi = o.GetType().GetMethod("呼び出したいメソッド名");
mi.Invoke(o, null);
UnityInjector.PluginBase[] plugins = this.gameObject.GetComponentsInChildren<UnityInjector.PluginBase>();
UnityInjector.PluginBase plugin = plugins.FirstOrDefault((p) => p.Name == "プラグインのクラス名");
MethodInfo mi = plugin.GetType().GetMethod("呼び出したいメソッド名");
mi.Invoke(plugin, null);
  • 呼び出すだけであれば、this.gameObject.SendMessage("ユニークなメソッド名")で呼び出すのが一番簡単

UnityInjector.PluginManagerの擬似コード

以下はエラー処理省略版です

internal static class PluginManager
{
    public static bool IsInitialized { get; private set; }

    // 初期化 (プラグインをコンポーネントとして追加し、稼動開始)
    public static void Init()
    {
        if (PluginManager.IsInitialized) return;
        PluginManager.IsInitialized = true;
        string exeName = Process.GetCurrentProcess().ProcessName;

        // このGameObjectが本体
        GameObject gameObject = new GameObject("UnityInjector");

        // UnityInjectorが内臓しているDebugPluginを追加
        gameObject.AddComponent<UnityInjector.Plugins.DebugPlugin>();

        // プラグインを列挙して読み込み
        if (!Directory.Exists(Extensions.PluginsPath))
            Directory.CreateDirectory(Extensions.PluginsPath);
        List<Type> list = new List<Type>();
        list.AddRange(Directory.GetFiles(Extensions.PluginsPath, "*.dll").
            SelectMany(s => LoadPlugins_DLL(s, exeName)));

        // プラグインをAddComponentで追加
        foreach (Type componentType in list)
        {
            Console.WriteLine("Adding Component: '{0}'", componentType.Name);
            gameObject.AddComponent(componentType);
        }
        gameObject.SetActive(true);
    }

    // プラグインDLL読み込み
    static IEnumerable<Type> LoadPlugins_DLL(string dll, string exe)
    {
        List<Type> result = new List<Type>();
        Console.WriteLine(string.Format("Loading Assembly: '{0}'", dll));

        // UnityInjector.PluginBaseを継承しているクラスを列挙
        var types = Assembly.LoadFile(dll).GetTypes().
            Where(t => typeof(UnityInjector.PluginBase).IsAssignableFrom(t));
        foreach (Type type in types)
        {
            // [PluginFilter("...")]で指定されている実行ファイル名をチェック
            object[] customAttributes = type.GetCustomAttributes(false);
            List<string> list2 = customAttributes.
                OfType<UnityInjector.Attributes.PluginFilterAttribute>().
                Select(a => a.ExeName).ToList();
            if (!list2.Any() || list2.Contains(exe, StringComparer.InvariantCultureIgnoreCase))
            {
                // 条件を満たしているので追加
                result.Add(type);
            }
        }
        return result;
    }
}

UnityInjector.PluginBaseの擬似コード

public abstract class PluginBase : MonoBehaviour
{
    private IniFile _prefs;

    public string DataPath { get { return Extensions.UserDataPath; } }
    public string Name { get { return this.GetType().Name; } }
    public IniFile Preferences { get { return this._prefs ?? (this._prefs = this.ReloadConfig()); } }

    internal string ConfigPath
    {
        get
        {
            string[] strArray = new string[2];
            strArray[0] = this.DataPath;
            strArray[1] = Extensions.Asciify(this.Name) + ".ini";
            return Extensions.CombinePaths(strArray);
        }
    }

    protected IniFile ReloadConfig()
    {
        if (!File.Exists(this.ConfigPath))
            return this._prefs ?? new IniFile();
        IniFile ini = IniFile.FromFile(this.ConfigPath);
        if (this._prefs == null)
            return this._prefs = ini;
        this._prefs.Merge(ini);
        return this._prefs;
    }

    protected void SaveConfig()
    {
        this.Preferences.Save(this.ConfigPath);
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.