SaGa SCARLET GRACE: AMBITIONS™

SaGa SCARLET GRACE: AMBITIONS™

Not enough ratings
サガスカで始めたBepInEx
By penta0
このガイドではサガスカを使って基本的なBepInExのプラグイン作成方法を説明する。
ただしMODについて何も分かっていない門外漢が書いた個人用の備忘録なので眉に唾を付けながら読んで欲しい。
   
Award
Favorite
Favorited
Unfavorite
初めに
このガイドではサガスカを使って基本的なBepInExのプラグイン作成方法を説明する。
ただしMODについて何も分かっていない門外漢が書いた個人用の備忘録なので眉に唾を付けながら読んで欲しい。
準備
BepInExのプラグインを作成して実行するのに必要なアプリケーションは下記からダウンロードできる。
  1. BepInEx
    このページ [github.com]の「Releases」からダウンロードする。
    このガイドではBepInEx_win_x64_5.4.23.2.zipを使用した。
    これはBepInEx 5のパッケージである。
  2. .NET SDK
    このページ [dotnet.microsoft.com]の「.NET SDK x64 をダウンロードする」から最新の.NET SDKをダウンロードして実行する事でインストールができる。
    このガイドではdotnet-sdk-9.0.101-win-x64.exeを使用した。
  3. Visual Studio Community(任意)
    これだけでゲームのソースの確認からプラグインの編集・ビルドまでを実行できる。
    このページ [visualstudio.microsoft.com]の「無料ダウンロード」からVisualStudioSetup.exeをダウンロードして実行する事でインストールができる。
    このガイドでは17.12.3のVisual Studio Community 2022を使用した。
    ただしVisual Studioのエディタを使わないのであれば必須ではない。
    プラグインのビルドも.NET SDKだけあれば実行可能である。
  4. dnSpy(任意)
    Visual Studioを使わない場合はこれのようなゲームのdllからソースを確認できるツールが必要になる。
    このページ [github.com]の「Releases」からダウンロードする。
    このガイドではv6.1.8のdnSpy-net-win64.zipを使用した。
BepInExとは
BepInExはUnityで制作されたソフトウェアに対してそれそのものを変更することなく外部からパッチを当てたりして本来とは違う動作をさせるアプリケーションである。
つまり簡単に書くとUnityソフトウェア用のMODを作れるアプリになり、そのMODを複数共存させることが出来るのも利点になる。
サガスカにBepInExを導入する
サガスカのゲームフォルダにダウンロードしたBepInExのzipファイルを解凍する。


この状態でSaGaSCARLETGRACE.exeを実行するとBepInExの初期設定が行われ、BepInExフォルダにconfigなどのフォルダが作られる。


これはサガスカの実行時にBepInExのzipファイルに含まれていたwinhttp.dllが読み込まれ、それによりdoorstop_config.iniに書いてある設定が読み込まれ、そこに書かれている通りにBepInExフォルダ内のファイルが読み込まれて実行された事による結果である。

これは Unity Doorstop [github.com]というアプリケーションを利用した動作らしい。
つまりサガスカでBepInExを使ってMODを適用する場合以下の様な処理の流れになっている。

サガスカ→Unity Doorstop→BepInEx→各種MOD
プラグインの作成と実行方法
プラグインとは「サガスカのゲームフォルダ\BepInEx\plugins」に配置してゲーム本来とは異なる動作をさせるファイルの事である。

デバッグ用コンソールの表示
「サガスカのゲームフォルダ\BepInEx\config\BepInEx.cfg」をメモ帳などのテキストエディタで開いて、以下を変更して保存しエディタを閉じる。
  • [Logging.Console]の「Enabled」を「true」に書き換える。
  • [Logging.Console]の「LogLevels」に「, Debug」を追記する。
[Logging.Console] ## Enables showing a console for log output. # Setting type: Boolean # Default value: falseEnabled = true ## Which log levels to show in the console output. # Setting type: LogLevel # Default value: Fatal, Error, Warning, Message, Info # Acceptable values: None, Fatal, Error, Warning, Message, Info, Debug, All # Multiple values can be set at the same time by separating them with , (e.g. Debug, Warning) LogLevels = Fatal, Error, Warning, Message, Info, Debug

