v
Skip to content

持ち物と装備 ​

プレイヤーが開いて使える持ち物を作ります。物語の中で手に入れたものはかばんに入り、ポーションは飲めば体力が増えて数が減り、武器を装備すれば AI もプレイヤーが何を持っているかわかります。かばん、ポーション、武器はどれもコードなしで作れます。アイテムごとにボタンが付いた本格的なアイテム一覧は、最後のカスタムコードの方法で作ります。


完成するもの ​

  • かばん:チャットから開けて、拾ったものがひとことの説明付きで並びます。物語で新しく何かを手に入れると、ポップアップも出ます
  • ポーションのボタン:体力 +20、ポーションが 1 つ減る。もう残っていなければ「ポーションがもうない!」と出ます
  • 装備のボタン:武器の欄が「Iron Sword」になり、AI はプレイヤーがそれを手にしている前提で書きます
  • かばんの中身は AI が更新:戦利品、報酬、なくしたものや壊れたものを、物語に合わせて反映します

役割分担 ​

何を誰が担当するか理由
物語で手に入れたアイテムの一覧AI(かばんの変数のビヘイビアルールに従う)物語の中でいつ何かを渡されたかは AI にしかわからない
ポーションの数と回復ビヘイビア計算は正確であるべき
装備中の武器ボタンまたはビヘイビア値は 1 つだけで、プレイヤーが選んだときに設定する
これらすべての表示プレイヤー画面テンプレートと部品で作れて、コードは不要

作り方 ​

ステップ 1:持ち物テンプレートを追加する ​

上部バー中央の プレイヤー画面 を押し、テンプレート から 持ち物 を追加します(プレイ中 · チャット画面の「メニュー」から開く の中にあります)。説明は「アイテムをタップで使う。新しく手に入るとポップアップ。」です。

追加されるのは、かばんのページ、チャット画面のポップアップ、そして変数 2 つです。

変数中身
持ち物(リスト/表)アイテム 1 つにつき 1 行。それぞれ名前とひとことの説明を持つ
手に入れたもの(文字)プレイヤーがたったいま手に入れたもの。ポップアップがこれを表示し、閉じると空に戻す

どちらにも、AI に使い方を教えるビヘイビアルールが最初から付いています。持ち物のほうは、たとえばこう書かれています。

持ち物のリスト。各項目は {"name": アイテム名, "note": 一行の説明}。手に入れたら push し、同じターンに new_item を「アイテム名——一言」にする。使い切ったり失くしたらその番号(0 から)を delete。毎ターン配らない。

AI は物語の中でこれに従うので、戦利品も贈り物も報酬も、あなたが何もしなくてもかばんに入ります。

かばんのページでアイテムをタップすると、プレイヤーの発言として「(古い懐中時計を取り出す)」のような一文が送られ、チャットに戻ります。それを使う場面は AI が描きます。

最初から持たせておくアイテムは、持ち物の変数の 初期値 に入れます。たとえば次のとおりです。

json
[{"name": "Iron Sword", "note": "Plain, but well balanced."}]

ステップ 2:数えられるポーション ​

かばんは AI が書くリストです。ポーションの残りのように正確な数が必要なものは、数値の変数とビヘイビアで扱います。

変数(追加できるもの から + 変数):

名前型初期値範囲メモ
体力数値800 〜 100ビヘイビアルール:プレイヤーが傷を負うと減る。休むと少しずつ回復する。
ポーション数値20 〜(空欄)ビヘイビアルール:プレイヤーが持っている回復ポーションの数。見つけたり買ったりしたら増やす。

体力を最大値より低いところから始めると、プレイヤーがポーションを試してみる理由になります。

ビヘイビア(+ ビヘイビア):「ポーションを飲む」

部分設定
発動するときプレイヤーがボタンを押した、ボタン drink-potion
次の場合のみポーション より大きい 0
効果変数を変更:ポーション 減算 1
変数を変更:体力 加算 20
通知を表示:ポーションを飲んだ。体力 +20、スタイル 成功
AIに伝える:プレイヤーはたったいま回復ポーションを飲んだ。
条件が合わないときに伝えるポーションがもうない!

体力は 100 を超えません。変数の範囲が上限で止めてくれます。

ボタン:プレイヤー画面 の会話のページかかばんのページに、「ポーションを飲む(残り {ポーション})」と書いた ボタン を置きます(数は 変数の値を挿入… で入れます)。押したとき、順番に に ビヘイビアを動かす → ポーションを飲む を追加します。

