v
Skip to content

マップとシーン移動 ​

プレイヤーがタップして使える地図です。Forest を選ぶと、場面が森に移ります。AI には森の雰囲気が渡され、音楽は森の曲へフェードし、AI の次の返信で到着の様子が描かれます。変数ひとつ、条件ロア、音楽のルール、プレイヤー画面のボタンで作るので、コードは要りません。


これから作るもの ​

  • 地図のページ:Village、Forest、Cave、Market の 4 か所
  • いまいる場所がわかる:その場所のボタンはもう押せません
  • 場所をタップすると場面が移る:現在地が変わり、その場所のロアが AI に届き、プレイヤーの一言「(I head to the Forest)」がチャットに送られるので、AI が到着の様子を描きます
  • 場所ごとに音楽がある:場所を移ると、前の曲から次の曲へフェードで切り替わります

しくみ ​

プレイヤーが地図の「Forest」をタップする
  → ボタンが Location を Forest にし、チャットに戻り、
    プレイヤーの代わりに「(I head to the Forest)」と発言する
  → ロアエントリ「Forest」(条件:Location が Forest)が AI に届くようになり、「Village」は届かなくなる
  → Forest の音楽ルールが当てはまり、村の曲がフェードアウトして森の曲がフェードイン
  → AI は森の雰囲気を受け取ったうえで「(I head to the Forest)」に返信する

変わるのは変数だけです。ロアと音楽はそれを見て、ついてきます。


ステップバイステップ ​

ステップ1:変数を作る ​

カードの下にある 追加できるもの で + 変数 を押します。

項目値
名前Location
型文字
初期値Village
ビヘイビアルールプレイヤーのいる場所。Village、Forest、Cave、Market のどれか。プレイヤーがはっきりそのどこかへ移動したら変える。

物語の中で AI にもプレイヤーを移動させたいとき(「小川をたどって森に入る」など)は、AI アクセス を AI が読み書き可能 のままにします。移動は地図からだけにしたいなら、AI は読み取り専用 にします。


ステップ2:場所ごとにロアエントリを書く ​

場所ごとにロアエントリを 1 つずつ追加します。次に Location を各エントリの上にドラッグし、できた線をクリックして、条件を「Location = Village」のように設定します(ほかの場所も同様)。エントリは条件ロアのブロックに移り、AI に届くのはいまいる場所のエントリだけになります。

Village ​

[Location: Village]
プレイヤーは村にいる。場面を描写するときは、次の雰囲気を取り入れること:
- 静かな小さな村。木造の家々のあいだを石畳の小道が曲がりくねっている
- 煙突から煙が上がり、焼きたてのパンとシチューの匂いがする
- 井戸のそばで村人が話し込み、鍛冶屋から槌の音が響く
- 麦畑がずっと先まで広がり、風に揺れている
- あたたかく、いつもどおりの雰囲気

Forest ​

[Location: Forest]
プレイヤーは森にいる。場面を描写するときは、次の雰囲気を取り入れること:
- 古い木々が空のほとんどをふさぎ、光はまだらになって苔の上に落ちる
- 空気は湿っていて、土と樹脂と野の花の匂いがする
- 四方から鳥の声がして、ときおり枝の折れる音がする
- 下草の陰には、ウサギか、シカか、もっと悪いものが潜んでいるかもしれない
- 奥へ進むほど木々は密になり、光は暗くなる
- 古く、荒々しく、先の読めない雰囲気

Cave ​

[Location: Cave]
プレイヤーは洞窟にいる。場面を描写するときは、次の雰囲気を取り入れること:
- 岩壁に生えた光るキノコが、かすかな青緑の光を放っている
- 鍾乳石から水が滴り、一滴ごとに音が反響する
- 空気は冷たく湿っていて、鉱物の匂いがする
- 足もとは滑りやすくでこぼこで、奥の通路は真っ暗
- ときどき、低いうなり声や岩がずれる音が下のほうから聞こえてくる
- 暗く、息苦しい雰囲気