この時エディタを開いたままにしてBepInEx.cfgをエディタに占有させているとBepInExがエラーになるので注意。
なおBepInExがエラーになった場合はサガスカフォルダの直下にpreloader_20250104_150520_996.logのようなエラーログが作成される。

この状態でサガスカを起動すると下図のようなコンソールウィンドウが開く。


BepInExのテンプレートのインストール
Windowsのプログラムの検索で「cmd」と入力してコマンドプロンプトを開き、BepInExのテンプレートをインストールする。
これによりBepInExのプラグインプロジェクトをコマンドひとつで作成できるようになる。

dotnet new install BepInEx.Templates::2.0.0-be.4 --nuget-source https://nuget.bepinex.dev/v3/index.json
*途中で改行されているように見えるかもしれないが実際は1行のコマンドである。

プラグインのプロジェクト作成に必要な情報の調査
プラグインのプロジェクトを作成するにはまずどのテンプレートで作成するかを調べなければならない。
Unity製ゲームにはMonoとIL2CPPのものがあるのだが、サガスカのゲームフォルダSaGaSCARLETGRACE_Dataを確認してみると「Managed」フォルダが存在し「il2cpp_data」フォルダが存在しないのでサガスカはMonoであると分かる。
*今回インストールしたのはBepInEx 5だがそもそもBepInEx 6でないとIL2CPPのゲームのプラグインは作成できない事には後で気づいた。

よってMonoのテンプレートを使うべきだと分かったが、このテンプレートを使うには
以下の情報が必要になる。
  • プラグインの.NET ターゲット フレームワーク(以下<TFM>)
  • ゲームのUnityバージョン(以下<Unity>)

サガスカの場合<TFM>は「net35」、<Unity>は「2017.2.1」になる。
<TFM>の調べ方については複雑だしこのガイドを書いた後に変わるかもしれないので書かない事にする。
必要なら このページ [docs.bepinex.dev]を参照してほしい。
<Unity>はサガスカのゲームフォルダ直下にあるUnityPlayer.dllのプロパティの詳細タブに書いてある値を使った。

プラグインのプロジェクトを作成
コマンドプロンプトで適当なフォルダに移動して以下のコマンドを入力すればプラグインのプロジェクトを作成できる。
dotnet new bepinex5plugin -n MyFirstPlugin -T <TFM> -U <Unity> dotnet restore MyFirstPlugin
*ネットでテンプレートのインストール方法を調べると「bepinex5plugin」の代わりに「bep6plugin_unity_mono」と書いているページがあるがこれだとBepInEx 6用のプラグインのテンプレートを使ってしまう。
BepInEx 6のプラグインをBepInEx 5で読み込もうとしてもエラー表示すらなくプラグインが読み込まれないだけになってしまうのでBepInExとテンプレートのバージョンは必ず合わせなければならない。

ただしこのMyFirstPluginはサンプルのプロジェクト名なので<TFM>と<Unity>も合わせて適当な値に書き換える必要がある。
今回は以下のコマンドを使う。
dotnet new bepinex5plugin -n SaGaScarletGracePlugin -T net35 -U 2017.2.1 dotnet restore SaGaScarletGracePlugin

コマンドが無事実行されると以下の様にSaGaScarletGracePluginフォルダが作成され、その中にプラグインのプロジェクトファイルが配置される。


プラグインのビルドと実行
コマンドプロンプトでSaGaScarletGracePluginフォルダに移動して下記のコマンドを入力すると「SaGaScarletGracePlugin\bin\Debug\net35」にプラグインファイル「SaGaScarletGracePlugin.dll」が作成される。
dotnet build

後はこの「SaGaScarletGracePlugin.dll」を「サガスカのゲームフォルダ\BepInEx\plugins」に配置してゲームを実行すれば下記の様にプラグインが読み込まれゲームと同時に開いたコンソールに「[Info :My first plugin] Plugin SaGaScarletGracePlugin is loaded!」のメッセージが表示されるようになる。