ステップ 3:武器を装備する ​

変数:装備中の武器。型は文字で、初期値は空です。ビヘイビアルールは次のとおり。

プレイヤーが手にしている武器。空なら素手。戦いの場面ではこの武器で描写する。物語の中で壊れたり奪われたりしたら、空に戻す。

ボタン:「Iron Sword を装備」。手順は 2 つです。

  1. 変数を変える:装備中の武器、にする、Iron Sword
  2. 通知を表示:Iron Sword を装備した

表示するタイミング を 変数が条件を満たすとき:装備中の武器 と等しくない Iron Sword にしておくと、装備したあとはボタンが消えます。隣に 武器:{装備中の武器} と書いた 文字 の部品を置けば、装備の欄になります。

AI は毎ターン、プロンプトの中で装備中の武器を見ているので、次の返信からプレイヤーは剣を持っています。すぐに反応させたいときは、ボタンから ビヘイビアを動かす で、変数を変えて AIに伝える プレイヤーはたったいま Iron Sword を装備した。 も行うビヘイビアを呼びます。

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

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

  1. 体力 80、ポーション 2、武器の欄は空
  2. ポーションを飲む をタップ:体力 100、ポーション 1、通知が出る
  3. さらに 2 回タップ:2 回目で「ポーションがもうない!」と出て、何も変わらない
  4. Iron Sword を装備 をタップ:欄が Iron Sword になり、ボタンが消える。戦いを始めると、AI がその剣を使って描写する
  5. 物語で何かを手に入れるまで遊ぶ:ポップアップに表示され、かばんにも入っている(メニュー → 持ち物)

うまくいかないときは:

症状よくある原因直し方
ポーションのボタンを押しても何も起きないボタンが呼ぶビヘイビアと、ビヘイビアのボタン名が合っていないビヘイビアを動かす がポーションを飲むを指しているか確認する
ポーションがマイナスになる次の場合のみ の条件がないポーション より大きい 0 を追加する
体力が 100 を超える体力に最大値がない範囲 を 0 〜 100 にする
かばんにアイテムが入らないAI が持ち物に書き込んでいない持ち物の変数にビヘイビアルールが残っているか、AI アクセス が AI が読み書き可能 になっているか確認する
ポップアップが出ないAI が持ち物は更新しても、手に入れたものを更新していない手に入れたもののビヘイビアルールを消さずに残す。同じターンに両方を書くよう指示しています

ここでビヘイビアだけでは足りない理由 ​

ビヘイビアの効果で変えられるのは数値と文字です(設定、加算、減算、乗算、切り替え、文字の追加)。リストに行を足したり、リストの中から「ポーション」を探したり、それを取り除いたりはできません。条件ではリストがある値を 含む かどうかは調べられますが、行の中身までは見られません。そのためこのレシピでは、アイテムの一覧は AI に任せ、正確な数が必要なものはそれぞれ専用の数値の変数で数えています。

JavaScript を 1 行書くなら、ビヘイビアの コード の効果でリストに追加することもできます。たとえば ctx.push("Inventory", { name: "Potion", note: "Heals a little." })。ビヘイビアの「コードのビヘイビア」を参照してください。

両方を 1 か所にまとめたい(アイテムごとに数を持ち、それぞれに「使う」や「装備」のボタンが付いた本格的な一覧)なら、インターフェースのコードで書きます。


カスタムコードの方法:使用と装備ができるアイテム一覧 ​

この方法では、持ち物全体を inventory という名前のリスト/表の変数 1 つで持ち、アイテムごとに個数を付け、使用と装備はインターフェースのコードで処理します。ほかに数値の変数 hp(0 〜 100、初期値 80)と文字の変数 equipped_weapon も使います。

inventory の初期値:

json
[{"name":"Potion","icon":"🧪","count":2},{"name":"Iron Sword","icon":"⚔️","count":1}]

ビヘイビアルール:

プレイヤーのアイテム。各アイテムは {"name", "icon", "count"}。プレイヤーが何かを見つけたら、新しいアイテムを push する。個数は持ち物のボタンに任せる。

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