Market ​

[Location: Market]
プレイヤーは市場にいる。場面を描写するときは、次の雰囲気を取り入れること:
- 色とりどりの露店やテントが並び、ありとあらゆる品物が積まれている
- 商人が売り声を上げ、あちこちで値切りの声が飛び交う
- 香辛料、焼いた肉、革、花の匂いがいっぺんに漂う
- 魔法の店の窓には妙な光がちらつき、隅では錬金術師が何かを混ぜている
- いろいろな旅人が人ごみをかき分けていく
- 騒がしく、活気のある雰囲気

ステップ3(任意):場所ごとの音楽 ​

オーディオ ページに BGM トラックを 4 つ追加します(カードにまだオーディオがなければ、追加できるもの で + オーディオ を押します)。Village、Forest、Cave、Market の 4 つで、どれも ループ再生 をオン、フェードイン(秒) と フェードアウト(秒) は 2 秒にします。Village は デフォルトプレイリスト に入れ、自動再生 をオンにします。

次に BGM設定 → 条件付きBGM → 条件付きルールを追加 を、Forest、Cave、Market について 1 回ずつ行います。

項目値
トリガー変数条件:Location が Forest
再生Forest
フェードイン(秒) / フェードアウト(秒)3 / 3
前のBGMを停止オン
終了時デフォルトプレイリストに戻る

プレイヤーが Village に戻ると、どのルールも当てはまらなくなり、終了時 の設定でプレイリストの村の曲が戻ってきます。

フェードを 3 秒にすると、古い曲と新しい曲が少しのあいだ重なり、片方が小さくなりながらもう片方が大きくなります。場所を移るあいだも音楽が途切れません。詳しくはオーディオデザインガイドを見てください。


ステップ4:地図のページを作る ​

上部バーの真ん中にある プレイヤー画面 を押します。ページを追加 でページを追加し(白紙のページ)、名前を「Map」にして、タイトルと「どこへ行く?」のような短い一行を入れます。

場所ごとに ボタン を 1 つずつ追加します(部品を追加 → ボタン)。Forest のボタンなら、押したとき、順番に に次の手順を並べます。

  1. 変数を変える:Location、にする、Forest
  2. ページへ移動:会話のページ
  3. プレイヤーの代わりに発言:(I head to the Forest)

最後の手順のおかげで、AI がすぐに到着の様子を描写します。プレイヤーが自分で打ち込んだのと同じ扱いになるからです。

いまいる場所へ移動できないように、各ボタンに 表示するタイミング → 変数が条件を満たすとき を設定します。Location と等しくない Forest です。ページの上には 文字 の部品を置き、You are in: {Location} と表示させます(変数の値を挿入… を使います)。

移動せずに地図を閉じられるよう、会話に戻るだけのボタン(ページへ移動 → 会話)もページに置いておきます。そして会話のページには、ページへ移動 → Map の「Map」ボタンを追加します。

編集 をオフにすれば、マッシーを使わずにクリックして動きを試せます。

マップ移動テンプレート

テンプレート → マップ移動(「場所をタップで移動。まだ開いていない場所は押せません。」)を選ぶと、例の場所が 5 か所入った地図のページがすぐにできます。Location と 行ける場所 が追加されます。行ける場所はリストで、物語の中でプレイヤーがある場所へ行けるようになると AI がそこに追加します。なので、場所は最初はロックされていて、物語が進むにつれて開いていきます。最初から全部開いているより、物語で場所を解放していきたいときに使ってください。

場所をシチュエーションにする

ある場所がゲームの中でひとつのまとまり(専用の NPC、ルール、変数、場合によっては専用の AI)にまで育ったら、ロアエントリ 1 つではなくシチュエーションにします。シチュエーションの いつ使うか を 変数が条件を満たしたとき、Location = Cave にすれば、中にあるものはプレイヤーがそこにいるあいだだけ使われます。この設定は、変数 Location をシチュエーションの上にドラッグしてもできます。


