MOD制作 > 基本的な実装方法




さて、前回はソースコードの見方を学んで、「どこ」を改造すればいいかを見つけられるようになりました。
次はいよいよ、「どんな風に」改造するのか、つまり具体的な実装方法を見ていきます。

このゲームには、基本的な MOD 実装方法として Behavior (ビヘイビアー) と Model という二つの概念があります。



Behavior

ゲームのロード、毎時/日/週のティック、パークの習得など、各種のイベントに対する「挙動」を定義するクラスです。
TaleWorlds.CampaignSystem.SandBox.CampaignBehaviors.CampaignBehaviorBase を継承して、自前のビヘイビアーを作ります。
TaleWorlds.CampaignSystem.CampaignEvents の各 Event プロパティに対して、TaleWorlds.CampaignSystem.AddNonSerializedListener() でイベントリスナーを作成し、CampaignBehaviorBase.RegisterEvents() 内で登録します。


サンプルコード

1日1回、プレイヤーの所持金に +1000 する

namespace と同じ名前で Visual Studio のプロジェクトを作成し、サンプルコードをコピペすると、サンプル MOD が作れます。試作編と同じ手順で作ってみましょう。その際、MOD 設定ファイル (SubModule.xml) の「ExampleMod」となっている部分と、「MySubModule」となっている部分を忘れずに変更しましょう。

  1. using TaleWorlds.CampaignSystem;
  2. using TaleWorlds.CampaignSystem.Actions;
  3. using TaleWorlds.Core;
  4. using TaleWorlds.MountAndBlade;
  5.  
  6. namespace DailyIncome
  7. {
  8. // MBSubModuleBase を継承してモジュールのエントリーポイントとするのは常に一緒です。
  9. // クラス名は任意です。
  10. public class SubModule : MBSubModuleBase
  11. {
  12. protected override void OnGameStart(Game game, IGameStarter gameStarterObject)
  13. {
  14. // 現状、MBSubModule.OnGameStart() は空の処理ですが、念のため。
  15. base.OnGameStart(game, gameStarterObject);
  16.  
  17. // ビヘイビアーを適用する GameType を選べます。
  18. // Campaign: キャンペーン全般 (ストーリーモードも含まれます)
  19. // CampaignStoryMode: ストーリーモードのみ
  20. // CustomGame: カスタムバトル?
  21. // EditorGame: たぶん Bannerlord Modding Kit 経由で起動されたゲーム
  22. // MultiplayerGame: マルチ
  23. if (game.GameType is Campaign)
  24. {
  25. ((CampaignGameStarter)gameStarterObject).AddBehavior(new DailyIncomeBehavior());
  26. }
  27. }
  28. }
  29.  
  30. // CampaignBehaviorBase を継承して自前のビヘイビアーを作成します。
  31. // クラス名は任意です。
  32. public class DailyIncomeBehavior : CampaignBehaviorBase
  33. {
  34. // イベントリスナーを登録します。
  35. public override void RegisterEvents()
  36. {
  37. // CampaignEvents クラスのプロパティには様々なイベントが用意されており、ビヘイビアーの実行タイミングを制御できます。
  38. // 今回は、1日1回実行するように CampaignEvents.DailyTickEvent を使用しています。
  39. CampaignEvents.DailyTickEvent.AddNonSerializedListener(this, OnDailyTick);
  40. }
  41.  
  42. // セーブデータへの値の保存に使うらしいです。
  43. // この例では使いませんが、抽象メソッドなので実装が必要です。
  44. public override void SyncData(IDataStore dataStore)
  45. {
  46. }
  47.  
  48. // イベントリスナー
  49. // イベントがトリガーされた際に呼び出されるコールバックです。
  50. // メソッド名は任意ですが、イベント名と対にするのが望ましいでしょう。
  51. // CampaignEvents.○○Event -> On○○()
  52. private void OnDailyTick()
  53. {
  54. // 1000 デナルをプレイヤーに与える処理です。
  55. // こうした処理は、自分の実現したいことと似たことをやっているパーク等を解析して参考にするのがよいでしょう。
  56. GiveGoldAction.ApplyBetweenCharacters(null, Hero.MainHero, 1000);
  57. }
  58. }
  59. }
  60.  