このビルドからコピーの作業をプラグインの修正の度に毎回行うのは面倒なので以下の様なバッチファイルを作成してSaGaScarletGracePluginフォルダ直下に配置しておけば、これを実行するだけでビルドとプラグインのテストができて便利である。

build_SaGaScarletGracePlugin.bat
echo off set gameDir="F:\SteamLibrary\steamapps\common\SaGaSCARLETGRACE" set gameExe="SaGaSCARLETGRACE.exe" set pluginName="SaGaScarletGracePlugin" echo on dotnet build copy ".\bin\Debug\net35\%pluginName%.dll" "%gameDir:~1,-1%\BepInEx\plugins" "%gameDir:~1,-1%\%gameExe%"

なおVisual Studioで「SaGaScarletGracePlugin.csproj」を開いて画面上部メニューの「ビルド(B)→SaGaScarletGracePlugin のビルド(U)」を選択してもビルドできるが、batファイルでプラグインのビルドからゲームの起動まで自動化した方が便利なので推奨しない。
Harmonyを使用したプラグインの作成
HarmonyとはUnityなどの中間バイトコード言語であるCILにコンパイルされるすべての言語で動作するアプリケーションの実行時に動的に動作を変更する為のライブラリである。
これを使う事でゲームファイルを書き換えることなくゲームの動作を変更できる。
BepInExにはHarmonyの派生であるHarmonyXが組み込まれているので特に他のファイルをダウンロードしたりしなくてもこのライブラリが利用できる。
Harmonyについてはあまりに複雑なのでここでは詳しく説明する事は止めてとりあえずゲームの動作を変更してみる程度のみを説明する。
より詳しく知りたい人は このページ [github.com]などを参照すると良い。

Harmonyの有効化
プラグインでHarmonyを有効にするにはSaGaScarletGracePluginフォルダ直下に自動で作成されている「Plugin.cs」に対して以下の様に追記するだけで良い。
using BepInEx; using BepInEx.Logging; using HarmonyLib; // 追加したコード namespace SaGaScarletGracePlugin; [BepInPlugin(MyPluginInfo.PLUGIN_GUID, MyPluginInfo.PLUGIN_NAME, MyPluginInfo.PLUGIN_VERSION)] public class Plugin : BaseUnityPlugin { internal static new ManualLogSource Logger; private void Awake() { // Plugin startup logic Logger = base.Logger; Logger.LogInfo($"Plugin {MyPluginInfo.PLUGIN_GUID} is loaded!"); // 追加したコード ↓ Harmony.CreateAndPatchAll(typeof(Patch)); Logger.LogDebug($"Created Harmony!"); // 追加したコード ↑ } }

これをコンパイルしてゲームを実行し、コンソールに「Created Harmony!」のメッセージが表示されればHarmonyは問題なく有効化されている。
プラグインプロジェクトの設定
本格的なプラグインの作成の前にSaGaScarletGracePluginプロジェクトの設定変更を行う。

Visual Studioを使う場合
Visual Studioを起動して画面上部メニューの「ファイル(F)→開く(O)→プロジェクト/ソリューション(P)」から「SaGaScarletGracePluginフォルダ\SaGaScarletGracePlugin.csproj」を選択しSaGaScarletGracePluginプロジェクトを開く。

プラグイン名の変更
プラグイン名が「My first plugin」のままだと分かりにくいので「SaGa ScarletGrace Plugin」に変更する。
  1. Visual Studioの画面右に表示されているはずのソリューションエクスプローラーで「SaGaScarletGracePlugin」を右クリックし「プロパティ」を選択する。
  2. 「パッケージ→全般→製品」に表示されている「My first plugin」を「SaGa ScarletGrace Plugin」に変更する。
Visual Studioで行った変更は明示的に保存していないとファイルに書き込まれていない場合があるので、そのままビルド用batファイルを実行するなどしてVisual Studio以外からビルドすると変更以前の状態でビルドされる事があるので注意。

