Joël van der Werf
記事一覧

ルール、ビュー、ゲームセッション:ゲームコードの構成

ジオラマRPGで、バトルの計算をGPUなしでテストでき、UIを単純に保ち、実行中の状態をシーン切り替えをまたいで維持するための構成。

エンジンのコードにはルールがありますが、ゲームのコードにも独自のルールが必要です。Eonloreにバトル、クエスト、パーティー、メニューが揃ってくると、スクリプトが絡まり始めました。UIのハンドラーの中にバトルの計算があり、シーンを切り替えると状態が失われ、ゲームを起動しないと何もテストできない状態でした。

最終的に落ち着いた構成は4つの要素からなります。UnityでMVPやMVCを使ってゲームを作ったことがある人には見覚えがあるはずで、それは意図的です。

1. 実行中の状態はゲームセッションに

エンジンは、実行が始まるとき(エディタの再生、またはプレイヤーの起動)にゲームセッションを1つ作り、実行が終わると破棄します。その間のシーン切り替えをすべてまたいで存続します。

class RpgSession : public GameSession
{
public:
    static RpgSession& Get() { return GameSession::Get<RpgSession>(); }
    PartyState Party;    // who has joined; the first ones fight
    BattleState Battle;  // the hand-off between field and battle
    QuestLog Quests;
    Inventory Items;
};

停止して再生すれば、リセット処理をどこにも書かずに新しい実行が始まります。シーンをまたぐ必要があるもの(誰がバトルを始めたか、誰が勝ったか、クエストの段階)はセッションの状態です。シーンスタックに残ったシーンは破棄されずに一時停止し、再開時に状態を読み込みます。

2. ルールは単純な値に対する静的関数

BattleRules::ResolveHit、LootRules::Roll、PartyRules::Equip。必要なもの(乱数関数も含む)を引数で受け取り、何が起きたかを返します。エンティティやシーンには一切触れないので、セッションもシーンもGPUもなしにヘッドレステストで実行できます。何が起きたかはルールが決め、何を見せるかはスクリプトが決めます。

3. ビューはスクリプトが持つ普通のクラス

ビューは自分のUIエンティティを一度だけ探し、UIのイベントを要求(「このアイテムを使う」「閉じる」)に変換し、状態からUIを書き出します。ビュー自身は何も決めません。フィールドメニューはこの方法で5つのページのビューを持っています。セリフを1文字ずつ表示するダイアログボックスのように、独自の更新処理が必要になったときだけ、ビューは独立したスクリプトになります。

4. コンテンツはデータ

ヒーロー、敵、アビリティ、アイテム、ドロップ、クエストは、リフレクションされたデータ型として.hydefファイル(1レコード)と.hytableファイル(ID付きの行)に置き、インスペクターで編集します。ヒーローを追加するのは、テーブルに1行足して、ストーリー上の適切な場所でPartyState::Get().Join("Id")を呼ぶだけです。他は何も変わりません。

セーブは自然についてくる

実行中の状態がすべて1つのセッションにあるので、セーブデータはそのセッションとプレイヤーの位置をYAMLに書き出すだけです。ロードは読み戻して、シーンシステムに現在のシーンの差し替えを頼むだけ。セーブ対象にしたい新しい状態は、Writeに1行、Loadに1行足すだけです。

まとめ

  • 実行中の状態を一つの所有者にまとめれば、シーン切り替えもプレイモードも特別なものではなくなる。
  • ルールを純粋に保てば、ゲームの面白い部分を起動せずにテストできる。
  • ビューは単純に保つ。UIはルールよりずっと頻繁に変わるから。
前の記事:ゲームコンテンツをデータに:HydroのScriptableObject次の記事:AIエージェントが読めるエンジンにする

お仕事のご相談はお気軽にどうぞ

東京在住です。Unity・C++・ゲームエンジン関連のポジション、C#・.NETのソフトウェア開発のポジション、そしてAIエージェントを活用した開発体制づくりに関心があります。英語・オランダ語・日本語(勉強中)で対応できます。