OnSubModuleLoad() や OnGameStart() などがどのタイミングで呼び出されるか (ゲーム初期化処理の流れ) についてはこちらを参照してください。



Model

部隊速度、キャラクターの成長、繁栄度などの拠点パラメータ等々、様々な事象の「計算方法」、あるいは条件判断等に使う「設定値」を定義するクラスです。
TaleWorlds.Core.GameModel を基底クラスとした様々な抽象クラスが用意されており、それらのデフォルトの実装が Default○○Model となっています (dnSpy の Analyzer で、GameModel クラスの Used By をたどると見つかります)。これらのデフォルトモデルの一部をオーバーライドするなどして自前のモデルを作ります。
自前のモデルは、TaleWorlds.MountAndBlade.MBSubModuleBase.OnGameStart() 内で TaleWorlds.CampaignSystem.CampaignGameStarter.AddModel() メソッドを使って登録します。


サンプルコード

スキルフォーカスポイントの獲得量を変更する

  1. using TaleWorlds.CampaignSystem;
  2. using TaleWorlds.CampaignSystem.SandBox.GameComponents;
  3. using TaleWorlds.Core;
  4. using TaleWorlds.MountAndBlade;
  5.  
  6. namespace MoreFocusPointsPerLevel
  7. {
  8. public class SubModule : MBSubModuleBase
  9. {
  10. protected override void OnGameStart(Game game, IGameStarter gameStarterObject)
  11. {
  12. if (game.GameType is Campaign)
  13. {
  14. // カスタムモデルを登録します。
  15. // Campaign.OnInitialize() を見るとわかりますが、GameModel の適用は OnGameStart() 直後に
  16. // 行われるので、必ず OnGameStart() 内で AddModel() しておかなければなりません。
  17. gameStarterObject.AddModel(new CustomCharacterDevelopmentModel());
  18. }
  19. }
  20. }
  21.  
  22. // デフォルトのキャラクター成長モデルの一部をオーバーライドした、カスタムモデルを作成します。
  23. // クラス名は任意です。
  24. public class CustomCharacterDevelopmentModel : DefaultCharacterDevelopmentModel
  25. {
  26. // 抽象・仮想メンバーなら override して処理を変更してしまえます。
  27. public override int FocusPointsPerLevel
  28. {
  29. get
  30. {
  31. // ここを 2 でも 3 でも好きな数に変えると、その分のフォーカスポイントがレベルアップ時に付与されます。
  32. return 2;
  33. }
  34. }
  35. }
  36. }
  37.  


モデルの競合

GameModel は、GameModels 型として Campaign._gameModels フィールドに格納されます。その際、AddModel() で登録された GameModel のリスト最後尾に近いものから、それぞれの抽象モデル (CharacterDevelopmentModel など) につき一つずつピックアップされます (文章だと分かりにくいので GameModels.GetSpecificGameBehaviors() を見てください)。

Campaign.OnInitialize() における処理の流れで言うと、
SandBoxManager.OnGameStart()        // Default○○Model をリストに追加
base.GameManager.OnGameStart()      // 各モジュールの GameModel をリストに追加 (GameModel リストの出来上がり)
base.CurrentGame.SecondInitialize() // 
this._gameModels = ....             // GameModels 型のコンストラクタで、実際に使用する GameModel をリスト後方からピックアップしてフィールドに格納
となっていることで、MOD の GameModel の方が優先して採用されるわけです。

ところが、これは MOD 同士の間にも言えることで、ある抽象クラスから派生した GameModel を改変している MOD は、同じ抽象クラスから派生した GameModel を改変する、ロードオーダーが後ろの MOD によって、自分が登録した GameModel を上書きされてしまいます。

