Unityを分かっていない人(主に筆者)向けのメモです
AddModsSliderへのプルリクエストの検討時にCM3D2-01さんから教わりました。
- 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.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);
- プラグイン間の呼び出しであれば、GameObject.GetComponentsInChildrenのほうが良いかも
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("ユニークなメソッド名")で呼び出すのが一番簡単
以下はエラー処理省略版です
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;
}
}
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);
}
}