赤宿 = Red Inn

素人の試行錯誤と2Dローグライク制作 (GUI)

実装に関する様々なBobの話

 Roguelike Advent Calendar 2018 二日目の記事です。書いているのは、wanna-beというか、素人です。Bob Nystrom氏の発信をまとめただけですが……ご了承いただければと思います。

Bob Nystrom - Is There More to Game Architecture than ECS?

 この動画 が元ネタです。オブジェクト指向の話です。

1. Components (as Capabilities)

 オブジェクトをComponentの組み合わせとして捉えると、(動的な)組み合わせにより、柔軟性が得られます。たとえばキャラは、 AI, Inventory, Item などの組み合わせになります。

public class Item : Component {
    // Itemも、capabilityの組み合わせとして構成する
    val attack: Attack   // 武器の機能
    val defence: Defence // 防具の機能
    val use: Use         // アイテムの機能(読む、飲む、振る、……)
    ...
}

 これで、振ると火を吹くペットの少女を装備して攻撃できますね! (すごい絵面だ)

 Componentは長年のトレンドであり、ADOMの作者さんもはまっています。

2. Type Object Pattern (is a Must)

 Type Objectは、パラメータ依存の領域をオブジェクトにして切り出すパタンです。

public class ItemType : Content {
    val name: Name
    val price: Int
    val attack: Attack
    ...
}

 パラメータ依存の部分を、データ駆動にできるのはもちろん、抽象的なインタフェイスに置換できます。(データ駆動ではなく、Builderなどを使ってハードコーディングしても構いません)。

 定義ファイルでは、他のデータを『継承』する記法をサポートしてもいいでしょう。継承と言っても、データの上書き・拡張ができれば十分です。

:: club
    category = Melee Weapon

Cudgel[s] :: club
    art     = LightBrown \
    attack  = 1-4 wood
    price = 12

 原文よりも限定的で、how to風な書き方をしました。元の説明は比喩的です。

3. Command Pattern (is Great Good)

 Commandは、素朴にはクロージャであるとBobは認識しています

// 本質的には変数のキャプチャ
public class WalkAction( actor: Entity, dir: EDirection ) : Action( actor ) {
    // クロージャを後から .invoke() するのと似ている
    protected override fun perform() {
        ...
    }
    ...
}

 実際には、オブジェクトであるため、文脈に強く、拡張できます。プロパティを持ったり、undo()を足すこともあります。(でも、ローグライクでundoは難しそうです)。

 Actionオブジェクトを作ると、Actorのフィールドから.walk()などの無数のメソッドを抜き出すことができます。また、Actionはドメインをまたいで利用できます。(たとえば、AIとPlayerInputHandlerの両方から)。

 Commandを、how to風に抽象すると……動詞をオブジェクトに変えて独立させると、柔軟性が得られる場合があります。悩んだ時には検討したい項目です。

4. An Iterator as a Game Loop (is Essential)

 以下の記事が基になっています:

 具体的には、Amaranthのゲームループが参考になります。Hauberkのループは、言語機能の制限からか、update関数で頑張っています。(Dartには、fiber, green thread, coroutine などがありません)(たぶん)

Engine-UI Separation

 コードをEngineとUIに分けます。Engineとは、ローグライクの内部状態です。UIは、演出や、入力への対応を行います。(EngineとUIは、ModelView の関係に近いです)。

 複数のUI (CLIGUI) から、同じEngineのコードを使いたいとします。そのために、EngineをUIから分離します。

EngineとUIの関係

 Engineの状態を、ステップ単位で進められるようにします。Engineは、.process()することで僅かにゲーム状態を進め、GameResultを返します。Engineのゲームループは、GameResultを生み出し続けるものと言えます(エニュメレータ)。

 UIがEngineオブジェクトを持ちます。UIは、engine.process()で時間を進めては、進んだ分を演出します。(つまり、ログの表示やActionの演出を行います)。ユーザの入力に応じて、キャラのActionを決めてEngineに渡すこともあります。

ゲームループの実装法

 ローグライクのゲームループは、Actionのプロセッサです。本質的には、

class Engine:
    def __init__(self, scheduler: Iterable[Actor]):
        self.scheduler = scheduler
    def game_loop(self):
        for actor in self.scheduler:
            actor.gain_energy()
            for action in actor.may_take_turn():
                action.perform()
    # game_loop() の内容を、停止・再開できるようにして実装する
    def process(self) -> GameResult:
        pass

 C#では、イテレータブロックを使えば、実装が楽です。他の言語でも、fiber, green thread, coroutineなど、別名で似た機能があるかもしれません。無ければ、Hauberkのように、update関数で頑張って実装します

Actionを演出する方法?

 特にGUIの場合、 *苦戦* します。(BobのゲームはAsciiベースです)。

 まだ良い方法が思いつかないので、飛ばします。一番肝心な部分ですみません。

 むしろ、どなたか教えてください (T_T) 。EngineとUIを完全に分離したまま、Actionの演出をUIに担当させたいです。(ActionはEngineにあります)。

補足: 『ゲームループ』について

 メジャなのは特殊な定義で、FPSを生み出します。いわば、アプリケーション・レベルのゲームループです。(ゲームアプリのループと呼べば、一般名詞から区別できると思います)。

 今回考えた『ゲームループ』は、用語ではなく、単なる「ローグライク・ゲームのループ」です。Engineレベルのゲームループとも言えます。Engineの時間を入力などから分離した、という意味では、本質はゲームアプリのループと同じかもしれません。

 以上で、記事の内容は終わりです。

References

Bob

Open-Source Roguelike Games

 本記事とは関係がありませんが、参考になりそうなものを引っ張ってきました。

Else

 RogueBasinより、FoVの記事を引っ張ってきました。

 貴重な情報であり、ありがたく思います。日本語では、この手の文書はほぼ無い気がします。(あれば教えてください!)

 やはりCLI寄りですが、 r/roguelikedev も参考になりますね。

蛇足

いつかやりたい/書きたいこと

 ここが難しそう! のリストです。 こうして見ると、ローグライク・ゲームを完成させるのは、中々の栄光だと思います。

 他の方が、どのようなルートでゲーム制作の技術を得たのか、興味が湧きます。(気合だと言われそうですが……)

View
  • キャラのActionの可視化(非常に苦戦しそうです)
  • 影(視界)とフォグ (描画方法で苦戦しそうです)
    • FoWの実装と可視化
    • FoVのOOPな実装と可視化
UI
  • ダッシュやクリック移動のディテール(GUI
    • Behavior : Component の切り替え?
Engine
  • マルチタイルのEntity (e.g. 2x2)
  • セーブ・ロードのディテール(かなり苦戦しそうです)
    • versionと互換性
    • 暗号化
  • スクリプト、Interactableの実装(RPGツクールのイベントに相当するもの)
Map Editor
  • Tiledのマップにスクリプトを配置(苦戦しそうです)
    • 理想的には、Godotのように、独自のエディタにTiledのマップを表示して、スクリプトを配置していきます
    • Ascii表示のエディタでマップを編集し、ゲームではタイル表示にする人もいるようです
      • 施せられる装飾に限界があると思います
Else
  • Pure ECSの実装の理解(使いません)

以上です

 最後まで読んでいただき、ありがとうございました。

 皆様の記事を読める日を楽しみにしています。