プログラマーが使いやすい「ほどよく緩い」コマンド選択型ゲーム用の独自エンジンを作ってみた【ミオの○○な一日 開発記】

スポンサーリンク

<PR>『ミオの○○な一日』【体験版】を公開しました!!。

現在開発中のコマンド選択型ゲーム「ミオの〇〇な一日」の中核を担うシステム、「ComponentEngine」について紹介します。

Fungusへのリスペクト、そして「自作」の道へ

「ComponentEngine」は、Unityで広く使われているノベル・アドベンチャー用アセット「Fungus」のような役割を果たすシステムです。

Fungusについては、↓の記事を参照してください

Fungusは、ノーコードでゲームが完成するほどの高い完成度を持つ素晴らしいアセットです。
しかし、多数の変数が絡み合う複雑なロジックを組もうとすると、GUIベース特有の「管理の難しさ」に直面することもありました。

そこで、Fungusの良さを取り入れつつも、
「複雑な処理や変数の管理はすべてGDScript側に任せる」
という割り切った設計を目指しました。

「ノーコード」ではなく「ローコード」を目指した理由

ComponentEngineの最大の特徴は、
あえて独自の変数システムを持たない
ことです。

これにより、Godotエンジンの柔軟性を最大限に活かしつつ、プログラマーがGDScriptでガリガリとロジックを書ける余地を残しました。
いわば、「ノーコード」の直感性と「プログラミング」の自由度を両立させた「ローコード」な設計です。

中核を成すコンポーネント群

「ComponentEngine」は、ゲーム内の各種UIや進行を制御するためのコンポーネントを、簡易的なコマンド(スクリプト)で制御します。

全体像は、↓のフローチャートになります。

flowchart LR node_1["GDScript"] node_2["ComponentEngine"] node_3["SpeakDialog"] node_4["MenuDialog"] node_5["PhaseManager"] node_6["CharacterManager"] node_7["BackgroundManager"] node_8["FlagManager"] node_1 --"簡易スクリプト"--> node_2 node_2 --> node_3 node_2 --> node_4 node_2 --> node_5 node_2 --> node_6 node_2 --> node_7 node_2 --> node_8

主な構成要素は以下の通りです。

  • ComponentEngine本体
    受け取った簡易スクリプトを元に、各種DialogやManagerに指示を出し、全体のシーケンスを制御するコントローラーです。
  • SpeakDialog
    キャラクターのセリフを表示するダイアログです。
    GodotのRichTextLabelとBBCodeをフル活用し、リッチなテキストアニメーションや表現を実現しています。
  • MenuDialog
    プレイヤーへの選択肢提示を担当します。
    選択されたボタンのid(int型)を返すだけの極めてシンプルな仕様にしました。
    あえて複雑な処理を持たせないことで、その後の分岐処理をGDScript側で自由に記述できるようにしています。
  • PhaseManager
    ゲーム内の「一場面」を一つの"Phase"として定義して、
    状態遷移や進行状況を一元管理します。
  • 各種マネージャ群
    • CharacterManager:立ち絵の表示・制御
    • BackgroundManager:背景の切り替え
    • FlagManager:フラグ管理

完全にGUIで完結させるのではなく、GDScriptと簡易スクリプトを組み合わせることで、「拡張性の高さ」と「記述のしやすさ」の黄金比を目指しました。

使い方解説

下のコードが、ComponentEngine の一般的な使い方です。
(※ここでは、詳細の説明は省略します。)

 ## コンポーネントエンジンを使用する
@onready var engine = $ComponentEngine

func start() -> void:

    # 1. スクリプト作成。
    var _script = [
        ["_FadeIn", "UsualField"],
        ["@FadeIn", "Mio"],
        ["Mio", "よし!。レイのために、野イチゴを採るぞ!!。"],
        ["@FadeIn", "Misaki"],
        ["Misaki", "あんまり、より道しないようにするのよ。"],
        ["Misaki", "あなたが、より道しだすと、止まらないんだから。"],
        ["Mio", "うん。わかった。\n「なるべく」気を付ける。"],
        ["Misaki", "「なるべく」ね…。(ため息)"],
    ]

    # 2. エンジンにスクリプトを投げる。
    await engine.start_script(_script)
    engine.speak_dialog.hide()

    # 3. MenuDialog の設定。
    var _menu_item_list_data = [
        ["先へ進む", State.OPEN],
    ]

    # 4. MenuDialog の表示 → 選択。
    var _i:int = await exec_menu(_menu_item_list_data)

    # 5. 選択されたボタン別に実行。
    if _i == 0:

        # 1. スクリプト作成。
        _script = [
            ["@FadeOut", "Misaki"],
            ["@FadeOut", "Mio"],
            ["_FadeOut"],
            [">Goto", "BigTreePhase"],
        ]

        # 2. エンジンにスクリプトを投げる。
        await  engine.start_script(_script)
  1. スクリプト作成。
  2. エンジンにスクリプトを投げる。
    →エンジンから各マネージャーやダイアログに命令して、
    処理を行わせる。
  3. MenuDialog の設定。
  4. MenuDialog の表示 → 選択。
  5. 選択されたボタン別に実行。

文字列の配列で作成した簡易スクリプトをComponentEngineに投げることで、様々な処理を促している事が分かるかと思います。

これを、ひとつの フェイズ内で実行し、別のフェイズへ移動していくことで、シナリオが進んでいくことになります。

おわりに

今回は、開発中のゲーム「ミオの〇〇な一日」の中核となる「ComponentEngine」の全体像について紹介しました。
次回以降の連載記事では、このエンジンを構成する「SpeakDialog」のBBCode活用法や、各コンポーネントの詳細について掘り下げて紹介していきたいと思います!

コメント

タイトルとURLをコピーしました