ゲームプログラムの参照設定を追加
この設定を行わないとHarmonyで動作変更する対象のメソッドが見つからないといった問題がおこる。
  1. ソリューションエクスプローラーで「SaGaScarletGracePlugin→依存関係」を右クリックし「アセンブリ参照の追加(A)」から参照マネージャーを開く。

  2. 「参照」を押下して下記のファイルを追加する。
    • サガスカのゲームフォルダ\SaGaSCARLETGRACE_Data\Managed\Assembly-CSharp.dll
    • サガスカのゲームフォルダ\SaGaSCARLETGRACE_Data\Managed\Assembly-CSharp-firstpass.dll

Visual Studioを使わない場合
「SaGaScarletGracePluginフォルダ\SaGaScarletGracePlugin.csproj」をエディタで開き、変更して保存するだけでもプロジェクト設定は変更できる。

プラグイン名の変更
「<PropertyGroup>」の「<Product>」を「SaGa ScarletGrace Plugin」に変更する。
<PropertyGroup> <TargetFramework>net35</TargetFramework> <AssemblyName>SaGaScarletGracePlugin</AssemblyName> <Product>SaGa ScarletGrace Plugin</Product> <Version>1.0.0</Version> <AllowUnsafeBlocks>true</AllowUnsafeBlocks> <LangVersion>latest</LangVersion> <RestoreAdditionalProjectSources> https://api.nuget.org/v3/index.json; https://nuget.bepinex.dev/v3/index.json; https://nuget.samboy.dev/v3/index.json </RestoreAdditionalProjectSources> <RootNamespace>SaGaScarletGracePlugin</RootNamespace> </PropertyGroup> </ItemGroup>

ゲームプログラムの参照設定を追加
「<Project>」直下に下記の「<ItemGroup>」を追加する。
<ItemGroup> <Reference Include="Assembly-CSharp"> <HintPath>サガスカのゲームフォルダ\SaGaSCARLETGRACE_Data\Managed\Assembly-CSharp.dll</HintPath> </Reference> <Reference Include="Assembly-CSharp-firstpass"> <HintPath>サガスカのゲームフォルダ\SaGaSCARLETGRACE_Data\Managed\Assembly-CSharp-firstpass.dll</HintPath> </Reference> </ItemGroup>
サガスカのReadyGoカウントダウン演出カットプラグイン作成
実は私はサガスカをPlayStation Vita版の発売日に買っているのだが、この時はプレイし始めて数日で投げてしまっている。
何故ならReadyGoのカウントダウン演出があまりにも遅くて耐えられなかったからである。
この演出はSteam版になっても高速化されたとはいえ残ってしまっている。
気に入らない。
なのでこの演出をカットするプラグインを作ってみる。

サガスカのソースの調査
以下の方法でソースを検索してReadyGoの処理を行っている所を見つける。
Visual Studioを使う場合
  1. Visual Studioを起動してSaGaScarletGracePluginプロジェクトを開く。
    この時ゲームプログラムの参照設定は終わっているものとする。
  2. 画面上部メニューの「編集(E)→検索と置換(F)→フォルダーを指定して検索(I)」を開き、「複数のファイル内を検索」タブで一番上の入力欄に「readygo」と入力して「すべて検索」を押下する。

dnSpyを使う場合
  1. dnSpyを起動して下記のファイルを開く。
    サガスカのゲームフォルダ\SaGaSCARLETGRACE_Data\Managed\Assembly-CSharp.dll
  2. 画面上部メニューの「Edit→Search Assemblies」を開くと画面右下のSearchボックスにフォーカスが当たるので、「readygo」と入力する。

