安定性: 安定 — 現在の
stdアイテムだけで組み立てたレシピです。
レンダーの組み立て
レンダーは const fn(Screen) -> Screen です。大きな演出は 2 種類の関数を組み合わせて作ります。
- ヘルパーは
&mut Screenを受け取って何も返しません。Characterのメソッドがその例です。どのレンダーからも、ほかのヘルパーからも呼べます。 - レンダーは画面を値で受け取り、ヘルパーや射影を呼んで画面を返します。ステップは
s!(render = name, …)で使います。
レンダーの中では、タイムラインのカーソルが各変更の開始時刻を決めます。2 回のカーソル移動の間にある文は同時に始まり、barrier()、wait(d)、at_mark(m) がカーソルを動かします。すべての変更はアニメーションを明示し、生成されたエンティティは不透明度 0.0 から始まるので、登場のたびに不透明度をフェードやトゥイーンで上げます。
1 つの演出の間だけ存在すべきエンティティ(幕、カード)は、フェードアウトさせるのと同じレンダーで削除します。エンティティはすぐに論理画面から消え、フェードが終わるまでは描画され続けます。
共通の宣言
以下のレシピはすべてこれらの宣言を前提とします。スタジオのエンティティはキャラクターより上にあります。
use std::narrative::Mark;use std::narrative::text::text_style;use std::screen::BOTTOM_CENTER;use std::screen::shader::{ColorShader, filter};use std::stage::{hide_all, side_face};
const HANAKO: Character = character(EntityId(1)).offset(offset(0, 40)).anchor(BOTTOM_CENTER);
mod HANAKO { pub const NORMAL: ImageAsset = asset("hanako_normal.png"); pub const ANGRY: ImageAsset = asset("hanako_angry.png"); pub const SURPRISED: ImageAsset = asset("hanako_surprised.png");}
const CENTER: Point = point(640, 720);const RIGHT: Point = point(960, 720);
const CURTAIN: EntityId = EntityId(100);const TINT: EntityId = EntityId(101);const CARD: EntityId = EntityId(102);const ITEM: EntityId = EntityId(103);const STILL: EntityId = EntityId(104);
mod BG { pub const CLASSROOM: ImageAsset = asset("bg_classroom.png"); pub const NIGHT: ImageAsset = asset("bg_night.png");}
mod CG { pub const FIREWORKS: ImageAsset = asset("cg_fireworks.png");}
const BLINDS_RULE: ImageAsset = asset("rule_blinds.png");const KEY_ITEM: ImageAsset = asset("item_key.png");/// The arguments of the flashback filter.#[shader_arg]struct Memory { strength: Decimal,}const MEMORY: ColorShader<Memory> = asset("memory.wgsl");const THEME: AudioAsset = asset("bgm_theme.ogg");const KNOCK: AudioAsset = asset("se_knock.ogg");スロット定数 CENTER は std のアンカー std::screen::CENTER を隠すので、アンカーが必要なレシピではパスで書きます。
シーンの開始
音楽が始まると同時に背景がルールで現れ、barrier の後に花子がサイド画像とともに登場します。
const fn opening(mut screen: Screen) -> Screen { screen.background().change(BG::CLASSROOM).apply_animation(rule(BLINDS_RULE, ms(1000))); screen.music().change(looped(THEME).region(secs(16), Some(secs(80)))).apply_animation(crossfade(ms(1000))); screen.barrier(); HANAKO.show(&mut screen, HANAKO::NORMAL, CENTER, tween(ms(300))); side_face(&mut screen, Some(HANAKO::NORMAL), fade(ms(300))); screen}暗転
塗りだけを持つ Front エンティティが画面を覆います。塗りが TRANSPARENT → BLACK と変化し、画面が黒い間に背景が切り替わり、塗りが元に戻ります。幕はすぐに削除されるので論理画面には含まれませんが、フェードが終わるまでは描画されます。
/// Fades the screen to black, swaps the background while it is hidden, and fades back.const fn dip_to_black(screen: &mut Screen, next: ImageAsset) { screen.entity(CURTAIN).set_plane(Plane::Front); screen.entity(CURTAIN).opacity().change(1.0).apply_animation(instant()); screen.entity(CURTAIN).fill().change(BLACK).apply_animation(tween(ms(1000))); screen.barrier(); screen.background().change(next).apply_animation(cut()); screen.entity(CURTAIN).fill().change(TRANSPARENT).apply_animation(tween(ms(1000))); screen.despawn(CURTAIN);}
const fn night_falls(mut screen: Screen) -> Screen { hide_all(&mut screen, tween(ms(500))); side_face(&mut screen, None, fade(ms(300))); dip_to_black(&mut screen, BG::NIGHT); screen}色を経由するだけの背景変更なら、背景の差し替えに through(BLACK, d, hold) を使えば、エンティティなしで同じことができます。
回想
舞台の上に color ミックスシェーダーで合成する塗りを重ね、型付きの引数を持つシェーダーの画面フィルターを加えます。色調は、終了用のレンダーがフェードさせて削除するまで論理画面に残ります。
/// Tints the stage with a colour mix and a shader filter, for a memory.const fn flashback(mut screen: Screen) -> Screen { screen.entity(TINT).set_plane(Plane::Front); screen.entity(TINT).mix().change(Some(std::shaders::mix::color())).apply_animation(instant()); screen.entity(TINT).fill().change(hex(0x382000)).apply_animation(instant()); screen.entity(TINT).opacity().change(0.6).apply_animation(tween(ms(800))); screen.filter().change(Some(filter(MEMORY, Memory { strength: 0.8 }))).apply_animation(tween(ms(800))); screen}
/// Ends the flashback.const fn flashback_end(mut screen: Screen) -> Screen { screen.entity(TINT).opacity().change(0.0).apply_animation(tween(ms(800))); screen.despawn(TINT); screen.filter().change(None).apply_animation(tween(ms(800))); screen}現在唯一のランタイムプロファイルである native_v0 はビルド時に .wgsl シェーダーアセットを拒否するため、MEMORY フィルターはまだビルドできません(ランタイムプロファイル を参照)。MEMORY と filter() の行を削除してください。色調だけでも機能します。
スチル(CG)
まずテキストウィンドウが隠れ、barrier の後に CG がルールで現れます。次のステップでウィンドウが戻ります。
/// Hides the text window, then reveals a CG through a rule.const fn still(mut screen: Screen) -> Screen { screen.hud().text_window().change(Visibility::Hidden).apply_animation(tween(ms(300))); screen.barrier(); screen.entity(STILL).set_plane(Plane::Front); screen.entity(STILL).opacity().change(1.0).apply_animation(instant()); screen.entity(STILL).image().change(CG::FIREWORKS).apply_animation(rule(BLINDS_RULE, ms(1000))); screen}
/// Brings the text window back over the CG.const fn still_text(mut screen: Screen) -> Screen { screen.hud().text_window().change(Visibility::Shown).apply_animation(tween(ms(300))); screen}s!(render = still);s!(render = still_text, t!["The fireworks lit the sky."]);CG はステップの論理画面に載った時点で、ギャラリー用に既読として扱われます(std::system を参照)。
章タイトル
テキストウィンドウの上のラベルエンティティがフェードインし、待ってからフェードアウトし、削除されます。削除は論理画面上で即座に行われるので次のステップはカードを引き継ぎませんが、フェードアウトは描画されます。
/// Shows a chapter title over the text window, then removes it once it has faded out.const fn chapter_card(mut screen: Screen) -> Screen { screen.entity(CARD).set_plane(Plane::OverWindow); screen.entity(CARD).set_anchor(std::screen::CENTER); screen.entity(CARD).position().change(point(640, 360)).apply_animation(instant()); screen.entity(CARD).label().set_style(text_style(48, WHITE)); screen.entity(CARD).label().change(t!["Chapter 1"]).apply_animation(cut()); screen.entity(CARD).opacity().change(1.0).apply_animation(tween(ms(500))); screen.wait(ms(2500)); screen.entity(CARD).opacity().change(0.0).apply_animation(tween(ms(500))); screen.despawn(CARD); screen}アイテム表示
アイテムは小さく生成され、フェードインしながら行き過ぎるイージングで大きくなります。
/// Pops an item up in the middle of the screen.const fn item_popup(mut screen: Screen) -> Screen { screen.entity(ITEM).set_plane(Plane::Front); screen.entity(ITEM).set_anchor(std::screen::CENTER); screen.entity(ITEM).position().change(point(640, 300)).apply_animation(instant()); screen.entity(ITEM).image().change(KEY_ITEM).apply_animation(cut()); screen.entity(ITEM).scale().change(0.8).apply_animation(instant()); screen.entity(ITEM).scale().change(1.0).apply_animation(tween(ms(300)).ease(Easing::OutBack)); screen.entity(ITEM).opacity().change(1.0).apply_animation(tween(ms(300))); screen}
/// Removes the item popup.const fn item_popup_end(mut screen: Screen) -> Screen { screen.entity(ITEM).opacity().change(0.0).apply_animation(tween(ms(300))); screen.despawn(ITEM); screen}2 つの倍率変更は同じカーソル位置で始まります。1 つ目が即座に 0.8 を設定し、2 つ目が 0.8 から 1.0 へ補間します。
行の途中のキュー
テキストが mark(1) で位置に名前を付け、レンダーがそこにカーソルを固定します。
/// Changes Hanako's face when the text reaches mark 1.const fn surprised_at_mark(mut screen: Screen) -> Screen { screen.at_mark(Mark(1)); HANAKO.face(&mut screen, HANAKO::SURPRISED, fade(ms(300))); screen}s!(who = "Hanako", render = surprised_at_mark, t).and(mark(1))) "—what?"]);“Wait” の後で表示が 500 ms 止まり、“—what?” が始まると表情が変わります。クリックすると行全体が表示され、表情の変化もすぐに完了します。
連続音とモーション
遅延付きのワンショットで連続音を作り、キャラクターのモーションとカメラの揺れで重みを加えます。どちらのモーションも論理画面を変えません。
const fn knocking(mut screen: Screen) -> Screen { screen.play(sound(KNOCK)); screen.play(sound(KNOCK).delay(ms(250))); screen.play(sound(KNOCK).volume(0.6).delay(ms(500))); HANAKO.face(&mut screen, HANAKO::ANGRY, fade(ms(300))); HANAKO.act(&mut screen, shake(10, ms(600), 3)); screen}
const fn earthquake(mut screen: Screen) -> Screen { screen.camera().apply_animation(quake(8, 4, ms(500))); HANAKO.move_to(&mut screen, RIGHT, tween(ms(1000))); screen}無効なアーム
ステップが表示する選択肢に、表示専用のアームを加えます。選択できず、どの解析にも見えません。このレンダーをいつ使うかは物語のコードが決めます(std::narrative を参照)。
const fn locked_arm(mut screen: Screen) -> Screen { screen.choice_menu().disabled_arm(1, t!["Locked"]); screen}最終更新日 2026年10月29日