【MOD作成】初心者MODDERが初めてのMOD作成から、Nexus Modへの投稿を行うまで【作成編】

MOD作成

この記事では、MOD作成初心者がインターネットの情報を集めながら自力でMOD作成を行い、大手のMOD共有サイトのNexus Modへ初投稿を行った記録です。

・1. 準備編(新しいタブで開く
・2. 作成編(この記事)
・3. 改造編(準備中)
・4. 投稿編(準備中)

第2回目の作成編では、MODをゲームに読み込ませる機能を作成したのち、実際にゲームを改造するプログラムを作っていきます。

C#の詳しい機能の説明は極力省き、できるだけ分かりやすく説明しています。
詳細を調べたい方は、インターネットで検索して調べてみてください!

1.まずはゲームにMODを読み込ませてみよう!

まずは、前回作成した「Plugin.cs」にコードを書いていきます。
最初は下記のようになっていると思います。

using System;

namespace SprintOn
{
    public class Plugin
    {

    }
}

5行目のclass(クラス)とは、C#のメソッド(関数)の親のようなものです。
Pluginという名前のクラスのみがある状態です。

次のように変更します。

using System;
using BepInEx;      //BepInExのライブラリへのアクセスを簡易化する

namespace SprintOn
{
    public class Plugin : BaseUnityPlugin   //PluginはBaseUnityPlugin継承する
    {

    }
}

2行目はBepInExの機能を用いるためのライブラリへのアクセスを簡易化するために追加します。

6行目はC#の継承という機能を用いています。
PluginにBaseUnityPluginを継承させるとこで、MOD作成に便利なメソッドをPluginで使うことができるため、必ず記述しましょう

次は、MODの基本情報を記述します。

using System;
using BepInEx;      //BepInExのライブラリへのアクセスを簡易化する

namespace SprintOn
{
    [BepInPlugin(PluginGuid, PluginName, PluginVer)]    //MODの情報(属性)を与える
    public class Plugin : BaseUnityPlugin   //PluginはBaseUnityPlugin継承する
    {
        private const string PluginGuid = "yourname.gyk.SprintOn";      //MODを識別するID
        private const string PluginName = "SprintOn";                   //MODの名前
        private const string PluginVer  = "1.0.0";                      //MODのバージョン
    }
}

9行目のGUID(Globally Unique ID)とはグローバル一意識別子といい、作成するMODを識別する固有のIDを付けます。「作成者の名前.ゲーム名.MOD名」が一般的なようです。

10行目は文字通り、MODの名前です。目的に合ったユニークな名前を付けましょう!

11行目はMODのバージョンです。最初は「1.0.0」で良いでしょう。

そして、6行目はPluginクラスに属性を与えるという機能です。
難しい説明を避けると、追加で情報を持たせるということで、ここではMODの基本情報をPluginに持たせています。

次はいよいよメソッドを作成します!

using System;
using BepInEx;      //BepInExのライブラリへのアクセスを簡易化する

namespace SprintOn
{
    [BepInPlugin(PluginGuid, PluginName, PluginVer)]    //MODの情報(属性)を与える
    public class Plugin : BaseUnityPlugin   //PluginはBaseUnityPlugin継承する
    {
        private const string PluginGuid = "yourname.gyk.SprintOn";      //MODを識別するID
        private const string PluginName = "SprintOn";                   //MODの名前
        private const string PluginVer  = "1.0.0";                      //MODのバージョン

        private void Awake()
        {

        }
    }
}

13行目にAwake()というメソッドを記述します。
このAwakeというメソッドは、ゲームとMODが読み込まれたら一度だけ実行されるメソッドです。
つまり、このMOD(プログラム)で一番最初に呼び出されるメソッドということになります。(C言語を学んだことがある方ならば、「void main( void )」というと分かるかもしれません)

それでは、Awakeメソッドの中にたったの1文を書き加えます。

using System;
using BepInEx;      //BepInExのライブラリへのアクセスを簡易化する

namespace SprintOn
{
    [BepInPlugin(PluginGuid, PluginName, PluginVer)]    //MODの情報(属性)を与える
    public class Plugin : BaseUnityPlugin   //PluginはBaseUnityPlugin継承する
    {
        private const string PluginGuid = "yourname.gyk.SprintOn";      //MODを識別するID
        private const string PluginName = "SprintOn";                   //MODの名前
        private const string PluginVer  = "1.0.0";                      //MODのバージョン

        private void Awake()
        {
            Logger.LogInfo("これが初めてのMODだ!!!");
        }
    }
}

15行目を追加しました。
この処理は、BepInExのデバッグ用の画面に文章を表示する処理です。

さあ、ここで「Ctrl + B」を押してこのプログラムを書き出しましょう!(上部タブの「ビルド」から行ってもOK)

書き出されたMODは、プロジェクト内の下記のパスにあります。
(あなたのプロジェクトへのパス)\SprintOn\SprintOn\bin\Debug\netstandard2.0

下の画像のような、「ファイル名.dll」というファイルがあなたが作成したMODです!

2.作成したMODを入れてゲームを起動してみる!

作成したMODをゲームフォルダに配置します。
Graveyard Keeperのインストールフォルダを開きましょう。

インストールフォルダを開いたら下記のようにパスをたどり、MODを配置します

(各々の保管場所)\Graveyard Keeper\BepInEx\plugins

また、ゲームを起動する前に下記パスの中にある「BepInEx.cfg」というファイルを開きます。(開き方はメモ帳でOK)

(各々の保管場所)\Graveyard Keeper\BepInEx\config

その中に、下のような記述があるため、「Enabled = true」に変更します。
これによって、ゲーム起動時にデバッグ用のログが表示されるコンソールを開くことができます。

[Logging.Console]

## Enables showing a console for log output.
# Setting type: Boolean
# Default value: false
Enabled = true

さあ、とうとうゲームを起動してみましょう!
ゲーム画面とは別に黒いウィンドウが表示され、下記のようなログが表示されれば成功です!

作成したMODが無事に読み込まれ、ログにメッセージが表示されています!

これであなたもMOD製作者の1人になりました!おめでとうございます!🎊

まだメッセージが表示されるだけでゲームは何も変わってない?
では次からは実際にゲームに影響する機能を加えていきます

3.ゲームを改造するコードを適用する機能を作成する

現在の状態では、MODがゲームに読み込まれただけの状態です。
次は、ゲームにMODの機能を追加するためのパッチを当てる部分を作成します。

下記のようにコードを追加しましょう。

using System;
using System.Reflection;
using BepInEx;      //BepInExのライブラリへのアクセスを簡易化する
using HarmonyLib;   //Harmonyのライブラリへのアクセスを簡易化する

namespace SprintOn
{
    [BepInPlugin(PluginGuid, PluginName, PluginVer)]    //MODの情報(属性)を与える
    public class Plugin : BaseUnityPlugin   //PluginはBaseUnityPlugin継承する
    {
        private const string PluginGuid = "yourname.gyk.SprintOn";      //MODを識別するID
        private const string PluginName = "SprintOn";                   //MODの名前
        private const string PluginVer  = "1.0.0";                      //MODのバージョン

        private void Awake()
        {
            Logger.LogInfo("これが初めてのMODだ!!!");

            Harmony Harmony = new Harmony(PluginGuid);              //MODのIDを与えたインスタンスを作成する
            Harmony.PatchAll(Assembly.GetExecutingAssembly());      //実行ファイルを取得しパッチを当てる
        }
    }
}

2行目と4行目は、機能を用いるためのライブラリへのアクセスを簡易化するために追加します。

19行目はパッチを当てるためのHarmonyクラスをインスタンス化しています。
パッチを当てる機能にMODの情報を与えているということです。

20行目ではゲームの実行ファイルを取得し、パッチを当てています。

これで、パッチを当てるためのコードは完成です!
「Plugin.cs」は一度これで完成とし、次から別ファイルにゲームを改造する内容を書いていきます!

4.ゲームを改造するコードを書いていく!

それでは新しいファイルを作成します。
下の画像のようにメニューを開き、「新しい項目」をクリックします。

ファイルの名前は「Patches.cs」とします。
下の画像のようになればOKです。

ここからは「Patches.cs」にコードを書き込んでいきます、と言いたいところですが、そのためにはこのMOD作成の手順で最も難しいと思われる、「改造したい機能に関して、ゲームで使われている関数を探し当てる」という作業を行う必要があります。

5.DnSpyを用いてゲーム内メソッドを解析する

正直に言いますと、この工程はかなり難易度が高いですが、順番に説明していきます。

5.1.MODがゲームを改造する仕組み

MOD作成ツールによって異なりますが、今回用いているBepInExとそれに付随するHarmonyというツールでは、大まかに次の3種類の方法でゲームの機能を改造します。

5.1.1.あるゲーム内メソッドが実行される直前に、実行されるメソッドを追加する「Prefix(プレフィックス)」

「Prefix」では次のような手順でMODが当てられます。

MOD適用前
ゲームが機能「A」を呼び出し → 機能「A」が実行 → ゲームに結果を反映

MOD適用後「Prefix」
ゲームが機能「A」を呼び出し → 機能「pre A」を実行 → 機能「A」が実行 → ゲームに結果を反映

例えば、
「もともと攻撃力を2倍にする」という機能に対して、
「PrefixによるMODで攻撃力に10を足してから2倍にしたい」場合は次のような手順を踏みます。

このように、元の機能の前に機能を追加して改造するのが「Prefix」です。

5.1.2.あるゲーム内メソッドが実行される直後に、実行されるメソッドを追加する「Postfix(ポストフィックス)」

「Postfix」では次のような手順でMODが当てられます。

MOD適用前
ゲームが機能「A」を呼び出し → 機能「A」が実行 → ゲームに結果を反映

MOD適用後「Prefix」
ゲームが機能「A」を呼び出し → 機能「A」が実行 → 機能「post A」を実行 → ゲームに結果を反映

例えば、
「もともと攻撃力を2倍にする」という機能に対して、
「攻撃力に2倍にしてからPostfixによるMODで攻撃力に10を足したい」場合は次のような手順を踏みます。

このように、元の機能の前に機能を追加して改造するのが「Prefix」です。

5.1.3.あるゲーム内メソッドを丸ごと、別のメソッドに変更する「Transpiler(トランスパイラー)」

「Transpiler」では次のような手順でMODが当てられます。

MOD適用前
ゲームが機能「A」を呼び出し → 機能「A」が実行 → ゲームに結果を反映

MOD適用後「Prefix」
ゲームが機能「A」を呼び出し → 機能「Trans A」が実行 → ゲームに結果を反映

例えば、
「もともと攻撃力を2倍にする」という機能に対して、
「攻撃力を2倍ではなくTranspilerによるMODで攻撃力に2を足したい」場合は次のような手順を踏みます。

このように、元の機能を完全に別の機能に変更して改造するのが「Transpiler」です。

ただし「Transpiler」によるMOD改造は、元のゲームに多大な影響を与えることや、他のMODとの機能競合が起きやすくなるためあまりおすすめしません。

また、「Prefix」と「Postfix」と「Transpiler」をそれぞれ組み合わせて改造することもできます。

このように、MODでゲーム内メソッドを改造するためには、そのメソッドの名前はもちろんのこと、どのような機能を持っているかを必ず調べる必要があります。

5.2.MODで改造したいゲーム内メソッドを調べる

ゲーム内メソッドを調べるためには、「1. 準備編(新しいタブで開く)」でインストールした「DnSpy」を用います。

「DnSpy」を起動し、「Graveyard Keeper」のインストールフォルダを開きます。

(それぞれの保存場所)\Graveyard Keeper\Graveyard Keeper_Data\Managed
の中にある、「Assembly-CSharp.dll」を「DnSpy」にドラッグアンドドロップします。

この「Assembly-CSharp.dll」というのが、ゲーム制作者が作成したゲーム内メソッドをすべて含んでいるファイルであり、この中から目的のメソッドを探し当てます。

まずは、黄色い四角で囲まれた「Search」の検索ボックスに、今回改造したい機能に関係しそうな単語を入れていきます。

今回は移動速度に関する改造なので、「Movement」や「Speed」などの言葉を入れます。

そうすると、赤色の四角の中にその語句に関係する機能が表示されるため、一つ一つ表示して目的の機能を見つけます。

緑色の四角の検索タイプを「Method」を選択することで、メソッドのみを表示して多少見つけやすくなります。

この作業が今回の解説の中で最も難しい手順です。
コードを読む力、検索する能力と根気が求められる作業です。

今回のMODでは、「UpdateMovement」という関数が移動速度に関する機能であることが分かったため、この機能をもとに改造していきます。

6.改造する内容をプログラミングしていく

Patchesの中身は現在下のようになっていると思います。

using System;
using System.Collections.Generic;
using System.Text;

namespace SprintOn
{
    public static class Patches
    {
    }
}

まずは次のように書き足します。

using System;
using System.Collections.Generic;
using System.Text;
using HarmonyLib;

namespace SprintOn
{
    [HarmonyPatch]      //PatchAllで適用されるための属性を与える
    public static class Patches
    {
        [HarmonyPostfix]        //Postfixで改造するという属性を与える
        [HarmonyPatch(typeof(MovementComponent), "UpdateMovement")]                             //パッチを当てるメソッドを指定する
        public static void MovementComponentUpdatePatch(MovementComponent __instance)           //MODで改造する内容のメソッド
        {
        }
    }
}

4行目は、BepInExに付随するHarmonyというMOD改造に便利なライブラリへのアクセスを簡易化するために追加します。

8行目は、Harmonyのパッチを当てるクラスであるという属性を付けています。「Plugin.cs」で記述したPatchAllというメソッドでパッチが当たる対象となります。

11行目は、今回は「Postfix」で改造するという属性を与えています。

12行目は、「DnSpy」で解析した改造対象となる機能を指定するための属性を与えています。

13行目は、実際に改造したい機能の内容を書いていくメソッドです。メソッド名は特に指定されていないので、分かりやすい名前を付けましょう。
引数に与えた「MovementComponent __instance」は、ちょっと難しいため下のボックスに記述しています。

「MovementComponent 」というクラスは移動に関する機能であり、プレイヤーやNPC、ゾンビなど、様々なオブジェクトが用いるクラスです。

「MovementComponent __instance」は、このクラスを誰が呼び出したか?
ということを引数にすることができるHarmonyの機能です。

「__instance」を用いるためには、元になるクラスが静的でない(Staticでない)必要があります。

さあ、これで改造内容をコーディングする準備が整いました。
それでは、まずはプレイヤーの通常の移動速度を上昇させるようなコードを書きます。

using System;
using System.Collections.Generic;
using System.Text;
using HarmonyLib;

namespace SprintOn
{
    [HarmonyPatch]      //PatchAllで適用されるための属性を与える
    public static class Patches
    {
        [HarmonyPostfix]        //Postfixで改造するという属性を与える
        [HarmonyPatch(typeof(MovementComponent), "UpdateMovement")]                             //パッチを当てるメソッドを指定する
        public static void MovementComponentUpdatePatch(MovementComponent __instance)           //MODで改造する内容のメソッド
        {
            if (__instance.wgo.is_player)       //対象をプレイヤーのみに指定
            {
                __instance.SetSpeed(10f);       //移動速度を設定
            }
        }
    }
}

15~18行目がプレイヤーの通常の移動速度を上昇させる機能です。

15行目は、移動速度の上昇が適用されるのがプレイヤーのみとなるようにしています。
16行目は、移動速度が10になるように設定しています。(ちなみに通常の歩き速度は3.3)

それではビルドを行い、出力されたdllファイルを下記パスに格納しましょう!(上書きでOK)
(各々の保管場所)\Graveyard Keeper\BepInEx\plugins

そしたらゲームを起動して移動してみましょう!

うおおおお!爆速移動!

これであなたもゲームを改造できるMODDERとなりました!

7.今回のまとめと次回の内容

今回はら実際にプログラミングを行って「プレイヤーの移動速度を変更する」というMODを作成しました。

・MODを読み込ませる方法
・BepInExでログを表示する方法
・ゲーム内機能を解析して改造したい個所を探すこと
・実際にゲームを改造するコードを作成すること

これらの手順はUnityでのMOD作成ならば活用できると思います!

次回から今回作成したMODに対して、下記の改造を加えます!

・ボタンを押すと移動速度が変わる
・走っているときはスタミナを消費する
・ゲーム内でMODパラメータを変更する

コメント

タイトルとURLをコピーしました