tsx
export default function MyWorld() {
  const api = useYumina();
  const msgs = api.messages || [];

  const hp = Number(api.variables.hp ?? 80);
  const equippedWeapon = String(api.variables.equipped_weapon || "");
  const inventory = Array.isArray(api.variables.inventory) ? api.variables.inventory : [];

  // Use one of an item: count down, remove at zero, apply its effect
  function useItem(itemName) {
    const inv = Array.isArray(api.variables.inventory) ? api.variables.inventory : [];
    const idx = inv.findIndex((it) => it && it.name === itemName);
    if (idx === -1) {
      api.showToast("No " + itemName + " left!", "error");
      return;
    }
    const item = inv[idx];
    const next = inv.slice();
    if (Number(item.count) <= 1) next.splice(idx, 1);
    else next[idx] = { ...item, count: Number(item.count) - 1 };
    api.setVariable("inventory", next);

    if (itemName === "Potion") {
      api.setVariable("hp", Math.min(Number(api.variables.hp ?? 0) + 20, 100));
      api.showToast("Used a potion! HP +20", "success");
      api.injectContext("The player just drank a healing potion.");
    }
  }

  function equipItem(itemName) {
    if (equippedWeapon === itemName) {
      api.showToast(itemName + " is already equipped!", "info");
      return;
    }
    api.setVariable("equipped_weapon", itemName);
    api.showToast("Equipped " + itemName + "!", "success");
    api.injectContext("The player just equipped the " + itemName + ".");
  }

  // Which button each item gets
  const itemActions = {
    "Potion": { type: "consumable", label: "Use", run: () => useItem("Potion") },
    "Iron Sword": { type: "equipment", label: "Equip", run: () => equipItem("Iron Sword") },
  };

  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 && (
            <div style={{
              marginTop: "16px",
              padding: "16px",
              background: "rgba(15, 23, 42, 0.6)",
              borderRadius: "12px",
              border: "1px solid #334155",
            }}>
              {/* HP bar */}
              <div style={{ display: "flex", alignItems: "center", gap: "10px", marginBottom: "14px" }}>
                <span style={{ fontSize: "16px" }}>❤️</span>
                <div style={{ flex: 1 }}>
                  <div style={{ display: "flex", justifyContent: "space-between", marginBottom: "4px" }}>
                    <span style={{ color: "#94a3b8", fontSize: "12px" }}>HP</span>
                    <span style={{ color: "#e2e8f0", fontSize: "12px", fontWeight: "bold" }}>{hp} / 100</span>
                  </div>
                  <div style={{ height: "8px", background: "#1e293b", borderRadius: "4px", overflow: "hidden" }}>
                    <div style={{
                      height: "100%",
                      width: Math.min(hp, 100) + "%",
                      background: hp > 50
                        ? "linear-gradient(90deg, #22c55e, #4ade80)"
                        : hp > 20
                          ? "linear-gradient(90deg, #eab308, #facc15)"
                          : "linear-gradient(90deg, #ef4444, #f87171)",
                      borderRadius: "4px",
                      transition: "width 0.3s ease",
                    }} />
                  </div>
                </div>
              </div>

              {/* Weapon slot */}
              <div style={{
                display: "flex", alignItems: "center", gap: "8px", marginBottom: "14px",
                padding: "10px 14px", background: "rgba(30, 41, 59, 0.8)",
                borderRadius: "8px", border: "1px solid #475569",
              }}>
                <span style={{ fontSize: "16px" }}>⚔️</span>
                <span style={{ color: "#94a3b8", fontSize: "13px" }}>Weapon:</span>
                <span style={{
                  color: equippedWeapon ? "#e2e8f0" : "#475569",
                  fontSize: "13px",
                  fontWeight: equippedWeapon ? "600" : "normal",
                  fontStyle: equippedWeapon ? "normal" : "italic",
                }}>
                  {equippedWeapon || "None"}
                </span>
              </div>

              {/* Item grid */}
              {inventory.length === 0 ? (
                <div style={{
                  padding: "24px", textAlign: "center", color: "#475569", fontSize: "13px",
                  background: "rgba(30, 41, 59, 0.4)", borderRadius: "8px", border: "1px dashed #334155",
                }}>
                  Inventory is empty
                </div>
              ) : (
                <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(140px, 1fr))", gap: "8px" }}>
                  {inventory.map((item, idx) => {
                    const name = String(item?.name || item);
                    const action = itemActions[name];
                    const isEquipped = equippedWeapon === name;
                    return (
                      <div key={idx} style={{
                        display: "flex", flexDirection: "column", alignItems: "center", gap: "6px",
                        padding: "12px 8px 8px", background: "rgba(30, 41, 59, 0.8)", borderRadius: "8px",
                        border: isEquipped ? "1px solid #22d3ee" : "1px solid #475569",
                      }}>
                        <span style={{ fontSize: "28px" }}>{String(item?.icon || "📦")}</span>
                        <span style={{ color: "#e2e8f0", fontSize: "12px", fontWeight: "600", textAlign: "center" }}>{name}</span>
                        <span style={{ color: "#64748b", fontSize: "11px" }}>x{Number(item?.count ?? 1)}</span>
                        {action && (
                          <button
                            onClick={action.run}
                            style={{
                              marginTop: "4px", padding: "4px 14px", width: "100%",
                              borderRadius: "6px", fontSize: "12px", fontWeight: "600", cursor: "pointer",
                              background: action.type === "consumable" ? "#047857" : isEquipped ? "#4b5563" : "#1e40af",
                              border: "1px solid " + (action.type === "consumable" ? "#10b981" : isEquipped ? "#6b7280" : "#3b82f6"),
                              color: action.type === "consumable" ? "#a7f3d0" : isEquipped ? "#9ca3af" : "#bfdbfe",
                            }}
                          >
                            {isEquipped ? "Equipped" : action.label}
                          </button>
                        )}
                      </div>
                    );
                  })}
                </div>
              )}
            </div>
          )}
        </div>
      );
    }} />
  );
}