以上のどちらかをすると検索結果にサガスカの「readygo」という文字列を使っている箇所が全て表示されるので流し読みしてReadyGoの処理を行っている所を見つける。
ここはもう各自何となく分かれ!としか言えないので以下の「BattleManager.InitCutChange()」でReadyGoの処理を行っている事が分かった所から説明を続ける。
private void InitCutChange() { GameObject sequencePrefab = Sequence.GetSequencePrefab(4); GameObject gameObject = UnityEngine.Object.Instantiate(sequencePrefab); gameObject.name = sequencePrefab.name; TTSequence_old component = gameObject.GetComponent<TTSequence_old>(); component.ttInit(casterIsEnemy: false); component.isReadyGoLogo = true; LocalManagerBase<TeaTimeSequenceManager>.Instance.addSequence(component); stepTimer = 0.5f; minCutChangeNum = CalcCutChangeCount(); SelectCutChangeChara(); readyCutNum = 1; Sequence.playCameraSequence(BattleSequence.eSequenceType.readyGoCutC); readyGoCutChangeTime = Time.realtimeSinceStartup; ManagerBase<SoundManager>.Instance.se.Play(SoundLabel.SE_M_BTL_UI_READY01); ((MonoBehaviour)(object)this).StartCoroutine(ReadyGoCutChangeCoroutine()); UpdateMenuBPGauge(eChangeBPType.none); }
どうやら上記の「ReadyGoCutChangeCoroutine()」というコルーチン呼び出しの所でカウントダウン演出が行われているので、これを呼び出さないようにすれば良さそうだ。

ではこれをどうやって実現するかだが、まずHarmonyではどのように既存のプログラムの動作変更をするかを知らなければならない。

Harmonyによるメソッドのパッチ当て方法
Harmonyにはメソッドのパッチ当て方法としてPrefixとPostFixの2つの方法が用意されている。
なおどのメソッドにパッチを当てるかはアノテーションで指定するのだが、これの指定方法もいくつか用意されているので下記の例とは違う書き方でプラグインが作られる場合もある。
より詳しく知りたい人は このページ [harmony.pardeike.net]などを参照すると良い。

Prefix
パッチ当て対象のメソッドの実行前に呼び出されて実行されるメソッドで、これの返り値がfalseだった場合はパッチ当て対象のメソッドが実行されない事になっている。
なので元のメソッドの動作を書き換えたい場合もこちらを使う。
アノテーション「[HarmonyPatch(typeof(BattleManager), "InitCutChange")]」で「BattleManager」クラスの「InitCutChange」メソッドにパッチを当てることを表している。
メソッド名の「InitCutChangePrefix」はこの名前でなくても構わないので分かりやすい名前であれば何でも良い。
[HarmonyPatch(typeof(BattleManager), "InitCutChange")] [HarmonyPrefix] public static bool InitCutChangePrefix(ref BattleManager __instance) { return true; }

Postfix
パッチ当て対象のメソッドの実行後に呼び出されて実行されるメソッドである。
こちらには返り値が無い。
[HarmonyPatch(typeof(BattleManager), "InitCutChange")] [HarmonyPostfix] public static void InitCutChangePostfix(ref BattleManager __instance) { }

これらのPrefix・Postfix には引数「__instance」があるが、これは特殊な名前の引数で元のメソッドが呼び出されたインスタンスが実行時に渡される。
この引数は必要なければ書かなくても構わない。
またPrefix ・Postfix には他にも特殊な名前の引数を必要に応じて追加できるが、詳しくは このページ [github.com]などを参照すると良い。

以上を踏まえてどうやって「ReadyGoCutChangeCoroutine()」を呼び出さないようにするかを考えると
  1. 「BattleManager.InitCutChange()」の内容を一旦Prefixメソッドに全て移植した上でfalseを返して本来のメソッドの処理が動かない様にする。
  2. Prefixメソッドに移植した「ReadyGoCutChangeCoroutine()」の呼び出し部分をコメントアウトする。
とすれば良さそうだ。

そのように作業した結果が以下になる。