ステップ5:プレイテスト ​

上部バーの プレイ を押します。

  1. 村の音楽が流れます。Map を開くと「You are in: Village」と表示され、Village のボタンはありません
  2. Forest をタップします。チャットに戻り、「(I head to the Forest)」が送られ、AI が森を描写します
  3. 音楽を追加していれば、村の曲が森の曲へフェードします
  4. Cave に行ってから Village に戻ると、プレイリストの村の音楽が戻ってきます
  5. 移動の合間は普通にチャットできます。地図はいつでもワンタップで開けます

プレイテストの右側の このターン で、AI がどの場所のエントリを受け取ったかを確認できます。

うまくいかないときは:

症状よくある原因直し方
場所をタップしても何も起きないボタンに手順がない押したとき、順番に を確認します
現在地は変わるのに AI が返信しないプレイヤーの代わりに発言 の手順がない最後の手順として追加します
AI が違う場所を描写するエントリの条件が、ボタンが設定する値と一致していないボタンと条件の Forest を同じつづりにします
2 か所の雰囲気が混ざる常に送るブロックに入っているエントリがある各エントリの AI に渡すタイミング を確認します
2 曲が同時に鳴る前のBGMを停止 がオフ場所ごとのルールでオンにします
音楽がフェードせずにいきなり切り替わるフェードが 0ルールの フェードイン(秒) と フェードアウト(秒) を 2 か 3 にします

カスタムコードの方法:移動ルート付きの地図パネル ​

インターフェースを自分で書くなら、最後のメッセージの下に地図パネルを出し、いまいる場所から行ける先を制限することもできます。同じ Location 変数を設定し、同じ一言を送るので、ステップ2と3のロアと音楽はそのまま動きます。

パネル → フロントエンドコード → index.tsx を開き、デフォルトの return <Chat /> を置き換えます。

tsx
export default function MyWorld() {
  const api = useYumina();
  const current = String(api.variables["Location"] || "Village");
  const msgs = api.messages || [];

  const locations = [
    { id: "Village", icon: "🏘️", color: "#92400e", bg: "#fef3c7", border: "#f59e0b", activeBg: "#f59e0b" },
    { id: "Forest",  icon: "🌲", color: "#166534", bg: "#dcfce7", border: "#22c55e", activeBg: "#22c55e" },
    { id: "Cave",    icon: "🕳️", color: "#3b0764", bg: "#f3e8ff", border: "#a855f7", activeBg: "#a855f7" },
    { id: "Market",  icon: "🏪", color: "#9a3412", bg: "#ffedd5", border: "#f97316", activeBg: "#f97316" },
  ];

  // Where you can go from each place
  const routes = {
    Village: ["Forest", "Market"],
    Forest:  ["Village", "Cave"],
    Cave:    ["Forest"],
    Market:  ["Village"],
  };
  const reachable = routes[current] || [];

  const travel = (loc) => {
    api.setVariable("Location", loc.id);
    api.sendMessage("(I head to the " + loc.id + ")");
  };

  return (
    <Chat renderBubble={(msg) => {
      const isLastMsg = msg.messageIndex === msgs.length - 1;
      return (
        <div>
          <div
            style={{ color: "#e2e8f0", lineHeight: 1.7 }}
            dangerouslySetInnerHTML={{ __html: msg.contentHtml }}
          />

          {isLastMsg && !msg.isStreaming && (
            <div style={{
              marginTop: "16px",
              padding: "16px",
              background: "rgba(15,23,42,0.6)",
              borderRadius: "12px",
              border: "1px solid #334155",
            }}>
              <div style={{ fontSize: "13px", color: "#94a3b8", marginBottom: "12px", fontWeight: "600", letterSpacing: "0.05em" }}>
                WORLD MAP
              </div>
              <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "10px" }}>
                {locations.map((loc) => {
                  const isHere = current === loc.id;
                  const canGo = reachable.includes(loc.id);
                  return (
                    <button
                      key={loc.id}
                      onClick={() => { if (!isHere && canGo) travel(loc); }}
                      style={{
                        padding: "14px 10px",
                        background: isHere ? loc.activeBg : loc.bg,
                        border: `2px solid ${isHere ? loc.activeBg : loc.border}`,
                        borderRadius: "10px",
                        color: isHere ? "#ffffff" : loc.color,
                        fontSize: "14px",
                        fontWeight: "700",
                        cursor: isHere ? "default" : canGo ? "pointer" : "not-allowed",
                        opacity: isHere ? 1 : canGo ? 0.85 : 0.3,
                        display: "flex",
                        flexDirection: "column",
                        alignItems: "center",
                        gap: "6px",
                      }}
                    >
                      <span style={{ fontSize: "28px" }}>{loc.icon}</span>
                      <span>{loc.id}</span>
                      {isHere && <span style={{ fontSize: "11px", fontWeight: "500" }}>You are here</span>}
                    </button>
                  );
                })}
              </div>
            </div>
          )}
        </div>
      );
    }} />
  );
}

