さて、
前回はソースコードの見方を学んで、「どこ」を改造すればいいかを見つけられるようになりました。
次はいよいよ、「どんな風に」改造するのか、つまり具体的な実装方法を見ていきます。
このゲームには、基本的な 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」となっている部分を忘れずに変更しましょう。
using TaleWorlds.CampaignSystem;
using TaleWorlds.CampaignSystem.Actions;
using TaleWorlds.Core;
using TaleWorlds.MountAndBlade;
namespace DailyIncome
{
// MBSubModuleBase を継承してモジュールのエントリーポイントとするのは常に一緒です。
// クラス名は任意です。
public class SubModule : MBSubModuleBase
{
protected override void OnGameStart(Game game, IGameStarter gameStarterObject)
{
// 現状、MBSubModule.OnGameStart() は空の処理ですが、念のため。
base.OnGameStart(game, gameStarterObject);
// ビヘイビアーを適用する GameType を選べます。
// Campaign: キャンペーン全般 (ストーリーモードも含まれます)
// CampaignStoryMode: ストーリーモードのみ
// CustomGame: カスタムバトル?
// EditorGame: たぶん Bannerlord Modding Kit 経由で起動されたゲーム
// MultiplayerGame: マルチ
if (game
.GameType is Campaign
) {
((CampaignGameStarter
)gameStarterObject
).AddBehavior(new DailyIncomeBehavior
()); }
}
}
// CampaignBehaviorBase を継承して自前のビヘイビアーを作成します。
// クラス名は任意です。
public class DailyIncomeBehavior : CampaignBehaviorBase
{
// イベントリスナーを登録します。
public override void RegisterEvents()
{
// CampaignEvents クラスのプロパティには様々なイベントが用意されており、ビヘイビアーの実行タイミングを制御できます。
// 今回は、1日1回実行するように CampaignEvents.DailyTickEvent を使用しています。
CampaignEvents.DailyTickEvent.AddNonSerializedListener(this, OnDailyTick);
}
// セーブデータへの値の保存に使うらしいです。
// この例では使いませんが、抽象メソッドなので実装が必要です。
public override void SyncData(IDataStore dataStore)
{
}
// イベントリスナー
// イベントがトリガーされた際に呼び出されるコールバックです。
// メソッド名は任意ですが、イベント名と対にするのが望ましいでしょう。
// CampaignEvents.○○Event -> On○○()
private void OnDailyTick()
{
// 1000 デナルをプレイヤーに与える処理です。
// こうした処理は、自分の実現したいことと似たことをやっているパーク等を解析して参考にするのがよいでしょう。
GiveGoldAction.ApplyBetweenCharacters(null, Hero.MainHero, 1000);
}
}
}
OnSubModuleLoad() や OnGameStart() などがどのタイミングで呼び出されるか (ゲーム初期化処理の流れ) については
こちらを参照してください。
Model
部隊速度、
キャラクターの成長、繁栄度などの拠点パラメータ等々、様々な事象の「計算方法」、あるいは条件判断等に使う「設定値」を定義するクラスです。
TaleWorlds.Core.GameModel を基底クラスとした様々な抽象クラスが用意されており、それらのデフォルトの実装が Default○○Model となっています (dnSpy の Analyzer で、GameModel クラスの Used By をたどると見つかります)。これらのデフォルトモデルの一部をオーバーライドするなどして自前のモデルを作ります。
自前のモデルは、TaleWorlds.MountAndBlade.MBSubModuleBase.OnGameStart() 内で TaleWorlds.CampaignSystem.CampaignGameStarter.AddModel() メソッドを使って登録します。
サンプルコード
スキルフォーカスポイントの獲得量を変更する
using TaleWorlds.CampaignSystem;
using TaleWorlds.CampaignSystem.SandBox.GameComponents;
using TaleWorlds.Core;
using TaleWorlds.MountAndBlade;
namespace MoreFocusPointsPerLevel
{
public class SubModule : MBSubModuleBase
{
protected override void OnGameStart(Game game, IGameStarter gameStarterObject)
{
if (game
.GameType is Campaign
) {
// カスタムモデルを登録します。
// Campaign.OnInitialize() を見るとわかりますが、GameModel の適用は OnGameStart() 直後に
// 行われるので、必ず OnGameStart() 内で AddModel() しておかなければなりません。
gameStarterObject
.AddModel(new CustomCharacterDevelopmentModel
()); }
}
}
// デフォルトのキャラクター成長モデルの一部をオーバーライドした、カスタムモデルを作成します。
// クラス名は任意です。
public class CustomCharacterDevelopmentModel : DefaultCharacterDevelopmentModel
{
// 抽象・仮想メンバーなら override して処理を変更してしまえます。
public override int FocusPointsPerLevel
{
get
{
// ここを 2 でも 3 でも好きな数に変えると、その分のフォーカスポイントがレベルアップ時に付与されます。
return 2;
}
}
}
}
モデルの競合
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 があるとします。
using TaleWorlds.CampaignSystem;
using TaleWorlds.CampaignSystem.SandBox.GameComponents;
using TaleWorlds.Core;
using TaleWorlds.MountAndBlade;
namespace AnotherMod
{
public class SubModule : MBSubModuleBase
{
protected override void OnGameStart(Game game, IGameStarter gameStarterObject)
{
if (game
.GameType is Campaign
) {
gameStarterObject
.AddModel(new AnotherModCharacterDevelopmentModel
()); }
}
}
public class AnotherModCharacterDevelopmentModel : DefaultCharacterDevelopmentModel
{
public override int LevelsPerAttributePoint
{
get
{
// 毎レベル能力ポイントが付与されるようにしたい!
return 1;
}
}
}
}
サンプルの 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 をデコンパイルして研究し、さらに理解を深めていきましょう。
以下のページも参考にしてください。
最終更新:2021年06月06日 13:59