v
Skip to content

UI によるシーンジャンプと設定の切り替え ​

ボタンを押すと、あらかじめ書いておいた別のオープニングにジャンプする。テキストボックスに入力すると、設定が AI に伝える内容が変わる。このレシピでは両方を、まずコードなしで、次に画面のコードで作ります。


パート 1:ボタンでオープニングを切り替える ​

これから作るもの ​

あらかじめ書いたオープニングの場面がいくつもあるカードです。プレイヤーは最初に「メイン」のオープニングを見て、その下に押せるボタンがあります。ボタンを押すと、チャットの最初のメッセージが別のオープニングにすぐ切り替わります。AI は何も生成せず、あなたが書いた文章がそのまま出ます。

しくみ ​

カードにはオープニングを複数持たせられます。プレイヤーが新しくプレイを始めると、それらはすべて最初のメッセージの スワイプ としてまとめられます(左右で切り替え)。オープニングはそれぞれ、変数の初期値も持っています。

api.switchGreeting(index) は、その番号のオープニングへ直接ジャンプします。プレイヤー画面のボタンの オープニングを切り替え ステップは、同じことをコードなしで行います。

プレイヤーが「Enter the Dark Cave」を押す
  → ボタンがオープニング #2 に切り替える(index は 0 から数えるので、1 = 2 つ目)
  → 変数がそのオープニングの初期値に戻る
  → プレイヤーには、あなたが書いた暗い洞窟のオープニングが表示される

コードなしで作る ​

  1. オープニングを書く(下のステップ 1)
  2. プレイヤー画面で テンプレート を押し、オープニングを選ぶ を追加する。オープニングごとに一枚のカードが並び、プレイヤーがタップしたところから物語が始まります。または、ページに自分でボタンを置き、それぞれに オープニングを切り替え のステップを付けます
  3. ルートごとに別の設定を送りたいなら、下のステップ 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 を開き、中身を次のものに置き換えます。

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" になり、ボタンが消え、洞窟の設定が送られ始めます

押したときにもっと何かする。 ボタンでビヘイビアも動かしたい場合(音楽を流す、お知らせを出す、ほかの値を変える)は、先に切り替え、切り替えが終わってからビヘイビアを動かします。

tsx
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:保存してテストする ​

  1. 上部バーの 保存 を押す
  2. プレイ を押してプレイテストを始める
  3. メインのオープニングと、その下に 2 つのボタンが表示される
  4. 「Enter the Dark Cave」を押す。最初のメッセージが洞窟のオープニングに変わり、ボタンが消える
  5. AI にいくつかメッセージを送る。ステップ 3 を行っていれば、AI の返信は洞窟の設定に沿ったものになる
  6. もう一方のルートを試すには、新しいプレイテストを始めて、もう一方のボタンを押す

うまくいかないときは:

症状考えられる原因直し方
ボタンが見えない画面のコードが保存されていないか、構文エラーがあるパネル → フロントエンドコード の表示が エラー ではなく 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 の中、メッセージ本文の下に足してください。

tsx
// 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:保存してテストする ​

  1. カードを保存し、プレイ でプレイテストを始める
  2. 最後のメッセージの下に「World Rule: (not set)」と入力欄が表示される
  3. 「Magic is forbidden」と入力して「Apply」を押す(または Enter)
  4. 入力欄の上の文字が「World Rule: Magic is forbidden」に変わる。変数が更新されたということです
  5. ここでメッセージを送る(たとえば「I try to cast a fireball」)。このときエンジンがプロンプトを組み立て、{{custom_rule}} を「Magic is forbidden」に置き換えます
  6. AI の返信にこのルールが反映されているはずです(たとえば「呪文を唱えようと手を上げるが、見えない力に魔力を封じられているように感じる」)
  7. ルールをもう一度変えて(たとえば「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 ファイルをダウンロードしてインポートします。

recipe-1-demo.json

インポートのしかた:

  1. 左の 作成 を押し、空のプロジェクト を選ぶ
  2. パネル → カードの設定 を開き、ファイルをインポート → ファイルを選択
  3. ダウンロードした .json ファイルを選ぶ。インポートすると、そのカードの中身はすべて置き換わります
  4. 保存 を押してから、プレイ で試す

含まれているもの:

  • オープニング 3 つ(メインのオープニング + 暗い洞窟 + 日の当たる草原)
  • 変数 2 つ(ルートを記録する current_route、プレイヤーが書き換えられるルールの custom_rule)
  • プレイヤーがボタンを押した で動くビヘイビア 2 つ(choose-dark / choose-light)。ルートを設定し、ルートごとの設定をオン・オフします
  • メッセージのレンダラー(各メッセージを描く画面のコード)。ルートボタンとルールの編集欄が入っています
  • {{custom_rule}} マクロを使った設定 1 つ

デモのボタンは、ワンクリックでルートを設定し、ビヘイビアを動かし、そのあとでオープニングを切り替えます。切り替えで変数がオープニングの初期値に戻るため、ルートが残らないことがあります。上のステップ 2〜4 のやり方なら、この問題は起きません。