Harmonyによるプラグイン
public class KillReadyGoCountDown { [HarmonyPatch(typeof(BattleManager), "InitCutChange")] [HarmonyPrefix] public static bool InitCutChangePrefix(ref BattleManager __instance) { Plugin.Logger.LogDebug("InitCutChangePrefix"); // internal BattleSequence Sequence => sequence; // ↑のようなメンバはプロパティなのでFieldではなくPropertyを使う。 BattleSequence Sequence = Traverse.Create(__instance).Property("Sequence").GetValue<BattleSequence>(); GameObject sequencePrefab = Traverse.Create(Sequence).Method("GetSequencePrefab", new object[] { 4, }).GetValue<GameObject>(); GameObject gameObject = UnityEngine.Object.Instantiate(sequencePrefab); gameObject.name = sequencePrefab.name; TTSequence_old component = gameObject.GetComponent<TTSequence_old>(); component.ttInit(casterIsEnemy: false); component.isReadyGoLogo = true; LocalManagerBase<TeaTimeSequenceManager>.Instance.addSequence(component); Traverse.Create(__instance).Field("stepTimer").SetValue(0.5f); Traverse.Create(__instance).Field("minCutChangeNum").SetValue(Traverse.Create(__instance).Method("CalcCutChangeCount").GetValue<int>()); // MethodはGetValue()を呼び出さないと実行されない。 Traverse.Create(__instance).Method("SelectCutChangeChara").GetValue(); Traverse.Create(__instance).Field("readyCutNum").SetValue(1); Traverse.Create(Sequence).Method("playCameraSequence", new object[] { // internal enumを参照する。 Traverse.Create(typeof(BattleSequence)).Type("eSequenceType").Field("readyGoCutC").GetValue(), }); Traverse.Create(__instance).Field("readyGoCutChangeTime").SetValue(Time.realtimeSinceStartup); // nullをobject ChangeType(object value, Type conversionType)でPlayInfoにキャストしてもTraverse Method(string name, params object[] arguments)では // ulong Play(SoundLabel label, PlayInfo playInfo = null, bool isFixPitch = false)の呼び出しとして認識してくれないので // Typeの配列も渡す方のTraverse Method(string name, Type[] paramTypes, object[] arguments = null)を使わなければならない Traverse.Create(Traverse.Create(ManagerBase<SoundManager>.Instance).Property("se").GetValue()).Method("Play", new Type[] { typeof(SoundLabel), // アクセスが禁止されたインナークラスSoundManager.SE.PlayInfoの型を参照する。 Traverse.Create(ManagerBase<SoundManager>.Instance).Type("SE").Type("PlayInfo").GetValue<Type>(), typeof(bool), }, new object[] { SoundLabel.SE_M_BTL_UI_READY01, null, false, }); // このコルーチン呼び出しでReadyGoのカウントダウン処理が行われている。 // ReadyGoの演出時間の大半はこれのせいなのでコメントアウトする。 //((MonoBehaviour)(object)__instance).StartCoroutine(Traverse.Create(__instance).Method("ReadyGoCutChangeCoroutine").GetValue<IEnumerator>()); // 実の所これより上の処理は必要ないので削除して良いのだが説明にちょうどいいので残している。 // この変数設定をしないと次のシーンで処理が永遠に止まる。 Traverse.Create(__instance).Field("cutChangeStep").SetValue(2); // デフォルト引数があるメソッドを呼び出す場合でも引数の省略はできない Traverse.Create(__instance).Method("UpdateMenuBPGauge", new object[] { eChangeBPType.none, 0, }); // 本来のInitCutChangeは呼び出さない。 return false; } }

元のメソッドに比べて異常に複雑な書き方になっているのに気付いたと思うが、これはサガスカのソースがinternalやprivate指定を多用しているため、外部のPrefixメソッドからメンバをそのままでは参照できなくなっているせいである。
そこでHarmonyに用意されているTraverseクラスを使って強引に参照している訳だ。
詳しい説明はコメントに書いているので読んで欲しい。

これをビルドしできたプラグインをサガスカのBepInExの中に入れて実行した結果が以下になる。

ReadyGoの表示が完了した直後に行動が始まっているのが分かると思う。
サガスカのReadyGoカウントダウン演出カットプラグインのソース全文
これをSaGaScarletGracePluginフォルダ直下に自動で作成されている「Plugin.cs」にペーストしてビルドすればプラグインが作成されるはずだ。
using BepInEx; using BepInEx.Logging; using HarmonyLib; using System; using System.Collections; using System.Diagnostics; using System.Reflection; using UnityEngine; using UnityEngine.SocialPlatforms; using static BattleManager; namespace SaGaScarletGracePlugin; [BepInPlugin(MyPluginInfo.PLUGIN_GUID, MyPluginInfo.PLUGIN_NAME, MyPluginInfo.PLUGIN_VERSION)] public class Plugin : BaseUnityPlugin { internal static new ManualLogSource Logger; private void Awake() { // Plugin startup logic Logger = base.Logger; Logger.LogInfo($"Plugin {MyPluginInfo.PLUGIN_GUID} is loaded!"); // パッチを有効化する。 Harmony.CreateAndPatchAll(typeof(KillReadyGoCountDown)); Plugin.Logger.LogDebug($"KillReadyGoCountDown is activated!"); } } public class KillReadyGoCountDown { [HarmonyPatch(typeof(BattleManager), "InitCutChange")] [HarmonyPrefix] public static bool InitCutChangePrefix(ref BattleManager __instance) { Plugin.Logger.LogDebug("InitCutChangePrefix"); // internal BattleSequence Sequence => sequence; // ↑のようなメンバはプロパティなのでFieldではなくPropertyを使う。 BattleSequence Sequence = Traverse.Create(__instance).Property("Sequence").GetValue<BattleSequence>(); GameObject sequencePrefab = Traverse.Create(Sequence).Method("GetSequencePrefab", new object[] { 4, }).GetValue<GameObject>(); GameObject gameObject = UnityEngine.Object.Instantiate(sequencePrefab); gameObject.name = sequencePrefab.name; TTSequence_old component = gameObject.GetComponent<TTSequence_old>(); component.ttInit(casterIsEnemy: false); component.isReadyGoLogo = true; LocalManagerBase<TeaTimeSequenceManager>.Instance.addSequence(component); Traverse.Create(__instance).Field("stepTimer").SetValue(0.5f); Traverse.Create(__instance).Field("minCutChangeNum").SetValue(Traverse.Create(__instance).Method("CalcCutChangeCount").GetValue<int>()); // MethodはGetValue()を呼び出さないと実行されない。 Traverse.Create(__instance).Method("SelectCutChangeChara").GetValue(); Traverse.Create(__instance).Field("readyCutNum").SetValue(1); Traverse.Create(Sequence).Method("playCameraSequence", new object[] { // internal enumを参照する。 Traverse.Create(typeof(BattleSequence)).Type("eSequenceType").Field("readyGoCutC").GetValue(), }); Traverse.Create(__instance).Field("readyGoCutChangeTime").SetValue(Time.realtimeSinceStartup); // nullをobject ChangeType(object value, Type conversionType)でPlayInfoにキャストしてもTraverse Method(string name, params object[] arguments)では // ulong Play(SoundLabel label, PlayInfo playInfo = null, bool isFixPitch = false)の呼び出しとして認識してくれないので // Typeの配列も渡す方のTraverse Method(string name, Type[] paramTypes, object[] arguments = null)を使わなければならない Traverse.Create(Traverse.Create(ManagerBase<SoundManager>.Instance).Property("se").GetValue()).Method("Play", new Type[] { typeof(SoundLabel), // アクセスが禁止されたインナークラスSoundManager.SE.PlayInfoの型を参照する。 Traverse.Create(ManagerBase<SoundManager>.Instance).Type("SE").Type("PlayInfo").GetValue<Type>(), typeof(bool), }, new object[] { SoundLabel.SE_M_BTL_UI_READY01, null, false, }); // このコルーチン呼び出しでReadyGoのカウントダウン処理が行われている。 // ReadyGoの演出時間の大半はこれのせいなのでコメントアウトする。 //((MonoBehaviour)(object)__instance).StartCoroutine(Traverse.Create(__instance).Method("ReadyGoCutChangeCoroutine").GetValue<IEnumerator>()); // 実の所これより上の処理は必要ないので削除して良いのだが説明にちょうどいいので残している。 // この変数設定をしないと次のシーンで処理が永遠に止まる。 Traverse.Create(__instance).Field("cutChangeStep").SetValue(2); // デフォルト引数があるメソッドを呼び出す場合でも引数の省略はできない Traverse.Create(__instance).Method("UpdateMenuBPGauge", new object[] { eChangeBPType.none, 0, }); // 本来のInitCutChangeは呼び出さない。 return false; } }