modを作る、それは世界の改変を意味する。
日本コミュニティにおけるmod開発は、modを作りたい人と既にmodを作った人の間で技術を伝えることによって成立してきた部分があるのだが、一般に情報が開示されてこなかったので、ここにごく簡単なmodの製作方法をまとめる。
目指せmodder!
必須知識
自分が作りたいmodに応じて、身につけておかないといけない知識がある。
ブロックを作る場合
ブロック自体は公式が用意したモジュールを利用することで、プログラミングをせずとも実装可能。しかし、モジュールだけでできることはかなり少なく、えてしてコードを書くことになる。Add Custom Module modを用いればモジュール記述を増やすことができるが、これについては
ACMmod解説を参照してほしい。
- Blender等の3Dモデリング知識
エンティティを作る場合
エンティティとはレベルエディタでおけるオブジェクトのこと。エンティティはブロックと同じく基本的にプログラミング無しで実装できるが、こちらは後からスキンを変えることができない。
- Blender等の3Dモデリング知識
プログラミングをする場合
ほとんどのmodではプログラミングをすることになる。このページではこの部分について詳しく述べる。
- C#の基礎的な知識
- Unityの基礎的な知識
- 高校レベルの英語力(情報源が英語なため)
Unityのバージョンは5.4なので、事前にやりたいことがこのバージョンでできるか確認すること。足りないものがあれば各自勉強して身に着けてほしい。
また、主に次のソフトが必要。
- Visual Studio等のエディタ
- ILSpy等の逆コンパイラ
- Unity 5.4
エディタはプログラミングをするので当然必須。このページでは逆コンパイラを使うことはないが、実際にmodを作る際には公式のアセンブリや、他のmodのソースを見てやり方を勉強しないと現実的にやっていけない。Unityはブロックのエフェクトや海などのアセットを用意する場合に必要だが、全てのmod製作に必要なわけではない。
より詳細な情報
このページはあくまでチュートリアルなので、実用的なmodを作るまでは自分で開発することになる。
もっと多くの情報を得たい場合は以下の方法を試してみてほしい。
modを作る
おおもとのmodファイルを生成する。
Besiegeを開始して、どの画面でもいいのでCtrl+Kを押す。すると、
コンソール画面が出現する。
コンソールはコマンドを打ってBesiegeの設定をなんやかんやする(適当)ことができるのだが、ここで以下のようにコマンドを打つ。
createmod wiki_tutorial01
すると、Besiege_Data/Mods/にwiki_tutorial01 Projectというmodプロジェクトが自動で生成される。これでwiki_tutorial01というmodを作成することができた。
createmodは、直後に作りたいmodの名前を入力することで、そのmodを作成するコマンドである。
次に、このmodにC#のアセンブリを追加したいので、次のようにコマンドを打つ。
createassembly wiki_tutorial01 compiled tutorial01_assem Tutorial01Space
すると、wiki_tutorials01 modに、tutorial01_assemという名前のアセンブリが、Tutorial01Spaceという名前空間で生成される。アセンブリ名と名前空間はただの名前なので何を入れても良い。まよったら自分のユーザ名でも入れておけばよい。
詳細な説明は割愛するが、コンソール上で
createblock hogehoge
と打てばhogehogeというブロックを、
createentity hogehoge
と打てばhogehogeというエンティティを作成することができる。
メニュー画面のmodリストを見ると、次のようにmodが生成されているはずである。
次の章からプログラムを書いてビルドしてゲーム内の挙動を確かめて…とするが、modをビルドする度にBesiegeを再起動する必要があることに注意しよう。
Hello World!
Visual Studioで
Besiege_Data/Mods/wiki_tutorial01 Project/src/wiki_tutorial01.sln
を開く(何も表示されない場合は/src/tutorial01_assem/Mod.csも開く)。すると次のようにModクラスが書かれている。
これがmod作成時の初期画面になる。Modクラスについて簡単に説明すると、Modクラスはこのmodの基地のようなクラスで、例えばこのクラスで生成したGameObjectを親として、UIやら機能やらのスクリプトを付けたGameObjectを紐づけていくことになる。ちなみにクラス名を変えるとmodが認識されなくなる。
このままCtrl+Bでソリューションをビルドすると、Besiege内でmod loaderがビルドしたdllファイルを読み込んでmodとして使えるようになる。ただし、まだ何も機能を付けていないので、modがロードされたかどうか自体わからない。
ここで、OnLoad関数の中に以下のように記述してみよう。
using
System.Collections
.Generic
;using Modding;
using Modding.Blocks;
using UnityEngine;
namespace Tutorial01Space
{
// modの拠点となるクラス
public class Mod : ModEntryPoint
{
// ステージ(バレンエクスパンスなど)に初めて入った時にmodがロードされ、同時にOnLoad()が呼び出される。
public override void OnLoad()
{
// コンソールに「Hello World!」と表示させる。
ModConsole
.Log("Hello World!"); // Debug.Log("Hello World!"); でも可
}
}
}
ModConsole.Log()は、Besiegeのコンソールに入力したstring値を表示する関数である。
Unityユーザに取ってはDebug.Log()がなじみ深いと思うが、Debug.Log()でも同じことができる。Debug.LogWarningとDebug.LogErrorは色が変わって表示される。
これを書いてからビルドし、Besiegeを起動してどこかのシーンに入ると、コンソールに次のように表示される。
moddingの世界へようこそ。
+
|
おまけ |
おまけ
modをいじっていると、自分のmodがどこで何をしたのかプリント文デバッグすることが多いのだが、別のmodを入れながら作業しているとどのプリント文がどのmodから発せられているかわからなくなる場合があるので、メッセージの中にはmod名を書いておくことを推奨する
public class Mod : ModEntryPoint { public static void Log(string msg ) { // ModConsole.Log(...) と書いても同じ Debug .Log("wiki tutorial : " + msg ); } public static void Warning(string msg) { Debug.LogWarning("wiki tutorial : " + msg); } public static void Error(string msg) { Debug.LogError("wiki tutorial : " + msg); } }
|
GUIの追加
modといえばグラフィカルユーザインターフェースである。これを追加していこう。
そのためには、「GUIを制御するためのクラス」が必要になる。まずはこれを作る。
※これ以降は特別な記述がない限りusingとnamespace Tutorial01Spaceの記述を省きます。
public class Gui : SingleInstance<Gui>
{
// 変数
private Rect windowRect = new Rect(0, 80, 200, 100);
private int windowId;
// プロパティ
// SingleInstance<>の継承で必要だが、どんな名前でもよい
public override string Name
{
get
{
return "wiki tutorial gui";
}
}
// メソッド
public void Awake()
{
// 他のmodのGUIと競合しないwindow IDを生成する(呪文だと思ってOK)
windowId = ModUtility.GetWindowId();
}
// GUIの値が変化するフレームでのみ呼び出される
// そのため、Updateよりも計算量が少なくなる
public void OnGUI()
{
// マルチバース参加者ではなく、タイトル画面ではない場合
// すなわち、一人プレイ時またはサーバーホスト時でマップに入っている場合にUIを表示する
if (!StatMaster.isClient && !StatMaster.isMainMenu)
{
windowRect = GUI.Window(windowId, windowRect, delegate(int windowId)
{
// GUIの中身を構成する
// 文字を表示させる
GUILayout.Label("Mod完全に理解した");
GUI.DragWindow();
}
, "wiki tutorial");
}
}
}
SingleInstance<Type>の継承は呪文だと思ってほしい。Nameというプロパティを継承する必要があるが、これは適当に値を入れておけば良いし、別段どこかで使うわけでもない。
windowRect変数はUIの初期位置と初期サイズを決める。
OnGUI関数はコメントでも書いているが、Updateより計算量が少なくなるGUI専用のメソッドである。
GUIの中身はdelegate(int windowId){}の中に記述することになる。ここでは単純にラベルを表示させるようにしている他、GUI.DragWindow()メソッドを末尾に入れることで、マウスドラッグでUIを動かせるようにしている。UIのタイトルはそのあとにある「"wiki tutorial"」部分である。
このクラスはModクラスでまだ呼び出されていないので、Modクラスも書き換える必要がある。
// modの拠点となるクラス
public class Mod : ModEntryPoint
{
// 変数
GameObject mod;
// メソッド
// ステージ(バレンエクスパンスなど)に初めて入った時にmodがロードされ、同時にOnLoad()が呼び出される。
public override void OnLoad()
{
// コンソールに「Hello World!」と表示させる。
ModConsole
.Log("Hello World!");
// modゲームオブジェクトを初期化し、GUI等で使うクラスのインスタンスを子に指定する。
mod = new GameObject("WikiTutorialController");
SingleInstance<Gui>.Instance.transform.parent = mod.transform;
// シーンをまたいでもmodが消されないようにする。
UnityEngine.Object.DontDestroyOnLoad(mod);
}
}
modというGameObjectはGuiクラスのインスタンスを子として持つだけの空オブジェクトである。シーンをまたいでも消えないようにDontDestroyOnLoad(mod)としている。このSingleInstance<>回りの記述はどこのmodでも同じように書いているおまじないみたいなものなので、この通りに書こう。
ここまでうまく書けていれば、表題のようなUIが表示されているはずだ。
ブロックにスクリプトを貼り付ける
エンハンスmodや海mod、スタビmod、ABSなど、ありとあらゆるmodが既存ブロックにスクリプトを貼り付けることで動作している。入門用ページとしてはこれを通らない手はない。
ブロックにスクリプトを貼り付けるには、一般的に以下の方法をとる。
- 貼り付けたいスクリプトを用意する
- 貼り付けたいスクリプトと、貼り付けたいブロックを紐づけた辞書を設定する
- ブロックが設置された場合に、そのブロックを対象に以下の関数を適用するように挙動を追加する
- もしそのブロックが、自分がスクリプトを貼り付けたいブロックなら、辞書にしたがってスクリプトを貼り付ける
何を言っているかわからないかもしれないので、実際にコードを見てもらう。これは上の箇条書きのうち一番上以外である。
using
System.Collections
.Generic
; // これを追加using Modding;
using Modding.Blocks; // これを追加
using UnityEngine;
// ブロックにスクリプトを追加するクラス
public class BlockSelector : SingleInstance<BlockSelector>
{
// 変数
// ブロックのIDと、追加したいスクリプトを紐づけた辞書
public Dictionary<int, Type> BlockDict = new Dictionary<int, Type>
{
// スタートブロック
{0, typeof(StartingBlockScript) },
};
// プロパティ
// Guiと同様の呪文
public override string Name
{
get
{
return "wiki tutorial BlockSelector";
}
}
// メソッド
public void Awake()
{
// ブロックを設置した場合に呼び出されるアクションに、AddScriptというメソッドを追加する
Events.OnBlockInit += new Action<Block>(AddScript);
}
// ブロック設置時に、そのブロックに所定のスクリプトを貼り付ける関数
// Blockは、設置したブロックを表す
public void AddScript(Block block)
{
// 生成したブロックのBlockBehaviourコンポーネントを取得する
BlockBehaviour internalObject = block.BuildingBlock.InternalObject;
// そのブロックがスクリプトを貼り付けるべきブロックであるなら、貼り付ける
if (BlockDict.ContainsKey(internalObject.BlockID))
{
Type type = BlockDict[internalObject.BlockID];
try
{
// まだ所定のスクリプトが貼り付けられていない場合にのみ、貼り付ける
if (internalObject.GetComponent(type) == null)
{
internalObject.gameObject.AddComponent(type);
}
}
catch
{
Mod.Error("AddScript Error!");
}
return;
}
}
}
この記述はABSに書かれているものと同じ最小構成で、エンハンスmod等はエラーを極力排除するようにより複雑な記述になっている。実際は辞書部分以外をエンハンスmod等の既存modから持ってくる場合が多いだろう。
そして、実際に貼り付けたいスクリプトを、例えばこんな風に書いてみる。これは、
スタートブロックの設定によりGUIに文字を表示させたりさせなかったりするためのスクリプトである。先ほど生成したGUIとさっそく連携させる狙いがある。Guiクラスも書き換える必要があるので、Guiクラスも書いておく。
// スタートブロックに貼り付けるスクリプト
public class StartingBlockScript : MonoBehaviour
{
// 変数
public BlockBehaviour BB;
public MToggle toggle;
// メソッド
private void Awake()
{
// BlockBehaviourを取得
BB = GetComponent<BlockBehaviour>();
// ブロックの設定画面にトグルを追加
toggle = BB.AddToggle("toggle", "wiki tutorial toggle", false);
}
private void Update()
{
// GUIで文章を表示するかを、トグルの値に応じて変える
SingleInstance<Gui>.Instance.StartingBlockToggle = toggle.IsActive;
}
}
// GUIで用いるクラス
// SingleInstance<>を継承するのは呪文だと思って貰えばOK
public class Gui : SingleInstance<Gui>
{
// 変数
private Rect windowRect = new Rect(0, 80, 200, 100);
private int windowId;
public bool StartingBlockToggle = false;
// プロパティ
// SingleInstance<>の継承で必要だが、どんな名前でもよい
public override string Name
{
get
{
return "wiki tutorial gui";
}
}
// メソッド
public void Awake()
{
// 他のmodのGUIと競合しないwindow IDを生成する(呪文だと思ってOK)
windowId = ModUtility.GetWindowId();
}
// GUIの値が変化するフレームでのみ呼び出される
// そのため、Updateよりも計算量が少なくなる
public void OnGUI()
{
if (!StatMaster.isClient && !StatMaster.isMainMenu)
{
windowRect = GUILayout.Window(windowId, windowRect, delegate(int windowId)
{
// GUIの中身を構成する
// 文字を表示させる
GUILayout.Label("Mod完全に理解した");
// スタートブロックのトグルが押されていれば、これも表示する
if (StartingBlockToggle)
{
GUILayout.Label("スタブロと和解せよ");
}
GUI.DragWindow();
}
, "wiki tutorial");
}
}
}
BlockBehaviour.AddToggle関数はブロックの設定画面にトグルを追加する。他にもAddMenuとか似たような関数が色々あるので、もっと知りたい人は公式のドキュメントを読んでみよう。
Guiクラスには、StartingBlockScriptからアクセス可能なStartingBlockToggleというbool型変数と、それに応じて表示するか変わるGUILayout.Label関数を追加している。
C#コーディングまとめ
以上を総合すると、全体のMod.csは次のようになる。
+
|
Mod.cs全文 |
Mod.cs全文
using System.Collections .Generic ;using Modding; using Modding.Blocks; using UnityEngine; namespace Tutorial01Space { // modの拠点となるクラス public class Mod : ModEntryPoint { // 変数 GameObject mod; // メソッド // ステージ(バレンエクスパンスなど)に初めて入った時にmodがロードされ、同時にOnLoad()が呼び出される。 public override void OnLoad() { // コンソールに「Hello World!」と表示させる。 ModConsole .Log("Hello World!"); // modゲームオブジェクトを初期化し、GUI等で使うクラスのインスタンスを子に指定する。 mod = new GameObject("WikiTutorialController"); SingleInstance<Gui>.Instance.transform.parent = mod.transform; SingleInstance<BlockSelector>.Instance.transform.parent = mod.transform; // シーンをまたいでもmodが消されないようにする。 UnityEngine.Object.DontDestroyOnLoad(mod); } // このようなプリント系メソッドを用意しておくと他modのメッセージと混同しにくい。 public static void Log(string msg ) { // ModConsole.Log(...) と書いても同じ Debug .Log("wiki tutorial : " + msg ); } public static void Warning(string msg) { Debug.LogWarning("wiki tutorial : " + msg); } public static void Error(string msg) { Debug.LogError("wiki tutorial : " + msg); } } // GUIで用いるクラス // SingleInstance<>を継承するのは呪文だと思って貰えばOK public class Gui : SingleInstance<Gui> { // 変数 private Rect windowRect = new Rect(0, 80, 200, 100); private int windowId; public bool StartingBlockToggle = false; // プロパティ // SingleInstance<>の継承で必要だが、どんな名前でもよい public override string Name { get { return "wiki tutorial gui"; } } // メソッド public void Awake() { // 他のmodのGUIと競合しないwindow IDを生成する(呪文だと思ってOK) windowId = ModUtility.GetWindowId(); } // GUIの値が変化するフレームでのみ呼び出される // そのため、Updateよりも計算量が少なくなる public void OnGUI() { if (!StatMaster.isClient && !StatMaster.isMainMenu) { windowRect = GUILayout.Window(windowId, windowRect, delegate(int windowId) { // GUIの中身を構成する // 文字を表示させる GUILayout.Label("Mod完全に理解した"); // スタートブロックのトグルが押されていれば、これも表示する if (StartingBlockToggle) { GUILayout.Label("スタブロと和解せよ"); } GUI.DragWindow(); } , "wiki tutorial"); } } } // ブロックにスクリプトを追加するクラス public class BlockSelector : SingleInstance<BlockSelector> { // 変数 // ブロックのIDと、追加したいスクリプトを紐づけた辞書 public Dictionary<int, Type> BlockDict = new Dictionary<int, Type> { // スタートブロック {0, typeof(StartingBlockScript) }, }; // プロパティ // Guiと同様の呪文 public override string Name { get { return "wiki tutorial BlockSelector"; } } // メソッド public void Awake() { // ブロックを設置した場合に呼び出されるアクションに、AddScriptというメソッドを追加する Events.OnBlockInit += new Action<Block>(AddScript); } // ブロック設置時に、そのブロックに所定のスクリプトを貼り付ける関数 // Blockは、設置したブロックを表す public void AddScript(Block block) { // 生成したブロックのBlockBehaviourコンポーネントを取得する BlockBehaviour internalObject = block.BuildingBlock.InternalObject; // そのブロックがスクリプトを貼り付けるべきブロックであるなら、貼り付ける if (BlockDict.ContainsKey(internalObject.BlockID)) { Type type = BlockDict[internalObject.BlockID]; try { // まだ所定のスクリプトが貼り付けられていない場合にのみ、貼り付ける if (internalObject.GetComponent(type) == null) { internalObject.gameObject.AddComponent(type); } } catch { Mod.Error("AddScript Error!"); } return; } } } // スタートブロックに貼り付けるスクリプト public class StartingBlockScript : MonoBehaviour { // 変数 public BlockBehaviour BB; public MToggle toggle; // メソッド private void Awake() { // BlockBehaviourを取得 BB = GetComponent<BlockBehaviour>(); // ブロックの設定画面にトグルを追加 toggle = BB.AddToggle("toggle", "wiki tutorial toggle", false); } private void Update() { // GUIで文章を表示するかを、トグルの値に応じて変える SingleInstance<Gui>.Instance.StartingBlockToggle = toggle.IsActive; } } }
|
長いけどできることはほぼないから悲しいね。でかいmodは数千行数万行とコードが膨大になるので、野望に見合ったやる気が必要。頑張れ。
Mod.xmlの編集
Mod.xmlとは、modの情報をまとめたxmlファイルであり、Besiege起動時に読み込まれるmodの仕様書みたいなもので、createmodした時に自動で作成される。mod作成時には「Mod.xmlを編集しなさい」と言われるのだが、このサンプルmodの動作にはあまり影響が無い。modを公開する時に編集必須になる。
要素をいちいち説明すると長くなってしまうので割愛するが、下記のソースコードからDLできるサンプルmodのMod.xmlには、英語で書かれた説明文に加えてその和訳も付けておいたので、代わりにそちらを読んで欲しい。訳者註も付けたのでわかりやすいと思う。
ソースコード
このチュートリアルで作ったmodのサンプルは以下のGitHubページからダウンロードできる。
https://github.com/Yamabach/wiki_tutorial01
このmodをそのまま、または改変してワークショップにアップロードすることは固く禁止する(同じmod IDの別modが存在すると何が起こるかわからないため)。あくまで勉強用に使ってください。
最終更新:2024年04月07日 00:29