UI によるシーンジャンプと設定の切り替え
ボタンを押すと、あらかじめ書いておいた別のオープニングにジャンプする。テキストボックスに入力すると、設定が AI に伝える内容が変わる。このレシピでは両方を、まずコードなしで、次に画面のコードで作ります。
パート 1:ボタンでオープニングを切り替える
これから作るもの
あらかじめ書いたオープニングの場面がいくつもあるカードです。プレイヤーは最初に「メイン」のオープニングを見て、その下に押せるボタンがあります。ボタンを押すと、チャットの最初のメッセージが別のオープニングにすぐ切り替わります。AI は何も生成せず、あなたが書いた文章がそのまま出ます。
しくみ
カードにはオープニングを複数持たせられます。プレイヤーが新しくプレイを始めると、それらはすべて最初のメッセージの スワイプ としてまとめられます(左右で切り替え)。オープニングはそれぞれ、変数の初期値も持っています。
api.switchGreeting(index) は、その番号のオープニングへ直接ジャンプします。プレイヤー画面のボタンの オープニングを切り替え ステップは、同じことをコードなしで行います。
プレイヤーが「Enter the Dark Cave」を押す
→ ボタンがオープニング #2 に切り替える(index は 0 から数えるので、1 = 2 つ目)
→ 変数がそのオープニングの初期値に戻る
→ プレイヤーには、あなたが書いた暗い洞窟のオープニングが表示されるコードなしで作る
- オープニングを書く(下のステップ 1)
- プレイヤー画面で テンプレート を押し、オープニングを選ぶ を追加する。オープニングごとに一枚のカードが並び、プレイヤーがタップしたところから物語が始まります。または、ページに自分でボタンを置き、それぞれに オープニングを切り替え のステップを付けます
- ルートごとに別の設定を送りたいなら、下のステップ 2 と 3 に従います。こちらもコードは要りません
ルートを選んだときにほかのこと(音楽、お知らせ)も起こしたいなら、オープニングを切り替え のステップの 後 に ビヘイビアを動かす のステップを足します。切り替えると変数はそのオープニングの初期値に戻るので、切り替えの前に変えたものは消えてしまいます。
手順
ステップ 1:オープニングを書く
キャンバスでオープニングの行をクリックし、1 つ目を書きます。もう 1 つ足すときは、オープニングのブロックの右上にある + を押します。
1 つ目のオープニング(メインのオープニング。ルートの選択肢を出す):
プレイヤーがプレイを開いて最初に見るものです。場面を描写して、選択へと導きます。
*あなたは見知らぬ森で目を覚ます。古い木々のあいだを朝霧が渦巻いている。*
目の前で、道が二つに分かれている。
**左へ**:闇の奥へ続く細い小道。冷たい空気と、遠いこだま。
**右へ**:木漏れ日の差す道。野の花と鳥のさえずり。
どちらへ進む?オープニングは あなたが書いた決まった文章 で、AI が生成したものではありません。プレイヤーが目にする言葉を一字一句コントロールできます。
2 つ目のオープニング(暗い洞窟):
*あなたは左の道へ踏み出す。頭上の枝葉が厚くなり、光を飲み込んでいく。数分もしないうちに小道は岩肌の裂け目へと細まった。洞窟の入口だ。*
*冷たい空気が吹き出してくる。湿った石と、金属のような匂い。奥のほうで青緑の光がかすかにまたたく。壁にしがみつく光るキノコだ。*
*息を吸って、足を踏み入れる。背後で最後の日の光が細い線になり、やがて消えた。*
あなたは暗闇の中にひとりきりだ。この文章は、プレイヤーが「Enter the Dark Cave」を押したあとにだけ表示されます。それまでは 1 つ目のオープニングが見えています。
3 つ目のオープニング(日の当たる草原):
*あなたは右の道を選ぶ。木々がまばらになり、暖かな日差しが枝葉のあいだから降り注ぐ。数分もしないうちに森が開け、地平線まで続く広い草原に出た。*
*色とりどりの野の花がそよ風に揺れている。遠くで小川がきらめく。どこか近くで、聞いたことのない旋律を鳥が歌っている。*
*肩のこわばりがほどけていく。ここがどこであれ、安全な場所だと感じる。*
エバーブルームの草原へようこそ。オープニングの順番が index になる
オープニングの並び順が、switchGreeting() の index になります。0 から数えるので、1 つ目のオープニングが index 0(デフォルトで表示)、2 つ目が index 1、3 つ目が index 2 です。プレイヤー画面の オープニングを切り替え ステップでは、オープニング 1、2、3 として並びます。
ステップ 2:オープニングごとに値を持つルート変数を作る
プレイヤーがどのルートにいるかを変数に記録します。使い道は 2 つです。
- ルートを選んだらボタンを隠す(コードは値がまだ
"none"かどうかを見ます) - ルートごとの設定を送る(ステップ 3 で、この変数を条件に設定を送ります)
キャンバスで、追加できるもの の + 変数 を押します。
| 項目 | 入れるもの | 理由 |
|---|---|---|
| 変数名 | current_route | コードと条件は、この名前で変数を探します |
| 型 | 文字 | 値が文字だから("none"、"dark"、"light") |
| 初期値 | none | 「まだ選んでいない」の意味 |
| AI アクセス(AI での扱い の中) | AI は読み取り専用 | 値を決めるのはオープニングだけ。AI は見られるが変えられない |
次に、ルートごとに初期値を持たせます。2 つ目のオープニングを current_route の上にドラッグして dark に、3 つ目のオープニングを同じようにドラッグして light にします。プレイヤーがオープニングを切り替えると、current_route はそのオープニングの値になります。
ステップ 3:(任意)ルートごとに設定を用意する
ルートを選んだあと、AI の返信でルートごとに違う世界設定を使ってほしいなら、このステップを行います。オープニングの文章を切り替えるだけでいいなら、飛ばしてかまいません。
設定を 2 つ追加し、どちらも 条件ロア にします。current_route を設定の上にドラッグし、できた線をクリックして条件を決めます(洞窟の設定は current_route = dark、草原の設定は light)。条件ロアは、条件が成り立っているあいだだけ送られます。
暗い洞窟の設定(条件:current_route = dark):
[世界設定:シャドウモウの洞窟]
プレイヤーはシャドウモウの洞窟を探索している。要点:
- 何世紀も前に打ち捨てられた、古いドワーフの遺跡
- 光るキノコが、かすかな青緑の光を放っている
- 奥のトンネルには奇妙な生き物が潜んでいる
- 奥へ進むほど気温が下がる
緊張感のあるホラーサバイバルの雰囲気を保つこと。反響する音、揺らめく影、したたる水、頭上にのしかかる岩の重さを描写する。日の当たる草原の設定(条件:current_route = light):草原の設定と雰囲気を書きます。
プレイヤーが選ぶまでは current_route が none なので、どちらの設定も送られません。
ルート専用の設定、変数、AI がたくさんあるなら、代わりにシチュエーションにまとめ、オープニングをそのシチュエーションの上にドラッグします。するとそのシチュエーションは、そのオープニングから始めたプレイの中にだけ存在するようになります。
ステップ 4:画面のコードでルートボタンを置く
最初のチャットの吹き出しの中にボタンを置きたいなら、画面のコードで書きます。パネル → フロントエンドコード → index.tsx を開き、中身を次のものに置き換えます。
export default function MyWorld() {
const api = useYumina();
const hasChosen = api.variables.current_route !== "none";
return (
<Chat renderBubble={(msg) => (
<div>
{/* Render the message text normally */}
<div
style={{ color: "#e2e8f0", lineHeight: 1.7 }}
dangerouslySetInnerHTML={{ __html: msg.contentHtml }}
/>
{/* Route selection buttons */}
{/* msg.messageIndex === 0 means only show on the first message */}
{/* !hasChosen means hide once a choice has been made */}
{msg.messageIndex === 0 && !hasChosen && (
<div style={{
display: "flex",
gap: "12px",
marginTop: "16px",
}}>
<button
onClick={() => api.switchGreeting(1)} // Switch to the 2nd opening
style={{
flex: 1,
padding: "16px",
background: "linear-gradient(135deg, #1e1b4b, #312e81)",
border: "1px solid #4338ca",
borderRadius: "12px",
color: "#c7d2fe",
fontSize: "15px",
fontWeight: "bold",
cursor: "pointer",
}}
>
Enter the Dark Cave
</button>
<button
onClick={() => api.switchGreeting(2)} // Switch to the 3rd opening
style={{
flex: 1,
padding: "16px",
background: "linear-gradient(135deg, #365314, #4d7c0f)",
border: "1px solid #65a30d",
borderRadius: "12px",
color: "#ecfccb",
fontSize: "15px",
fontWeight: "bold",
cursor: "pointer",
}}
>
Walk to the Sunlit Meadow
</button>
</div>
)}
</div>
)} />
);
}1 行ずつの説明:
<Chat renderBubble={...} />:プラットフォーム標準のチャット画面を使います(入力欄、スワイプ、セーブポイントはすべて組み込み済み)。あなたが受け持つのは吹き出しの描き方だけですconst api = useYumina():Yumina の API を取得します。変数の読み書き、ビヘイビアの実行、オープニングの切り替えができますapi.variables.current_route:いまのルート変数の値を読みますhasChosen:"none"でなければ、プレイヤーはもう選んでいますmsg.contentHtml:renderBubble に渡される、レンダリング済みの HTML です(Markdown は処理済み)msg.messageIndex === 0:ボタンを最初のメッセージにだけ出します(すべてのメッセージには出しません)api.switchGreeting(1):最初のメッセージを index 1(2 つ目のオープニング)に切り替えます。変数はそのオープニングの初期値に戻るので、current_routeが"dark"になり、ボタンが消え、洞窟の設定が送られ始めます
押したときにもっと何かする。 ボタンでビヘイビアも動かしたい場合(音楽を流す、お知らせを出す、ほかの値を変える)は、先に切り替え、切り替えが終わってからビヘイビアを動かします。
onClick={async () => {
await api.switchGreeting(1); // resets variables to opening 2's values
await api.executeActionAndWait("choose-dark"); // a behavior triggered by The player presses a button → Button "choose-dark"
}}切り替えの 前 に setVariable を呼んだりビヘイビアを動かしたりしても、残りません。切り替えがそのオープニングの初期値を戻してしまうからです。
コードを自分で書きたくないときは
キャンバス右上の AIアシスタント を押して、ほしいボタンを説明してください。コードを書いてくれます。
ステップ 5:保存してテストする
- 上部バーの 保存 を押す
- プレイ を押してプレイテストを始める
- メインのオープニングと、その下に 2 つのボタンが表示される
- 「Enter the Dark Cave」を押す。最初のメッセージが洞窟のオープニングに変わり、ボタンが消える
- AI にいくつかメッセージを送る。ステップ 3 を行っていれば、AI の返信は洞窟の設定に沿ったものになる
- もう一方のルートを試すには、新しいプレイテストを始めて、もう一方のボタンを押す
うまくいかないときは:
| 症状 | 考えられる原因 | 直し方 |
|---|---|---|
| ボタンが見えない | 画面のコードが保存されていないか、構文エラーがある | パネル → フロントエンドコード の表示が エラー ではなく OK になっているか確認する |
| ボタンは押せるが、オープニングが切り替わらない | オープニングが足りない | カードにオープニングが 3 つあるか確認する |
| オープニングは切り替わるが、ボタンが残る | そのオープニングに current_route の初期値がない | そのオープニングを current_route の上にドラッグし、値を設定する |
| 設定が切り替わらない | 設定の条件が合っていない | current_route と設定のあいだの線をクリックし、値(dark / light)を確認する |
| 押したときに設定した値が消えている | 切り替えの前に設定している | 先に switchGreeting を await してから値を変える |
パート 2:プレイヤーの入力で設定の内容を変える
これから作るもの
プレイヤーが何か(独自のルール、キャラクターの名前、物語への指示)を入力するテキスト欄です。入力した文字は変数に入り、設定がその変数を含んでいるので、AI が次に読む内容が変わります。
しくみ
設定では マクロ が使えます。設定の本文に {{variable_name}} と書くと、エンジンが AI 向けのプロンプトを組み立てるたびに、その場所が変数の現在の値に置き換わります。
たとえば:
- 設定にこう書く:
Special rule: {{custom_rule}} - 変数
custom_ruleの値は"All magic is allowed" - AI が受け取るプロンプトでは、その行がこう書き換わる:
Special rule: All magic is allowed
置き換えはプロンプトを組み立てるたびに行われます。つまり、プレイヤーが次のメッセージを送り、AI が返信しようとするときです。
タイミングの全体像:
1. 設定の本文:「Special rule: {{custom_rule}}」
2. 変数 custom_rule の現在の値 =「All magic is allowed」
3. プレイヤーがメッセージを送る → エンジンがプロンプトを組み立てる → {{custom_rule}} を変数の値に置き換える
→ AI は「Special rule: All magic is allowed」を受け取る → それに沿って返信する
4. プレイヤーが入力欄に「Magic is forbidden」と入力し、「Apply」を押す
5. 変数 custom_rule が「Magic is forbidden」になる
→ 変数の値はすぐに更新される
6. AI にはまだ見えない。プロンプトがまだ組み立て直されていないから
7. プレイヤーがもう一度メッセージを送る → エンジンがプロンプトを組み立て直す → 今度は新しい値を使う
→ AI は「Special rule: Magic is forbidden」を受け取る → 新しいルールに従い始める変数はすぐに変わり、AI がその変化を見るのは次のメッセージからです。
コードなしで作る
プレイヤー画面で 入力欄 の部品(1行テキスト か 文章)を追加し、答えを入れる変数 を custom_rule にします。プレイヤーが入力するそばから変数が更新されます。文字 の部品で 変数の値を挿入… を使えば、いまのルールを表示できます。そのあと下のステップ 1 と 2 を行います。ステップ 3 は、チャットの中に入力欄を置きたいときだけ必要です。
手順
ステップ 1:文字の変数を作る
この変数に、プレイヤーが入力した内容が入ります。キャンバスで 追加できるもの の + 変数 を押します。
| 項目 | 入れるもの | 理由 |
|---|---|---|
| 変数名 | custom_rule | 設定の中の {{custom_rule}} マクロは、この名前で探します |
| 型 | 文字 | 中身はプレイヤーが入力したもの |
| 初期値 | (空のまま、または All magic is allowed のような既定値) | 空 = 新しいプレイではルールなし。空でない = 最初からあるルール |
| AI アクセス(AI での扱い の中) | AI は読み取り専用 | 決めるのはプレイヤー。AI は読めるが変えられない |
ステップ 2:設定の中でマクロを使う
{{custom_rule}} を差し込み口として使う設定を作ります。キャンバスの キャラクターと世界 のブロック(毎ターン送られます)で、ブロック右上の + を押します。
| 項目 | 入れるもの | 理由 |
|---|---|---|
| 名前 | 世界のルール | 自分用の目印 |
| AI に渡すタイミング | キャラクターと世界(毎ターン) | ルールはすべての返信に効かせたい |
本文:
[世界のルール]
この世界では次のルールが有効であり、常に守らなければならない:
{{custom_rule}}エンジンはプロンプトを組み立てるたびに、設定の中から
{{...}}を探します。かっこの中の名前が変数と一致すれば、その変数の現在の値に置き換えます。変数が空なら、その行も空になります。AI には「次のルールが有効であり……」のあとに何もない文が見えます。値が「Magic is forbidden」なら、AI には「次のルールが有効であり……Magic is forbidden」と見えます。
ステップ 3:画面のコードで入力欄を置く
チャットの最後のメッセージの下に入力欄を置きます。renderBubble の中に書きます(最後のメッセージの下にだけ出すので、入力欄はメッセージごとではなく 1 つだけになります)。
index.tsx に次のものを足します。パート 1 のコードがすでにあるなら、renderBubble が返す JSX の中、メッセージ本文の下に足してください。
// Near the top of MyWorld() (outside <Chat>), add these
const api = useYumina(); // If you already have it, don't duplicate
const msgs = api.messages || [];
const [ruleInput, setRuleInput] = React.useState("");
const currentRule = String(api.variables.custom_rule || "");
// Inside renderBubble, add a check
const isLastMsg = msg.messageIndex === msgs.length - 1; // whether this is the last message
// In the JSX renderBubble returns, below the message text
{isLastMsg && (
<div style={{
marginTop: "12px",
padding: "12px",
background: "rgba(30,41,59,0.5)",
borderRadius: "8px",
border: "1px solid #334155",
}}>
<div style={{ fontSize: "12px", color: "#94a3b8", marginBottom: "6px" }}>
World Rule: {currentRule || "(not set)"}
</div>
<div style={{ display: "flex", gap: "8px" }}>
<input
type="text"
value={ruleInput}
onChange={(e) => setRuleInput(e.target.value)}
placeholder="Type a new rule..."
style={{
flex: 1, padding: "6px 10px", background: "#1e293b",
border: "1px solid #475569", borderRadius: "6px",
color: "#e2e8f0", fontSize: "13px", outline: "none",
}}
onKeyDown={(e) => {
if (e.key === "Enter" && ruleInput.trim()) {
api.setVariable("custom_rule", ruleInput.trim());
setRuleInput("");
}
}}
/>
<button
onClick={() => {
if (ruleInput.trim()) {
api.setVariable("custom_rule", ruleInput.trim());
setRuleInput("");
}
}}
style={{
padding: "6px 14px", background: "#4338ca", borderRadius: "6px",
color: "#e0e7ff", fontSize: "13px", fontWeight: "600",
cursor: "pointer", border: "none",
}}
>
Apply
</button>
</div>
</div>
)}1 行ずつの説明:
isLastMsg:入力欄を最後のメッセージにだけ出します。そうしないと、すべてのメッセージに付いてしまいますcurrentRule:変数の現在の値を読み、入力欄の上に表示します。プレイヤーはいまのルールを確認できますruleInput:入力中の文字を持っておく React の state ですonKeyDown:ボタンを押すだけでなく、Enter でも送れるようにしますapi.setVariable("custom_rule", ...):プレイヤーの入力を変数に書き込みます。次の AI の返信で、設定の中の{{custom_rule}}がこの文字に置き換わりますsetRuleInput(""):送ったあと入力欄を空にします
なぜ renderBubble の中に置くの?
index.tsx から <Chat /> を返すと、プラットフォームのチャットになります。チャットの中に操作できる要素(ボタン、入力欄)を入れる方法は 2 つあります。1) ここでのように <Chat renderBubble={...} /> の中に入れ、メッセージの吹き出しと一緒に描く。2) flex レイアウトで <Chat /> と自分のコンポーネントを横に並べる(サイドバー向け)。チャットのない全画面の UI(ビジュアルノベルなど)なら、<Chat /> は使わずに自分でレイアウトを書き、必要に応じて <MessageList /> と <MessageInput /> を使います。
ステップ 4:保存してテストする
- カードを保存し、プレイ でプレイテストを始める
- 最後のメッセージの下に「World Rule: (not set)」と入力欄が表示される
- 「Magic is forbidden」と入力して「Apply」を押す(または Enter)
- 入力欄の上の文字が「World Rule: Magic is forbidden」に変わる。変数が更新されたということです
- ここでメッセージを送る(たとえば「I try to cast a fireball」)。このときエンジンがプロンプトを組み立て、
{{custom_rule}}を「Magic is forbidden」に置き換えます - AI の返信にこのルールが反映されているはずです(たとえば「呪文を唱えようと手を上げるが、見えない力に魔力を封じられているように感じる」)
- ルールをもう一度変えて(たとえば「Only fire magic is allowed」)メッセージを送ると、AI はそれに合わせます
2 つのパターンを組み合わせる
オープニングの切り替えと設定の書き換えは組み合わせられます。具体例です。
キャラクター作成 + 物語のオープニング:
- 1 つ目のオープニング(index 0) は物語ではなく、名前、クラス、バックストーリーを入力するキャラクター作成の画面にする
- プレイヤーが入力する → 入力が変数に書き込まれる →
{{player_name}}、{{player_class}}、{{player_backstory}}のマクロを含む設定がその値を拾う - プレイヤーが「Start Adventure」を押す →
switchGreeting(1)で本当の物語のオープニングへジャンプする。切り替えると変数はそのオープニングの初期値に戻るので、答えは切り替えの 後 に保存する(switchGreetingを await してからsetVariableかpatchVariables) - 最初の AI の返信から、AI はプレイヤーキャラクターの名前、クラス、バックストーリーを知っている
プレイヤー画面のオープニング用テンプレート(名前を入力、キャラクターシート、出自を選ぶ、能力値を振る)を使えば、物語が始まる前のこうした画面をコードなしで作れます。
早見表
| やりたいこと | やり方 |
|---|---|
| あらかじめ書いたオープニングへジャンプする | プレイヤー画面:オープニングを切り替え ステップか オープニングを選ぶ テンプレート。コード:await api.switchGreeting(index)(0 始まり、オープニングの並び順) |
| オープニングごとに値を変える | キャンバスでオープニングを変数の上にドラッグし、初期値を設定する |
| プレイヤーの入力で AI のふるまいを変える | 文字の変数 + 設定の中の {{variable_name}} + 入力欄(またはコードから setVariable()) |
| ボタンを最初のメッセージにだけ出す | <Chat renderBubble> の中で msg.messageIndex === 0 を確認する |
| 選んだらボタンを隠す | 選択を変数に記録し、TSX で hasChosen を確認する |
| ルートを選んだら設定を切り替える | ルート変数を条件にした 条件ロア、またはオープニングに結びつけたシチュエーション |
| 切り替えのときに音を鳴らす、お知らせを出す | 音楽を再生 / 効果音を再生 / 通知を表示 を持つビヘイビアを、切り替えの後に動かす |
試してみよう:インポートできるデモワールド
この JSON ファイルをダウンロードしてインポートします。
インポートのしかた:
- 左の 作成 を押し、空のプロジェクト を選ぶ
- パネル → カードの設定 を開き、ファイルをインポート → ファイルを選択
- ダウンロードした
.jsonファイルを選ぶ。インポートすると、そのカードの中身はすべて置き換わります - 保存 を押してから、プレイ で試す
含まれているもの:
- オープニング 3 つ(メインのオープニング + 暗い洞窟 + 日の当たる草原)
- 変数 2 つ(ルートを記録する
current_route、プレイヤーが書き換えられるルールのcustom_rule) - プレイヤーがボタンを押した で動くビヘイビア 2 つ(
choose-dark/choose-light)。ルートを設定し、ルートごとの設定をオン・オフします - メッセージのレンダラー(各メッセージを描く画面のコード)。ルートボタンとルールの編集欄が入っています
{{custom_rule}}マクロを使った設定 1 つ
デモのボタンは、ワンクリックでルートを設定し、ビヘイビアを動かし、そのあとでオープニングを切り替えます。切り替えで変数がオープニングの初期値に戻るため、ルートが残らないことがあります。上のステップ 2〜4 のやり方なら、この問題は起きません。
