Kagari
キーワードを入力してドキュメントを検索します。

中核概念

物語機械

物語とは、ゲームの pushdown fn の集まりです。この章では実行モデルと物語変数を説明します。プレイヤーに見えるものはステップ、物語の進み方はシーンと即時手続きで扱います。

実行モデル

エンジンは物語をプッシュダウン機械として実行します。

  • 位置は、シーンとその本体の中の一点です。
  • コールスタックは、実行中の pushdown fn の呼び出しごとに 1 つのフレームを持ちます。フレームには戻り先とその呼び出しのローカル変数が入ります。
  • 変数は、各フレームのローカル変数と物語変数です。すべて有限型です。

これらをまとめたものが σ(シグマ)、物語状態です。コードは 2 つに分かれます。

種類例入力を待つか
ステップs!(…)、choice!{…}待つ
それ以外呼び出し、if、match、ループ、代入、return待たない

物語はステップに達すると、そのステップのレンダーを実行し、ステップを表示して中断します。入力で再開します。s! なら NextStep、choice! ならアームの選択です。2 つのステップの間はすべて即座に実行されます。データが有限で、すべてのループと呼び出しの循環がステップを通らなければならないため(停止性)、2 つの入力の間のコードは必ず終わり、どのステップにどの状態で到達できるかという問いをコンパイラが決定できます。

プレゼンテーションは一方向です。物語はレンダーを選びますが、物語のコードが画面を読むことはできません。

物語変数

kis
static mut ROUTE: Route = Route::None;
static mut AFFECTION_MIO: Int<0, 5> = 0;
static mut MET_MIO: bool = false;

static mut は物語変数です。物語の 1 回の実行の間だけ存在する、大域的な物語状態です。

  • 型は有限でなければならず(E3008)、初期化子は定数式でなければなりません(E3010)。毎回の実行は初期化子から始まるので、未設定の変数を読むことはありません。
  • 読み書きできるのは pushdown fn だけです。fn や const fn のコードが物語変数を名指すと E3007 になります。これにより、システムが物語を読むことができなくなります。
  • 型に参照を含めることはできません(E3012)。
  • 物語はシングルスレッドなので、unsafe なしで書き込めます。

pushdown fn のローカル変数は普通の let と let mut の束縛です。呼び出しのフレームに置かれ、再帰呼び出しはそれぞれ自分のコピーを持ちます。

kis
enum Route { None, Hanako, Azusa, Both }

列挙型は普通の Rust の列挙型です。データが有限であれば、ヴァリアントにデータを持たせられます。

不変の static は物語変数ではありません。Setting<T> か Meta<T> のハンドルを持たなければなりません(E3011)。ゲームシステムを参照してください。

例:学園祭のハブ

地図から屋台を訪れ、各屋台は訪問済みかどうかを覚えています。シーン StallB、StallC、DormNight は省略しています。

kix
static mut VISITED_A: bool = false;
static mut VISITED_B: bool = false;
static mut VISITED_C: bool = false;
enum Ending { Normal }
enum Map { A, B, C, Leave }
#[scene]
#[entry]
pushdown fn Day1() -> Ending {
s!(t!["The school festival starts today."]);
FestivalHub();
DormNight();
Ending::Normal
}
#[scene]
pushdown fn FestivalHub() {
loop {
match choice!{
id = "map",
t!["Go to stall A"] => Map::A,
t!["Go to stall B"] => Map::B,
t!["Go to stall C"] => Map::C,
t!["Leave"] if VISITED_A && VISITED_B && VISITED_C => Map::Leave,
} {
Map::A => StallA(),
Map::B => StallB(),
Map::C => StallC(),
Map::Leave => break,
}
}
}
#[scene]
pushdown fn StallA() {
if VISITED_A {
s!(t!["The stall is already packing up."]);
return;
}
VISITED_A = true;
s!(who = "Hanako", render = hanako_jumps, t!["Welcome to stall A!"]);
}
const fn hanako_jumps(mut screen: Screen) -> Screen {
screen.entity(HANAKO).apply_animation(jump(20, ms(300)));
screen
}
  • choice! は 1 つのステップです。プレイヤーが選んだアームのヴァリアントに評価され、普通の match でそれに応じて分岐します。
  • Leave は 3 つの屋台をすべて訪れた後にだけ提示されます(if ガード)。
  • 各屋台はハブのループに戻り、ループが再び地図を表示します。break でループを抜けるとハブが戻り、Day1 は DormNight() に進みます。
  • ループを一周するすべての経路が choice! ステップを通るので、このループは許されます(停止性)。

戻り先はスタックが覚えているので、FestivalHub は複製することなく Day1 からも Day2 からも呼べ、そのたびに正しい場所へ戻ります。

Day1 FestivalHub() DormNight() #[scene] pushdown fn FestivalHub 開始 loop: 地図 ⇄ 屋台 return Day2 FestivalHub() EndingCheck() 呼び出し: Day1 を push 呼び出し: Day2 を push return: pop return: pop 2 日目の StallA 内でのスタック 先頭 StallA 現在のシーン FestivalHub StallA() の後から再開 Day2 FestivalHub() の後から再開

最終更新日 2026年10月29日