コードがしていること:

  • <Chat renderBubble={...} /> は、プラットフォームのメッセージ一覧、入力欄、スクロールをそのまま使います。描くのは各吹き出しだけで、パネルは最後の吹き出しの下に出ます
  • api.variables.inventory、.hp、.equipped_weapon は名前で変数を読みます
  • useItem は名前でアイテムを探し、数を 1 減らし(0 になったら取り除き)、api.setVariable("inventory", next) でリスト全体を書き戻します。ポーションなら hp も増やします
  • equipItem は equipped_weapon を設定し、プレイヤーに知らせます
  • api.injectContext(text) は、ビヘイビアの AIに伝える と同じように、次の返信に向けて AI にひとこと渡します
  • api.showToast(text, type) は通知を出します。type は "success"、"error"、"info" のどれかです
  • itemActions で、どのアイテムにボタンを付けるかを決めます。使えるアイテムを増やしたら、ここに 1 行足します

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

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

キャンバス右上の AIアシスタント を押して、「HP バー、武器の欄、使う/装備ボタンの付いたアイテム一覧」のように伝えます。

AI がリストを変える方法 ​

AI はリストの変更を、返信の最後に指示として書きます。書き方はエンジンが教えています。追加はうまくいきます。

ゴブリンの持ち物を探ると、小さな瓶が出てきた。 [inventory: push {"name":"Potion","icon":"🧪","count":1}]

位置を指定して取り除くこともできます([inventory: delete 0] で最初のアイテムが消えます)。ただ、「ポーション」を名前で探したり、1 つのアイテムの個数を確実に変えたりはできません。数はボタンで管理し、ビヘイビアルールでは AI にアイテムの追加だけをするよう伝えておきましょう。


早見表 ​

やりたいことやり方
物語の中で AI が中身を増やすかばんプレイヤー画面 → 持ち物 テンプレート
最初から持っているアイテム持ち物の変数の 初期値
正確な数(ポーション、矢)アイテムごとに数値の変数
確認付きでアイテムを使うビヘイビア:プレイヤーがボタンを押した、次の場合のみ 個数が 0 より大きい、条件が合わないときに伝える …
それを動かすボタンボタンの手順 ビヘイビアを動かす
何かを装備するボタンの手順 変数を変える で文字の変数を設定
済んだらボタンを隠す表示するタイミング → 変数が条件を満たすとき
すぐ AI に知らせるビヘイビアの AIに伝える、またはコードの api.injectContext()
アイテムごとのボタンと個数が付いた一覧インターフェースのコード(上のカスタムコードの方法)

これはレシピ #7 です

持ち物はショップ(金貨を払ってアイテムを得る)やクエストトラッカー(報酬としてのアイテム)と組み合わせられます。同じ分担はほかのリストにも使えます。物語の中のリストは AI に任せ、正確な数が必要なものには専用の変数を用意します。