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

ガイド

演出レシピ

安定性: 安定 — 現在の std アイテムだけで組み立てたレシピです。

レンダーの組み立て

レンダーは const fn(Screen) -> Screen です。大きな演出は 2 種類の関数を組み合わせて作ります。

  • ヘルパーは &mut Screen を受け取って何も返しません。Character のメソッドがその例です。どのレンダーからも、ほかのヘルパーからも呼べます。
  • レンダーは画面を値で受け取り、ヘルパーや射影を呼んで画面を返します。ステップは s!(render = name, …) で使います。

レンダーの中では、タイムラインのカーソルが各変更の開始時刻を決めます。2 回のカーソル移動の間にある文は同時に始まり、barrier()、wait(d)、at_mark(m) がカーソルを動かします。すべての変更はアニメーションを明示し、生成されたエンティティは不透明度 0.0 から始まるので、登場のたびに不透明度をフェードやトゥイーンで上げます。

1 つの演出の間だけ存在すべきエンティティ(幕、カード)は、フェードアウトさせるのと同じレンダーで削除します。エンティティはすぐに論理画面から消え、フェードが終わるまでは描画され続けます。

共通の宣言

以下のレシピはすべてこれらの宣言を前提とします。スタジオのエンティティはキャラクターより上にあります。

kix
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 の後に花子がサイド画像とともに登場します。

kix
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 と変化し、画面が黒い間に背景が切り替わり、塗りが元に戻ります。幕はすぐに削除されるので論理画面には含まれませんが、フェードが終わるまでは描画されます。

kix
/// 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 ミックスシェーダーで合成する塗りを重ね、型付きの引数を持つシェーダーの画面フィルターを加えます。色調は、終了用のレンダーがフェードさせて削除するまで論理画面に残ります。

kix
/// 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 がルールで現れます。次のステップでウィンドウが戻ります。

kix
/// 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
}
kix
s!(render = still);
s!(render = still_text, t!["The fireworks lit the sky."]);

CG はステップの論理画面に載った時点で、ギャラリー用に既読として扱われます(std::system を参照)。

章タイトル

テキストウィンドウの上のラベルエンティティがフェードインし、待ってからフェードアウトし、削除されます。削除は論理画面上で即座に行われるので次のステップはカードを引き継ぎませんが、フェードアウトは描画されます。

kix
/// 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
}

アイテム表示

アイテムは小さく生成され、フェードインしながら行き過ぎるイージングで大きくなります。

kix
/// 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) で位置に名前を付け、レンダーがそこにカーソルを固定します。

kix
/// 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
}
kix
s!(who = "Hanako", render = surprised_at_mark, t!["Wait" ![""](pause(ms(500)).and(mark(1))) "—what?"]);

“Wait” の後で表示が 500 ms 止まり、“—what?” が始まると表情が変わります。クリックすると行全体が表示され、表情の変化もすぐに完了します。

連続音とモーション

遅延付きのワンショットで連続音を作り、キャラクターのモーションとカメラの揺れで重みを加えます。どちらのモーションも論理画面を変えません。

kix
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 を参照)。

kix
const fn locked_arm(mut screen: Screen) -> Screen {
screen.choice_menu().disabled_arm(1, t!["Locked"]);
screen
}

最終更新日 2026年10月29日