はじめに
Craftopiaのmod開発をしたいと思っていても、情報が多すぎて何から始めたらよいかわからない人向けです。
注意事項
自己責任でお願いします。基本的にはソロ向けです。マルチプレイヤーでは機能しません。
対象
- プログラミングをある程度理解している人(C#は癖がないので他の言語が読み書きできれば問題なし)
- Windowsを利用している人
開発環境の構築
dnSpyの導入
dnSpyはダイナミックリンクライブラリを解析して、コードを復元するツールです。変更したい処理を探すときに利用します。
まず、dnSpyをダウンロードします。ダウンロードするファイルは、dnSpy-net472.zipです。ダウンロードして任意の場所に展開してください。中身のdnSpy.exeを実行して画面が表示されれば成功です。
では、クラフトピアの中身を見てみましょう。メニューのFile欄にあるOpenからクラフトピアのプログラム(~/SteamLibrary/steamapps/common/Craftopia/Craftopia_Data/Managed/AD__Overcraft.dll)を開きましょう。沢山表示されますが、クラフトピアのプログラムは名前空間”Oc”のしたにあります。Ctl + Shift + K で検索することができます。試しに、アイテムの情報が格納されているクラス(ItemData)の中身を見てみましょう。IdやPriceなどが確認できると思います。
まず、dnSpyをダウンロードします。ダウンロードするファイルは、dnSpy-net472.zipです。ダウンロードして任意の場所に展開してください。中身のdnSpy.exeを実行して画面が表示されれば成功です。
では、クラフトピアの中身を見てみましょう。メニューのFile欄にあるOpenからクラフトピアのプログラム(~/SteamLibrary/steamapps/common/Craftopia/Craftopia_Data/Managed/AD__Overcraft.dll)を開きましょう。沢山表示されますが、クラフトピアのプログラムは名前空間”Oc”のしたにあります。Ctl + Shift + K で検索することができます。試しに、アイテムの情報が格納されているクラス(ItemData)の中身を見てみましょう。IdやPriceなどが確認できると思います。
BepInExの導入
BepInExは作成したプラグインをCraftopiaに導入するためのツール・ライブラリです。
(■利用者向けの前提ファイル導入を行っている場合は、以下にあるBepInEx.cfgファイルの書き換えのみを行ってください。)
まず、BepInExをダウンロードします。ダウンロードするファイルは、BepInEx_x64_5.3.0.0.zipまたはBepInEx_x86_5.3.0.0.zipです。ダウンロードして任意の場所に展開してください。中身すべてをCraftopia.exeがあるファルダ(~/SteamLibrary/steamapps/common/Craftopia)にコピーします。その後、普通にクラフトピアを起動します。起動後、Craftopia/BepInEx/configフォルダに中身があれば成功です。
導入後、デバッグ用のコンソールを立ち上がるようにします。Craftopia/BepInEx/config/BepInEx.cfgをメモ帳などで開きます。Loggingの項目のLogConsoleToUnityLog = false を LogConsoleToUnityLog = true に、Logging.Consoleの項目のEnabled = false を Enabled = true に変更します。
以上でBepInExの導入は終了です。
(■利用者向けの前提ファイル導入を行っている場合は、以下にあるBepInEx.cfgファイルの書き換えのみを行ってください。)
まず、BepInExをダウンロードします。ダウンロードするファイルは、BepInEx_x64_5.3.0.0.zipまたはBepInEx_x86_5.3.0.0.zipです。ダウンロードして任意の場所に展開してください。中身すべてをCraftopia.exeがあるファルダ(~/SteamLibrary/steamapps/common/Craftopia)にコピーします。その後、普通にクラフトピアを起動します。起動後、Craftopia/BepInEx/configフォルダに中身があれば成功です。
導入後、デバッグ用のコンソールを立ち上がるようにします。Craftopia/BepInEx/config/BepInEx.cfgをメモ帳などで開きます。Loggingの項目のLogConsoleToUnityLog = false を LogConsoleToUnityLog = true に、Logging.Consoleの項目のEnabled = false を Enabled = true に変更します。
以上でBepInExの導入は終了です。
VisualStudio2017の導入(2019やその他統合開発環境も可)
VisualStudio2017はプログラムをコンパイルしてプラグインを作成します。
まず、https://visualstudio.microsoft.com/ja/vs/older-downloads/ からVisualStudioComunity2017をダウンロードしてインストールします。
インストールの際にUnity関連のパッケージもインストールしましょう。
まず、https://visualstudio.microsoft.com/ja/vs/older-downloads/ からVisualStudioComunity2017をダウンロードしてインストールします。
インストールの際にUnity関連のパッケージもインストールしましょう。
初歩的なMod(Hello World)
プラグインの作成方法を理解するために、
Hello Worldと表示する簡単なプラグインを作成します。
VisualStudioから新規プロジェクトを作成します。プロジェクトの種類は クラス ライブラリ(.Net Framework)です。
依存関係を設定します。ソリューション エクスプローラーの”参照”を右クリックし”参照の追加”を選択します。
出てきた画面の右下の"参照"を選びます。以下のファイルを追加していきます。
Hello Worldと表示する簡単なプラグインを作成します。
VisualStudioから新規プロジェクトを作成します。プロジェクトの種類は クラス ライブラリ(.Net Framework)です。
依存関係を設定します。ソリューション エクスプローラーの”参照”を右クリックし”参照の追加”を選択します。
出てきた画面の右下の"参照"を選びます。以下のファイルを追加していきます。
- Craftopia/BepInEx/core/BepInEx.dll
- Craftopia/BepInEx/core/0Harmony.dll
- Craftopia/Craftopia_Data/Managed/UnityEngine.dll
- Craftopia/Craftopia_Data/Managed/UnityEngine.CoreModule.dll
- Craftopia/Craftopia_Data/Managed/AD__Overcraft.dll
追加したファイルが選択されていることを確認した後、OKを押します。
Harmonyには0Harmony.dllと0Harmony20.dllの2種類があります。
どちらでも使用することはできてしまいますが、
公式の開発者によると「0Harmony.dll」を使用するようにとのこと。
0Harmony20は単なるラッパーDLLだそうです。
処理順の変更(HarmonyPriority)などを使う際には対象と同じものを使用する必要があります。
どちらでも使用することはできてしまいますが、
公式の開発者によると「0Harmony.dll」を使用するようにとのこと。
0Harmony20は単なるラッパーDLLだそうです。
処理順の変更(HarmonyPriority)などを使う際には対象と同じものを使用する必要があります。
依存関係を設定し終えたので、プログラムを作成します。
プロジェクトを作成したときに作られたファイルを以下のようにします。
プロジェクトを作成したときに作られたファイルを以下のようにします。
using System; using BepInEx; namespace ExamplePlugin { [BepInPlugin("org.bepinex.plugins.exampleplugin", "Example Plug-In", "1.0.0.0")] //""の中身は任意。ただし、第一引数の中身が他MODと重複すると競合する public class ExamplePlugin : BaseUnityPlugin { void Awake() { UnityEngine.Debug.Log("Hello, world!"); } } }
ビルドタイプをReleaseに設定したあと、メニューのビルドからソリューションをビルドします。ビルドに成功したら、ソリューション エクスプローラーでプロジェクトを右クリックしエクスプローラーでフォルダを開くを選択します。/bin/Release/"プロジェクト名".dll を /Craftopia/BepInEx/pluginへコピーします。クラフトピアを起動してデバッグコンソールに”Hello World”と表示されれば成功です。
[Message: BepInEx] BepInEx 5.4.11.0 - Craftopia [Info: BepInEx] Running under Unity v2020.3.8.5273881 [Info: BepInEx] CLR runtime version:4.0.30319.42000 [Info: BepInEx] Supports SRE: True [Info: BepInEx] System platform: Bits64, Windows [Message: BepInEx] Preloader started [Info: BepInEx] 1 patcher plugin loaded [Info: BepInEx] Patching [UnityEngine.CoreModule] with [BepInEx.Chainloader] [Message: BepInEx] Preloader finished [Message: BepInEx] Chainloader ready [Message: BepInEx] Chainloader started [Info: BepInEx] 1 plugins to loaded //ここが0ならMODが読み込めていな い [Info: BepInEx] Loading [Example Plug-In 1.0.0.0] //public前で指定した表記による [BepInPlugin("org.~~(以下略) [Info: Unity Log] Hello,World! //ここに出る [Message: BepInEx] Chainloader startup complete
(細かい表示は異なることがあります)
初歩的なMod(アイテムの一覧)
今回はHarmonyの概要を理解するためのModを作成します。Harmonyはメソッドを書き換えたりメソッドの前(prefix)か後(postfix)に処理を追加を手軽にすることができます(それ以外はできません)。
先程、作ったModを基にアイテムの一覧を得るmodを作成します。
有効なアイテムを一覧として保持するのは,Oc.Item.OcItemDataMngです。アイテムの一覧を格納している変数はvalidItemDataListで初期化されていません。どこで初期化しているかを探してみましょう。
validItemDataListはOnUnityAwake()の1行目で初期化されています。その直後でメソッドを呼び出しているものがあるので、今回はその関数(SetupCraftableItems())の処理の前に新しく処理を追加します。
他の関数の前後や、OnUnityAwake()の最後に処理を追加しても構いません。
有効なアイテムを一覧として保持するのは,Oc.Item.OcItemDataMngです。アイテムの一覧を格納している変数はvalidItemDataListで初期化されていません。どこで初期化しているかを探してみましょう。
validItemDataListはOnUnityAwake()の1行目で初期化されています。その直後でメソッドを呼び出しているものがあるので、今回はその関数(SetupCraftableItems())の処理の前に新しく処理を追加します。
他の関数の前後や、OnUnityAwake()の最後に処理を追加しても構いません。
まず、インポートに using HarmonyLib; を追加しましょう。
そうしたら、先程作ったプログラムのAwake()の中身を以下に書き換えます。
そうしたら、先程作ったプログラムのAwake()の中身を以下に書き換えます。
var harmony = new Harmony("com.example.patch"); //""の中身は任意。他MODと重複すると競合する。 harmony.PatchAll();
これでパッチを当てるプログラムが完成しました。
次に, 実際に当てるパッチを作成します。
パッチを記述する新しいクラスを作成します。ソリューションエクスプローラー上でプロジェクトを右クリックし、追加-新しい項目を選択した上で、クラスを選びクラスを作ってください。クラスの内容は以下のとおりです。
パッチを記述する新しいクラスを作成します。ソリューションエクスプローラー上でプロジェクトを右クリックし、追加-新しい項目を選択した上で、クラスを選びクラスを作ってください。クラスの内容は以下のとおりです。
using HarmonyLib; using Oc.Item; //usingは必要に応じて追加する。 namespace ExamplePlugin { [HarmonyPatch(typeof(OcItemDataMng))] //書き換えるメソッドを持つクラスを指定 [HarmonyPatch("SetupCraftableItems")] //書き換えるメソッドを指定 static class Class2 { //第1引数で書き換えるクラスのインスタンスを取得している 省略可 第一引数でなくても可 //第2引数でプレイベートメンバの参照を取得している 引数名は アンダーバー*3 + 変数名 にする必要がある 省略可 順番は関係ない いくつでも指定可能 static bool Prefix(OcItemDataMng __instance, ref ItemData[] ___validItemDataList) { foreach(var data in ___validItemDataList) { UnityEngine.Debug.Log(string.Format("{0} {1}", data.Id, data.DisplayName)); } return true;//trueを返すと基の関数が実行される falseだとされない //falseにすると他のMODが基の関数を利用すると競合するので、よほどでなければtrue推奨 } } }
プログラムの作成は以上になります。ビルドしてプラグインを入れ直しましょう。クラフトピアを立ち上げたときにアイテムのリストが得られれば成功です。
初歩的なMod(価格の変更)
処理の変更対象以外のインスタンスのプライベートメンバにアクセスするための方法を理解するために、アイテムの価格を変更するModを作成します。
アイテムの価格を保持しているクラスはOc.Item.ItemDataです。メンバはpriceでint型です。一応、dnSpyで確認しましょう。
今回は石の片手剣の価格を変更します。前回のプログラムのforeachの中身を以下に変更します。
アイテムの価格を保持しているクラスはOc.Item.ItemDataです。メンバはpriceでint型です。一応、dnSpyで確認しましょう。
今回は石の片手剣の価格を変更します。前回のプログラムのforeachの中身を以下に変更します。
if (data.Id == 44) //石の片手剣のIDは44 { //このようにAccessTools.FieldRefAccessを用いてプライベートメンバを読み書きすることができます。 //テンプレートの引数はアクセスするインスタンスの型、メンバーの型 //引数はインスタンス、メンバ名 ref int price = ref AccessTools.FieldRefAccess<ItemData, int>(data, "price"); price = 99999; } UnityEngine.Debug.Log(string.Format("{0} {1} {2}", data.Id, data.DisplayName, data.Price));
実際に起動して確かめてみましょう。石の片手剣の値段が上昇しているはずです。
最後に
ここまで読めばあなたは、駆け出しのModderとしての技術は身に付いたはずです。あとは、クラフトピアのコードをよく読み理解すれば一人前のModderになれます。少しずつで良いのでこのWikiで知見を共有していただければ幸いです。