クエストトラッカー
クエストトラッカーパネルを構築しましょう — 各クエストの完了状態を表示(チェックマークまたはX)、ゴールド報酬をリアルタイムで表示します。プレイヤーがクエストを完了すると、アチーブメント通知を自動的にポップアップし報酬を渡します。このレシピでは、変数、ビヘイビア、Root Componentで配線する方法を教えます。
これから作るもの
チャットインターフェースに組み込まれたクエストトラッカーパネル:
- クエストリスト — 各クエストが名前と完了状態を表示(完了 = 緑のチェックマーク、未完了 = 赤のX)
- ゴールドカウンタ — プレイヤーの現在のゴールドをリアルタイムで表示
- 自動検出 — プレイヤーのメッセージにキーワード(例:「herb」または「defeat」)が含まれると、クエストが自動的に完了マーク
- アチーブメント通知 — クエスト完了時に金色のトーストがポップアップし、プレイヤーにいくら獲得したか伝える
- ゴールド報酬 — 各クエストが完了時に自動的にゴールドを支払う
プレイヤーが「found the herbs」を含むメッセージを送信
→ エンジンがプレイヤーキーワード「herb」を検出
→ 条件をチェック:quest_1_complete == false?
→ はい:quest_1_complete = true に設定、30ゴールド追加、アチーブメント通知をポップアップ
→ いいえ:何もしない(クエストはすでに完了)
→ クエストパネルが自動更新:「Find Herbs」が ✗ から ✓ に変わる仕組み
このクエストシステムは3つのコアメカニズムを使用します:
- boolean変数 + キーワードトリガー — 各クエストはboolean変数で追跡されます。プレイヤーのメッセージに特定のキーワードが含まれると、ビヘイビアルールが自動的に変数を
trueに設定 - 条件チェック — ビヘイビアは発火前にクエストがすでに完了しているかチェック。完了したクエストは再度トリガーしない(二重報酬なし)
- Root Componentが変数を読む — パネルはクエスト状態とゴールドをリアルタイムで変数から読み、チェックマークまたはXを動的にレンダリング
ステップバイステップ
ステップ1:変数の作成
5つの変数が必要です — クエスト完了状態用の2つ、ゴールド用の1つ、クエスト名用の2つ(Root Componentが動的に表示できるように)。
エディタ → サイドバー → Variables タブ → 各々「Add Variable」をクリック
変数1:Quest 1 completion status
| フィールド | 値 | 理由 |
|---|---|---|
| Name | Quest 1 Complete | 変数リスト内で自分用の識別ラベル |
| ID | quest_1_complete | ビヘイビアとRoot ComponentがこのIDで値を読み書き |
| Type | Boolean | 2状態のみ:「完了」と「未完了」 |
| Default Value | false | 新規セッション開始時はクエスト未完了 |
| Category | Flag | これはステータスフラグ、数値ステータスではない |
| Behavior Rules | Set to true when the player completes the Find Herbs quest. Behaviors auto-detect this via keywords, but you may also mark it complete at an appropriate story moment. | この変数の意味といつ変わるべきかをAIに伝える |
変数2:Quest 2 completion status
| フィールド | 値 | 理由 |
|---|---|---|
| Name | Quest 2 Complete | 識別しやすい名前 |
| ID | quest_2_complete | ビヘイビアとRoot Componentで使用 |
| Type | Boolean | 同じ2状態セットアップ |
| Default Value | false | セッション開始時は未完了 |
| Category | Flag | ステータスフラグ |
| Behavior Rules | Set to true when the player defeats the Forest Wolf. Behaviors auto-detect this via keywords, but you may also mark it complete at an appropriate story moment. | この変数の意味といつ変わるべきかをAIに伝える |
変数3:Gold
| フィールド | 値 | 理由 |
|---|---|---|
| Name | Gold | 識別しやすい名前 |
| ID | gold | クエスト完了時に自動的に増加 |
| Type | Number | ゴールドは数値 — 加減算が必要 |
| Default Value | 0 | セッション開始時はゴールドなし — クエスト完了で獲得 |
| Min Value | 0 | ゴールドがマイナスになることを防ぐ |
| Category | Resource | ゴールドはリソース変数 |
| Behavior Rules | Gold is automatically awarded on quest completion. You may also add or subtract gold in the story — e.g., combat loot, trading, or theft. | ゴールドが複数のコンテキストで変化できることをAIに伝える |
変数4:Quest 1 name
| フィールド | 値 | 理由 |
|---|---|---|
| Name | Quest 1 Name | 識別しやすい名前 |
| ID | quest_1_name | Root ComponentがこのIDでクエスト名を表示 |
| Type | String | クエスト名はテキスト |
| Default Value | Find Herbs | 最初のクエストの名前 |
| Category | Custom | 単なる記述データ |
| Behavior Rules | Do not modify this variable. | クエスト名は変更すべきでない |
変数5:Quest 2 name
| フィールド | 値 | 理由 |
|---|---|---|
| Name | Quest 2 Name | 識別しやすい名前 |
| ID | quest_2_name | Root Componentで使用 |
| Type | String | クエスト名はテキスト |
| Default Value | Defeat the Forest Wolf | 2番目のクエストの名前 |
| Category | Custom | 記述データ |
| Behavior Rules | Do not modify this variable. | クエスト名は変更すべきでない |
なぜすべての変数にビヘイビアルールを書くのか?
AIが返信を生成するとき変数の変更を「提案」できるからです。変数を放っておくよう伝えないと、AIが自分でクエストを完了マークするかもしれません(例:AIが「プレイヤーがハーブを見つけた」と判断しquest_1_completeをtrueに設定 — しかしビヘイビアロジックをバイパスしたため、ゴールド報酬は支払われない)。ビヘイビアルールフィールドはAIへの指示 — 一度書けば、AIはこれらの変数がシステム制御だと知ります。
ステップ2:ビヘイビアの作成
これがクエストシステムの心臓部です。キーワードを検出し対応するクエストを完了マークしながら報酬を渡す2つのビヘイビアが必要です。
エディタ → Behaviors タブ → 各々「Add Behavior」をクリック
ビヘイビア1:「Find Herbs」クエスト完了
WHEN(チェックタイミング):
| フィールド | 値 | 理由 |
|---|---|---|
| Trigger type | Player said keyword (keyword) | プレイヤーのメッセージに特定のテキストが含まれると発火 |
| Keywords | herb または found herb | プレイヤーが「I found the herbs」のようなことを言ったら一致 |
キーワードマッチングはどう動くのか? エンジンはプレイヤーのメッセージの内容をチェック — どこかにキーワードが含まれていれば一致します。そのため「I found the herbs in the cave」は「herb」を含むためトリガーします。AIの応答内のキーワードも検出したい場合、トリガータイプを「AI said keyword」(
ai-keyword)に設定した別のビヘイビアを作成します。
ONLY IF(条件):
| 変数 | 演算子 | 値 | 理由 |
|---|---|---|---|
quest_1_complete | equals (eq) | false | クエストがまだ完了していないときだけトリガー — 二重報酬を防ぐ |
なぜ条件が必要なのか? 条件がなければ、誰かが「herb」と言うたびに報酬が再度発火します。
quest_1_complete == falseがあれば、最初の「herb」言及 → クエスト完了、報酬支払い、trueマーク。それ以降の言及 → 条件失敗(すでにtrue)、何も起きません。
DO(アクション):
これらのアクションを順番に追加:
| Action type | 設定 | 効果 |
|---|---|---|
| Modify variable | 変数quest_1_complete、オペレーションset、値true | クエストを完了マーク |
| Modify variable | 変数gold、オペレーションadd、値30 | 30ゴールド報酬を支払う |
| Show notification | メッセージQuest Complete: Find Herbs! +30 gold、スタイルachievement | 金色のアチーブメントトーストをポップアップ |
| Tell AI | 内容:The player just completed the quest "Find Herbs" and received 30 gold as a reward. Please acknowledge this in your response. | 何が起きたかAIに知らせ、より良いナラティブ遷移を書けるようにする |
なぜ「Tell AI」? 変数の変更と通知の表示は静かなシステム操作です — AI自体は「クエストが完了した」ことを知りません。このステップを追加することで、AIは次の返信で自然なフォローアップを書けます(例:「You carefully tuck the herbs into your pack, remembering the village elder's request. The trip wasn't for nothing after all」)。
ビヘイビア2:「Defeat the Forest Wolf」クエスト完了
WHEN(チェックタイミング):
| フィールド | 値 | 理由 |
|---|---|---|
| Trigger type | Player said keyword (keyword) | 同上 — プレイヤーキーワードトリガー |
| Keywords | defeat と wolf | 両方の単語が表示される必要がある — 「I saw a wolf」がトリガーしないようにする |
複数キーワードマッチングロジック。 複数のキーワードを入力すると、メッセージはすべてを含む必要があります。そのため「I defeated the forest wolf」はトリガー(「defeat」と「wolf」両方を含む)、「I spotted a wolf」はトリガーしない(「wolf」のみ、「defeat」なし)。
ONLY IF(条件):
| 変数 | 演算子 | 値 | 理由 |
|---|---|---|---|
quest_2_complete | equals (eq) | false | 同上 — 繰り返しトリガーを防ぐ |
DO(アクション):
| Action type | 設定 | 効果 |
|---|---|---|
| Modify variable | 変数quest_2_complete、オペレーションset、値true | クエストを完了マーク |
| Modify variable | 変数gold、オペレーションadd、値50 | 50ゴールド支払い(オオカミ討伐は難しい、報酬は大きい) |
| Show notification | メッセージQuest Complete: Defeat the Forest Wolf! +50 gold、スタイルachievement | 金色のアチーブメントトーストをポップアップ |
| Tell AI | 内容:The player just completed the quest "Defeat the Forest Wolf" and received 50 gold as a reward. Please acknowledge this in your response. | 何が起きたかAIに知らせる |
アクション実行順序
1つのビヘイビア内のアクションは順番に実行されます。そのため:完了マーク → ゴールド追加 → 通知ポップアップ → AIに伝える。この順序は重要 — 変数を最初にマークすることで、すべての後続ロジックが最新の状態に基づくことを保証します。
ステップ3:Root Componentにクエストトラッカーパネルを追加
これがチャットインターフェースにクエストパネルを表示させる重要なステップです。
エディタ → Custom UI セクション → index.tsxを開く → 以下のコードを貼り付け(デフォルトのreturn <Chat />を置き換え):
export default function MyWorld() {
const api = useYumina();
const msgs = api.messages || [];
// Read variables
const quest1Done = api.variables.quest_1_complete === true;
const quest2Done = api.variables.quest_2_complete === true;
const quest1Name = String(api.variables.quest_1_name || "Find Herbs");
const quest2Name = String(api.variables.quest_2_name || "Defeat the Forest Wolf");
const gold = Number(api.variables.gold ?? 0);
// Quest list data
const quests = [
{ name: quest1Name, done: quest1Done, reward: 30 },
{ name: quest2Name, done: quest2Done, reward: 50 },
];
const completedCount = quests.filter(q => q.done).length;
return (
<Chat renderBubble={(msg) => {
const isLastMsg = msg.messageIndex === msgs.length - 1;
return (
<div>
{/* Render message text normally (platform already rendered HTML, use contentHtml directly) */}
<div
style={{ color: "#e2e8f0", lineHeight: 1.7 }}
dangerouslySetInnerHTML={{ __html: msg.contentHtml }}
/>
{/* Quest tracker panel — only shown on the last message */}
{isLastMsg && (
<div style={{
marginTop: "16px",
padding: "16px",
background: "linear-gradient(135deg, rgba(30,41,59,0.8), rgba(15,23,42,0.9))",
borderRadius: "12px",
border: "1px solid #334155",
}}>
{/* Panel header */}
<div style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
marginBottom: "14px",
}}>
<div style={{
fontSize: "15px",
fontWeight: "bold",
color: "#e2e8f0",
letterSpacing: "0.5px",
}}>
Quest Tracker
</div>
{/* Gold counter */}
<div style={{
display: "flex",
alignItems: "center",
gap: "6px",
padding: "4px 12px",
background: "rgba(234,179,8,0.15)",
border: "1px solid rgba(234,179,8,0.3)",
borderRadius: "20px",
}}>
<span style={{ fontSize: "14px" }}>💰</span>
<span style={{
fontSize: "14px",
fontWeight: "bold",
color: "#fbbf24",
}}>
{gold}
</span>
</div>
</div>
{/* Progress indicator */}
<div style={{
fontSize: "12px",
color: "#64748b",
marginBottom: "12px",
}}>
Completed {completedCount}/{quests.length}
</div>
{/* Quest list */}
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
{quests.map((quest, idx) => (
<div
key={idx}
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
padding: "10px 14px",
background: quest.done
? "rgba(34,197,94,0.08)"
: "rgba(30,41,59,0.5)",
border: quest.done
? "1px solid rgba(34,197,94,0.2)"
: "1px solid #1e293b",
borderRadius: "8px",
}}
>
{/* Left side: quest name */}
<div style={{
display: "flex",
alignItems: "center",
gap: "10px",
}}>
<span style={{
fontSize: "13px",
color: quest.done ? "#94a3b8" : "#e2e8f0",
textDecoration: quest.done ? "line-through" : "none",
}}>
{quest.name}
</span>
</div>
{/* Right side: status badge */}
<div style={{
display: "flex",
alignItems: "center",
gap: "8px",
}}>
{/* Reward amount */}
<span style={{
fontSize: "12px",
color: quest.done ? "#4ade80" : "#64748b",
}}>
{quest.done ? `+${quest.reward} g` : `${quest.reward} g`}
</span>
{/* Completion status badge */}
<span style={{
display: "inline-flex",
alignItems: "center",
justifyContent: "center",
width: "24px",
height: "24px",
borderRadius: "6px",
fontSize: "13px",
fontWeight: "bold",
background: quest.done
? "rgba(34,197,94,0.2)"
: "rgba(239,68,68,0.15)",
color: quest.done ? "#4ade80" : "#f87171",
border: quest.done
? "1px solid rgba(34,197,94,0.3)"
: "1px solid rgba(239,68,68,0.25)",
}}>
{quest.done ? "✓" : "✗"}
</span>
</div>
</div>
))}
</div>
{/* All quests complete banner */}
{completedCount === quests.length && (
<div style={{
marginTop: "12px",
padding: "10px",
background: "rgba(34,197,94,0.1)",
border: "1px solid rgba(34,197,94,0.25)",
borderRadius: "8px",
textAlign: "center",
fontSize: "13px",
color: "#4ade80",
fontWeight: "600",
}}>
All quests complete!
</div>
)}
</div>
)}
</div>
);
}} />
);
}コードウォークスルー
長さに圧倒されないでください — 行うことは非常に簡単です。セクションごとに見ていきましょう:
基本セットアップ
const api = useYumina();
const msgs = api.messages || [];
// ...
<Chat renderBubble={(msg) => {
const isLastMsg = msg.messageIndex === msgs.length - 1;
// ...
}} />- Root Component
MyWorld()はワールドUIのエントリ。<Chat renderBubble={...} />はメッセージリスト、入力ボックス、スクロールをプラットフォームに任せ — 各バブルの見た目だけ引き継ぐ useYumina()— 変数を読めるようYumina APIを取得msg.messageIndex— 現在のバブルのメッセージリスト内のインデックス。クエストパネルは最後のメッセージの下にのみ表示されるため、各メッセージで複製パネルが出ないmsg.contentHtml— プラットフォームがMarkdownからレンダリングしたHTML、dangerouslySetInnerHTMLに直接使える
変数の読み取り
const quest1Done = api.variables.quest_1_complete === true;
const quest2Done = api.variables.quest_2_complete === true;
const quest1Name = String(api.variables.quest_1_name || "Find Herbs");
const quest2Name = String(api.variables.quest_2_name || "Defeat the Forest Wolf");
const gold = Number(api.variables.gold ?? 0);=== true— 厳密比較、booleantrueのみが完了とカウントされる。"true"(文字列)や1(数値)が誤解釈されることを防ぐString(... || "Find Herbs")— クエスト名を読み、変数が存在しない場合はデフォルトにフォールバックNumber(... ?? 0)— ゴールドを数値に変換。?? 0は「変数が存在しないなら0を使う」を意味
クエストリストデータ
const quests = [
{ name: quest1Name, done: quest1Done, reward: 30 },
{ name: quest2Name, done: quest2Done, reward: 50 },
];
const completedCount = quests.filter(q => q.done).length;クエスト情報を配列に収集し、.map()でループできるようにします。completedCountは何個完了したか集計、進捗表示に使用。
ステータスバッジ
<span style={{
background: quest.done
? "rgba(34,197,94,0.2)" // done → green background
: "rgba(239,68,68,0.15)", // not done → red background
color: quest.done ? "#4ade80" : "#f87171",
}}>
{quest.done ? "✓" : "✗"}
</span>各クエストの右側に小さなバッジ — 完了なら緑のチェックマーク、未完了なら赤のX。これがバッジコンポーネント効果。
ゴールドカウンタ
<div style={{
padding: "4px 12px",
background: "rgba(234,179,8,0.15)",
borderRadius: "20px",
}}>
💰 {gold}
</div>パネル右上のピル形状のゴールド表示。クエストが完了するたびに、ゴールドが増加しパネルが自動的にリフレッシュして新しい値を表示。
全完了バナー
{completedCount === quests.length && (
<div style={{ /* green highlight styles */ }}>
All quests complete!
</div>
)}すべてのクエストが完了すると、パネル下部に緑のテキスト行が表示されます。completedCount === quests.lengthは完了数が合計に等しいかチェック。
自分でコードを書きたくない?Studio AIを使おう
エディタ上部 → 「Enter Studio」をクリック → AI Assistantパネル → 平易な言葉で説明(例:「クエスト完了状態とゴールドを表示するクエストトラッカーパネルを構築」)すると、AIがコードを生成します。
ステップ4:保存してテスト
- エディタ上部のSaveをクリック
- Start Gameをクリックするか、ホームページに戻って新規セッションを開く
- AIの返信の下にクエストトラッカーパネルが見える:2つの赤Xマーク付きクエスト、0ゴールド
- キーワードを含むメッセージを送信(例:「I found the herbs」)— あなたのメッセージに「herb」が含まれ、ビヘイビアが即座に発火、パネルが更新:「Find Herbs」が緑のチェックマークに変わり、ゴールドが30になり、アチーブメント通知がポップアップ
- キーワード付き別メッセージを送信(例:「I defeated the forest wolf」)— あなたのメッセージに「defeat」と「wolf」両方が含まれ、2つ目のクエストが完了、ゴールドは80に増加
- 両方のクエストが完了したら、パネル下部に緑の「All quests complete!」バナーが表示
何かが機能していない場合:
| 症状 | 想定される原因 | 対処 |
|---|---|---|
| クエストパネルが表示されない | Root Componentコードが保存されていない、または構文エラー | Custom UIセクション下部のコンパイル状態を確認 — 緑の「OK」が表示されるべき |
| 「herb」を含むメッセージを送ったがクエストが完了しない | ビヘイビアキーワードが実際の表現と一致しない | メッセージに実際に「herb」が含まれていることを確認。注:トリガーはプレイヤーキーワードトリガー — プレイヤーのメッセージのみチェック、AIの返信はチェックしない |
| クエストは完了したがゴールドが変わらない | ビヘイビアに「modify variable gold add」アクションが欠落 | ビヘイビアエディタに戻り、「modify variable quest_1_complete」の後に「modify variable gold add 30」アクションがあることを確認 |
| 同じクエストが繰り返し報酬を与える | 条件が設定されていない | ビヘイビアのONLY IF条件にquest_1_complete eq falseが含まれていることを確認 — 未完了時のみトリガー |
| パネルがリアルタイムで更新されない | 正常 — パネルは次のメッセージで更新 | 変数はすでに変更済み、AIの返信を待つか別のメッセージを送るとパネルが自動更新 |
| 通知がポップアップしない | ビヘイビアに「show notification」アクションが欠落 | アクションリストに通知表示アクションがあり、スタイルがachievementに設定されていることを確認 |
| 「Defeat the wolf」クエストがトリガーしない | 両方のキーワードが同じメッセージに表示される必要 | メッセージに「defeat」と「wolf」両方が含まれていることを確認。「I beat the wolf」と書いた場合、キーワードを「beat」に変更するか「beat」を代替として追加する必要 |
さらに進める:クエストシステムの拡張
基本が分かったら、この基盤の上に構築できます。
クエストを追加
Variablesタブに新しいboolean変数(quest_3_complete)と文字列変数(quest_3_name)を追加し、Behaviorsタブで対応するキーワードトリガービヘイビアを作成。最後に、Root Componentのquests配列に行を追加:
const quests = [
{ name: quest1Name, done: quest1Done, reward: 30 },
{ name: quest2Name, done: quest2Done, reward: 50 },
{ name: quest3Name, done: quest3Done, reward: 100 },
];AIにクエストを割り当てさせる
「クエスト受諾」フローを構築できます — AIが対話で新しいクエストを記述し、ビヘイビアが特定のキーワードを検出してクエスト名変数を動的に更新:
| Action type | 設定 |
|---|---|
| Modify variable | 変数quest_3_name、オペレーションset、値Escort the merchant to safety |
| Show notification | メッセージNew Quest: Escort the merchant to safety、スタイルachievement |
ショップシステムとの組み合わせ
クエストで獲得したゴールドはショップで使えます。Recipe #3(ショップとトレード)を参照 — 同じgold変数を使用。クエストシステムはゴールドを追加し、ショップシステムはそれを差し引きます。両方のシステムが1つの経済を共有。
クエストチェーン
ビヘイビアで条件の組み合わせを使って複雑なクエスト依存関係を作成できます。例:「Find Herbsを完了してからのみSave the Villageを受諾できる」:
| 変数 | 演算子 | 値 |
|---|---|---|
quest_1_complete | equals (eq) | true |
quest_3_complete | equals (eq) | false |
両方の条件が満たされる必要がある — 前提クエストが完了し、現在のクエストがまだ完了していないことを保証。
クイックリファレンス
| やりたいこと | 方法 |
|---|---|
| クエスト完了を追跡 | boolean変数を作成、デフォルトfalse、カテゴリFlag |
| キーワードでクエスト完了を検出 | ビヘイビアトリガータイプ「Player said keyword」(keyword)、キーワードを入力 |
| 繰り返しトリガーを防ぐ | ビヘイビアの条件にquest_complete eq falseを追加 |
| 完了時にアチーブメントトーストをポップアップ | ビヘイビアアクション:通知表示、スタイルachievement |
| 完了時にゴールドを授与 | ビヘイビアアクション:変数を変更、gold add 数量 |
| クエスト完了をAIに知らせる | ビヘイビアアクション:tell AI、何が起きたか説明する文を書く |
| クエストパネルを表示 | Root Componentで変数を読み、チェックマーク/Xマークとゴールドをレンダリング |
| パネルを最後のメッセージにのみ表示 | <Chat renderBubble>内でmsg.messageIndex === msgs.length - 1をチェック |
| 完了したクエストに取り消し線 | textDecoration: "line-through"スタイルを使用 |
| 完了進捗を表示 | quests.filter(q => q.done).lengthでカウント |
| すべてのクエスト完了時に特別バナー | completedCount === quests.lengthをチェック |
自分で試す — インポート可能なデモワールド
このJSONをダウンロードし、インポートしてクエスト追跡システムを体験してください:
インポート方法:
- Yumina → My Worlds → Create New World に移動
- エディタで More Actions → Import Package をクリック
- ダウンロードした
.jsonファイルを選択 - 全変数、ビヘイビア、Root Componentが事前設定された新規ワールドが作成されます
- 新規セッションを開始して試す
含まれるもの:
- 5つの変数(クエスト状態用
quest_1_completeとquest_2_complete、通貨用gold、クエスト名用quest_1_nameとquest_2_name) - 2つのビヘイビア(Find Herbs完了 + Defeat the Forest Wolf完了、各々条件チェック、変数変更、通知、tell-AIアクション付き)
- Root Component(クエストトラッカーパネル:クエストリスト + ステータスバッジ + ゴールドカウンタ + 完了進捗)
これはRecipe #6です
これまでのレシピでは、シーンジャンプ、戦闘システム、ショップとトレード、キャラクター作成をカバーしました。このレシピでは、boolean変数 + キーワードトリガー + 条件チェックを使ってクエスト追跡システムを構築する方法を教えます。同じパターンが、アチーブメントシステム、ストーリー進捗追跡、サイドクエストツリーなど、「イベントを検出 → 状態をマーク → 報酬を支払う → UIを更新」のループが必要なあらゆるものに拡張できます。
