✏️

【RPGツクール】RPGMakerUniteのアドオン(プラグイン)作成入門 中級編

2024/10/01に公開

そしてRPGMakerUniteへ・・・

RPGMakerUnite(以降RMU)ですが、旧来のツクールがついにUnityに統合されたということで
誰もがUnityのアセットゴリゴリに使えるの?最強じゃんと思いました。
2024年10月現在、Unityのアセットをポチポチ置くことは出来ていません(絶望)

公式は特に言っていませんが、まだアーリーアクセスなので未実装な機能が多々ある様です。
ボク達が欲しいと思う機能はだいたい未実装中です。

ただし、RMUはUnityで動いているフレームワークに過ぎないので
Unity側から魔改造すれば、アセットもエフェクトもポチポチ置けます。

RMUの凄いのはエディタも含めてほぼオープンソースになっています。
なので、足りない機能は自分で実装すれば良いだけです。

ただし

それはもはやUnityであってツクールではないよね

と思いまして、プラグイン(RMUではアドオン)でどうにか出来ないのか研究を開始しました。
RMU発売から1年半経過しましたが、今のところ公開されているプラグインはほぼ壊滅状態です。

あのMV/MZ時代の輝かしい栄光はどこへ行ってしまったのか・・

これに関しては諸説あると思いますが

  • JavaScriptではないのでイキリITエンジが参戦しない
  • MV/MZの様にScriptペラ1枚のエレガントなコードが書けない
  • IL2CPP問題
  • Unityが解らん

ここら辺が原因だと思います。
MV/MZプラグインは尾島さんの変態的天才コードを弄れるので
Scriptキッズ達がウッキウキでイキリ散らしてましたが
RMUのコードはC#で、かなり解読し易いソースになっています。
ただし、Unityなので根本的に難しいです。

そしてクリティカルに問題になっているのが、MV/MZはJavaScriptなので
プロトタイプベースで本体を差し替えできたのですが
RMUはいわゆるリフレクションで関数のポインタを差し替えるという豪気な手法になりました。
RMU開発陣もリフレクションサイコーみたいなノリだったと思いますが
なんとiOS用のIL2CPPビルドでセキュリティで引っ掛かるという致命的な問題が発覚しました。

AddonManager.cs
public static void ExchangeFunctionPointer(MethodInfo method0, MethodInfo method1) {
    unsafe
    {
        var functionPointer0 = method0.MethodHandle.Value.ToPointer();
        var functionPointer1 = method1.MethodHandle.Value.ToPointer();
        var tmpPointer = *((int*) new IntPtr(((int*) functionPointer0 + 1)).ToPointer());
        *((int*) new IntPtr(((int*) functionPointer0 + 1)).ToPointer()) = *((int*) new IntPtr(((int*) functionPointer1 + 1)).ToPointer());
        *((int*) new IntPtr(((int*) functionPointer1 + 1)).ToPointer()) = tmpPointer;
    }
}

まあ、ポインタ差し替えとかヤバさしか感じないですもんね。

この結果、プラグインではなくアドオンという呼称になって
本体側のソースに干渉できなくなって、イキリキッズ達も
ダンマリになってしまったという状況の様です。

※有志によって、プラグインソースを文字列置換で本体コードと差し替える荒業も発信されましたが
※やはりエレガントさに欠けると言わざる得ず、イキリキッズの支持は得られていない様です
※でもその執念、私は感動しました
役演亭 https://yakuentei.jp/article/gamedev/unity_rmu_addon/

で、今回試にステータス画面のアドオン(プラグイン)を作成してみたのですが

Addon
Addon_WindowBattleStatusForceFace

パッチで当てればこれだけなんですが

WindowBattleStatus.cs
-            int characterType = DataManager.Self().GetUiSettingDataModel().commonMenus[0].characterType;
+            int characterType = (int) MenuIconTypeEnum.FACE;

これをアドオンで実現しようとすると、こうなりました。

WindowBattleStatusForceFace.cs
namespace RPGMaker.Codebase.Addon
{
  public class WindowBattleStatusForceFace
  {
    Boolean isSet = false;
    public void doWindowBattleStatusForceFace()
    {
      if(!isSet)
      {
        System.Reflection.MethodInfo method0 = typeof(WindowBattleStatus).GetMethod("DrawAllItems");
        System.Reflection.MethodInfo method1 = this.GetType().GetMethod("DrawAllItems");      
        AddonInstance.ExchangeFunctionPointer(method1, method0);
        isSet = true;
      }
    }

    public virtual void DrawAllItems()
    {
      WindowBattleStatus windowSelectable = UnityEngine.Object.FindObjectOfType<WindowBattleStatus>();

      if (windowSelectable.selectors != null && windowSelectable.selectors.Any()) windowSelectable.selectors.ForEach(selector => selector?.canvas?.SetActive(false));
      if (windowSelectable.MaxItems() == 0) windowSelectable.ClearItem();

      int characterType = DataManager.Self().GetUiSettingDataModel().commonMenus[0].characterType;

      DataManager.Self().GetUiSettingDataModel().commonMenus[0].characterType = (int)MenuIconTypeEnum.FACE;
      for (var i = 0; i < windowSelectable.MaxItems(); i++)
      {
        windowSelectable.DrawItem(i);
      }
      DataManager.Self().GetUiSettingDataModel().commonMenus[0].characterType = characterType;
    }
  }
}

概念としてはこれで合ってるのですが
AddonInstance.ExchangeFunctionPointerはIL2CPPで使えないし
このDataManager.characterTypeはstaticで、DrawAllItems()は並列で実行されるため
上記コードはバグになります。

DataManager.Self().GetUiSettingDataModel().commonMenus[0].characterType

結論としてパッチで本体改造した方が良いとなりました。

WindowBattleStatus.cs
-            int characterType = DataManager.Self().GetUiSettingDataModel().commonMenus[0].characterType;
+            int characterType = (int) MenuIconTypeEnum.FACE;

以上です。
【RPGツクール】RPGMakerUniteのアドオン(プラグイン)作成入門 初級編

Discussion