コードの中身:

  • api.variables["Location"] で、変数を名前で読みます
  • routes には、それぞれの場所から行ける先を並べています。いまの場所から行けない場所は薄く表示され、タップできません
  • travel() で Location を設定し、続けて api.sendMessage(...) でプレイヤーとして移動の一言を送ります。AI が返信するときには、新しい場所のロアがもうプロンプトに入っています
  • !msg.isStreaming で、AI が書いている途中は地図を隠し、返信の途中で移動できないようにしています
  • gridTemplateColumns: "1fr 1fr" で 4 か所を 2 × 2 に並べています。3 列にしたいときは "1fr 1fr 1fr" にします

パネル → フロントエンドコード のファイル横の状態が、緑の OK になっていれば大丈夫です。

コードを自分で書きたくないときは

キャンバス右上の AIアシスタント を押して、ほしい地図を説明してください。


場所を増やす ​

  1. ロアエントリ「Harbor」を追加し、条件を Location が Harbor にします
  2. 必要なら Harbor のトラックと、それ用の 条件付きBGM ルールを追加します
  3. 地図のページに Harbor のボタンを追加します(コードなら locations の配列と routes に一行足します)
  4. AI がその場所を知っているように、Location のビヘイビアルールに Harbor を書き足します

すでにあるものは何も変えなくて大丈夫です。


クイックリファレンス ​

やりたいことやり方
プレイヤーの居場所を記録する文字の変数 Location
いまの場所の雰囲気を AI に渡す場所ごとにロアエントリを作り、Location の条件を付ける
ボタンでプレイヤーを移動させる手順:変数を変える、ページへ移動、プレイヤーの代わりに発言
AI にすぐ到着の様子を書かせるプレイヤーの代わりに発言(コードなら api.sendMessage())
いまいる場所のボタンを隠す表示するタイミング → 変数が条件を満たすとき、Location と等しくない …
場所ごとの音楽場所ごとに 条件付きBGM ルール、前のBGMを停止 をオン
物語の途中で開いていく場所マップ移動 テンプレートと 行ける場所
専用の中身を持つ大きな場所変数が条件を満たしたとき に使うシチュエーション
場所と場所の行き来を制限するインターフェースのコード(上のカスタムコードの方法)

これはレシピ #12 です

同じ組み立て方(変数ひとつと、それについてくるロアと音楽)は、塔の階層、家の部屋、ポータルの先の世界にも使えます。昼夜サイクルは場所の代わりに時間で同じことをしていて、2 つは組み合わせられます。夜の森には、両方の条件を付けた専用のエントリを用意できます。