物語とは、ゲームの pushdown fn の集まりです。この章では実行モデルと物語変数を説明します。プレイヤーに見えるものはステップ、物語の進み方はシーンと即時手続きで扱います。
実行モデル
エンジンは物語をプッシュダウン機械として実行します。
- 位置は、シーンとその本体の中の一点です。
- コールスタックは、実行中の
pushdown fnの呼び出しごとに 1 つのフレームを持ちます。フレームには戻り先とその呼び出しのローカル変数が入ります。 - 変数は、各フレームのローカル変数と物語変数です。すべて有限型です。
これらをまとめたものが σ(シグマ)、物語状態です。コードは 2 つに分かれます。
| 種類 | 例 | 入力を待つか |
|---|---|---|
| ステップ | s!(…)、choice!{…} | 待つ |
| それ以外 | 呼び出し、if、match、ループ、代入、return | 待たない |
物語はステップに達すると、そのステップのレンダーを実行し、ステップを表示して中断します。入力で再開します。s! なら NextStep、choice! ならアームの選択です。2 つのステップの間はすべて即座に実行されます。データが有限で、すべてのループと呼び出しの循環がステップを通らなければならないため(停止性)、2 つの入力の間のコードは必ず終わり、どのステップにどの状態で到達できるかという問いをコンパイラが決定できます。
プレゼンテーションは一方向です。物語はレンダーを選びますが、物語のコードが画面を読むことはできません。
物語変数
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 の束縛です。呼び出しのフレームに置かれ、再帰呼び出しはそれぞれ自分のコピーを持ちます。
enum Route { None, Hanako, Azusa, Both }列挙型は普通の Rust の列挙型です。データが有限であれば、ヴァリアントにデータを持たせられます。
不変の static は物語変数ではありません。Setting<T> か Meta<T> のハンドルを持たなければなりません(E3011)。ゲームシステムを参照してください。
例:学園祭のハブ
地図から屋台を訪れ、各屋台は訪問済みかどうかを覚えています。シーン StallB、StallC、DormNight は省略しています。
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 からも呼べ、そのたびに正しい場所へ戻ります。