例えば、以下のような DefaultCharacterDevelopmentModel から派生した GameModel を持つ別の MOD があるとします。
  1. using TaleWorlds.CampaignSystem;
  2. using TaleWorlds.CampaignSystem.SandBox.GameComponents;
  3. using TaleWorlds.Core;
  4. using TaleWorlds.MountAndBlade;
  5.  
  6. namespace AnotherMod
  7. {
  8. public class SubModule : MBSubModuleBase
  9. {
  10. protected override void OnGameStart(Game game, IGameStarter gameStarterObject)
  11. {
  12. if (game.GameType is Campaign)
  13. {
  14. gameStarterObject.AddModel(new AnotherModCharacterDevelopmentModel());
  15. }
  16. }
  17. }
  18.  
  19. public class AnotherModCharacterDevelopmentModel : DefaultCharacterDevelopmentModel
  20. {
  21. public override int LevelsPerAttributePoint
  22. {
  23. get
  24. {
  25. // 毎レベル能力ポイントが付与されるようにしたい!
  26. return 1;
  27. }
  28. }
  29. }
  30. }
  31.  
サンプルの MoreFocusPointsPerLevel MOD と、上の AnotherMod を同時使用した場合、どちらも CharacterDevelopmentModel の派生モデルを使用していますので、ランチャーでロードオーダーを後ろにした方の GameModel だけが採用されます。採用されなかった方の MOD は、自身で定義した GameModel ではないものを使わされることになるのです。



プロジェクトの詳細設定


出力先

コンパイルした MOD をいちいち Modules フォルダーにコピペするのは面倒ですよね。出力先を変更しましょう。

プロジェクトメニューから[プロジェクト名]のプロパティを選択します。
プロパティのビルドで、構成を すべての構成 にします。
出力/出力パス
[Bannerlord インストールフォルダー]\Modules\[MOD名]\bin\Win64_Shipping_Client\
にします。

Visual Studio 右側のソリューション エクスプローラーで、プロジェクトが参照しているアセンブリを全て選択し、右下の「ローカルにコピー」を False にします。


リリースビルド

サンプル MOD のテストでは関係ないですが、将来的にあなた自身の MOD を公開する際にはやっておいた方がいい設定です。

プロパティのビルドで、構成を Release にします。
下の詳細ボタンをクリックし、出力/デバッグ情報を「PDB のみ」から「なし」にします。

これをしないと DLL に PDB ファイルのローカルパスが埋め込まれるので、プロジェクトをデフォルトであるユーザーフォルダーに保管している場合、あなたの Windows ユーザー名がさらけ出される羽目になります



デバッグ

プロパティのデバッグで、構成を Debug にします。
開始動作/外部プログラムの開始にチェックを入れ、
[Bannerlord インストールフォルダー]\bin\Win64_Shipping_Client\Bannerlord.exe
を指定します。

開始オプション/コマンドライン引数には
/singleplayer _MODULES_*Native*SandBoxCore*CustomBattle*SandBox*StoryMode*[MOD名]*_MODULES_
を入力し、
開始オプション/作業ディレクトリ
[Bannerlord インストールフォルダー]\bin\Win64_Shipping_Client\
とします。

以上の設定でデバッグが可能となります。デバッグを開始する際には Steam クライアントを起動しておいてください。



さて、以上でごく初歩的な MOD が作れるようになりました。サンプルコードをそのまま試すだけでなく、ぜひともいろんな処理をオーバーライドして遊んでみてください。Bannerlord 本体や、Nexus などに上げられている他人の MOD をデコンパイルして研究し、さらに理解を深めていきましょう。

以下のページも参考にしてください。



  • 筆者自身が初心者なのでそれほど踏み込んだことを書けていませんが、MOD制作を始めるにあたっての取っ掛かりにはなるかと思います。詳しい方がいらっしゃったら、別稿でも加筆でも、記事を充実させていっていただけるとありがたいです。 - 名無しさん (2021-05-15 18:50:22)
  • ver 2.0.0になり、MODが対応しなくなっていたので参考にしてMODを作成しました。分かりやすくてすごく助かりました! - 名無しさん (2023-11-19 14:07:03)

タグ:

+ タグ編集
  • タグ:
最終更新:2021年06月06日 13:59