キャラクター作成フォーム
物語が始まる前に、プレイヤーがキャラクターシートを埋めます。名前、クラス、短いバックストーリー。「Start Adventure」を押すと物語が始まり、AI は最初からプレイヤーが何者かを知っています。プレイヤー画面にはちょうどこのためのテンプレートがあるので、フォームにコードは要りません。
これから作るもの
最初のメッセージの前に、プレイヤーが順に通る数枚のページです。
- 名前の入力欄
- クラスの選択:Warrior、Mage、Rogue。それぞれに一行の説明が付きます
- バックストーリーの入力欄(任意)
- 確認ページ:選んだ内容をまとめ、物語を始めるボタンがあります
プレイヤーが入力したものは、すべて普通の変数に入ります。AI は最初の返信からそれを読めます。
しくみ
1. プレイヤーが新しくプレイを始める → チャットの代わりに最初の開始ページが出る
2. 名前とバックストーリーを入力 → 変数「プレイヤー名」と「バックストーリー」に保存
3. クラスを選ぶ → 変数「クラス」に保存
4. 確認する → あなたのオープニングでチャットが始まる
5. プレイヤーが最初のメッセージを送る
→ AI のプロンプトには、変数とそのビヘイビアルール、
選んだクラスのときだけ送られる設定が入る
→ AI はプレイヤーを名前で呼び、クラスに合わせて書く手順
ステップ 1:テンプレートから開始ページを追加する
上部中央の プレイヤー画面 を押し、テンプレート を開きます。オープニング · 物語が始まる前 から次の 3 つを追加します。
| テンプレート | できるもの |
|---|---|
| キャラクターシート | 名前・性別・年齢・外見・秘密ひとつ。入力するとカードができていきます。必須は名前だけです |
| 出自を選ぶ | 選択肢が 3 つあるカード選択。これをクラス選択に作り変えます |
| 開始前の確認 | ここまでの選択をまとめます。いつも最後に並びます |
プレイヤーは物語が始まる前に、これらを順番に通ります。どのテンプレートにも、カードに追加される変数が書いてあります(「このカードに次の変数を追加します」)。これらは普通の変数で、AI に意味を伝えるビヘイビアルールもすでに入っています。たとえば「プレイヤーの名前。物語の人物は皆この名前でプレイヤーを呼ぶ。別の名前を作らない。」のように。
名前だけでいいなら
名前を入力 テンプレートは、名前と呼ばれ方だけを聞きます。
ステップ 2:出自の選択をクラスの選択に変える
左のページの列で 出自を選ぶ のページを選び、次のように変えます。
- 見出しをクリックして「クラスを選ぶ」に書き換える
- カード選択を選び、3 つの選択肢の名前を Warrior(「近接戦闘の専門家。HP が高い」)、Mage(「遠距離魔法。MP が高い」)、Rogue(「身軽で隠密が得意。クリティカルが高い」)に変える
- ページの列でそのページをダブルクリックし、名前を「クラス」に変える
カード選択は、選ばれた選択肢を変数 出自 に保存します。キャンバスに戻り、変数ブロックで出自を見つけて名前を クラス に変え、ビヘイビアルール を書き直します。
プレイヤーが最初に選んだクラス:Warrior、Mage、Rogue のどれか。戦闘で何ができるか、周りからどう見られるかを決める。変えないこと。続けて AI での扱い を開き、AI アクセス を AI は読み取り専用 にします。これで AI がプレイヤーのクラスを書き換えられなくなります。
ステップ 3:キャラクターシートを整える
キャラクターシート のページでは、名前は残し、欄のひとつをバックストーリーに変えます。
- いらない部品は削除します(部品を選ぶか レイヤー から探して Delete キー)。その変数は、あとでキャンバスから消せます
- 「ひとつの秘密」の欄のラベルを「バックストーリー(任意)」に、プレースホルダーを「あなたの過去を数行で」に変えます。キャンバスでその変数の名前を プレイヤーの秘密 から バックストーリー に変え、ビヘイビアルールを
プレイヤー自身が書いたバックストーリー。物語に活かし、矛盾させないこと。にします(テンプレートのもとのルールは、物語の誰も知らない秘密として扱うもので、バックストーリーには合いません)
プレイヤー名 はテンプレートのルールのままで大丈夫です。名前を飛ばすプレイヤーに備えたいなら、開始ボタンを選んで これが埋まるまで押せない でプレイヤー名にチェックを入れます。名前が入力されるまで、ボタンはグレーのままになります。
ステップ 4(任意):クラスごとに設定を用意する
変数は、プレイヤーが何を選んだかを AI に伝えます。クラスごとにもっと材料を渡したいなら、そのクラスのときだけ AI に届く設定をクラスごとに追加します。
- 設定「Mage の設定」を追加し、この世界で Mage が何を知っていて何ができるかを書く
- 変数 クラス をその設定の上にドラッグし、できた線をクリックする。条件を クラス =
Mageにする - Warrior と Rogue でも同じようにする
これで Mage を選んだプレイヤーの AI には Mage の設定が届き、ほかのクラスの設定は届きません。条件ロアについてはオープニングと設定を参照してください。
ステップ 5:オープニングを書く
オープニング(キャンバスの オープニング)は、フォームのあとに出る最初のメッセージです。いつもどおりに書きます。
*運命の門が開き、エルダーランドに夜明けの光があふれる。*
*遠くに街の影がゆらめき、石畳の道がそこへ続いている。風は草の匂いと、遠くの炉の煙の匂いを運んでくる。*
目の前には三つの道がある。町へ続く広い道、森を抜ける細い小道、川へ下る坂。どちらへ進む?プレイヤーの選択をオープニングの本文に入れない
オープニングは、プレイヤーがフォームを埋める前、プレイが作られた時点で書き込まれます。オープニングに {{Player name}} のマクロを書くと、プレイヤーが入力した名前ではなく、変数の初期値が表示されます。名前やクラスは AI に任せましょう。AI の最初の返信は、もうそれを知っています。
ステップ 6:プレイテスト
プレイヤー画面で 編集 をオフにすると、マッシーを使わずにページを順に押して確かめられます。そのあと上部バーの プレイ を押します。
- キャラクターシートが出る。「Elara」と、バックストーリーを一行入力する
- Mage を選ぶ
- 確認ページに選んだ内容が並ぶ。開始ボタンを押す
- オープニングが出る。「町のほうへ向かう」と送る
- AI があなたを Elara と呼び、Mage として描く(杖、呪文、ローブに目を留める人々)
プレイテストの右側の このターン で、AI に届いた変数と設定を確認できます。
うまくいかないときは:
| 症状 | 考えられる原因 | 直し方 |
|---|---|---|
| フォームが出ない | ページが開始ページの流れに入っていない | オープニング · 物語が始まる前 のテンプレートから追加し、確認ページを最後にする |
| ページに 出口なし の注意が付く | そのページに、どこかへ進むボタンがない | ページへ移動 のボタンを付ける |
| AI がクラスを無視する | クラスの変数にビヘイビアルールがない、または AI が変数を見ていない | ビヘイビアルールを書く。カードの Context にある AI に見せる変数 が なし になっていないか確認する |
| クラスの設定が届かない | 条件の値が、選んだ選択肢と完全には一致していない | 条件は選択肢の名前どおり Mage にする |
| AI がプレイヤーの名前を変えてしまう | プレイヤー名を AI が書き換えられる | AI アクセス を AI は読み取り専用 にする |
カスタムコードの方法:最初のメッセージの中にフォームを出す
フォームを自分の画面のコードで描きたい場合、たとえば最初のメッセージのすぐ下に出したい場合は、こちらのコード版を使います。文字の変数を 3 つ(player_name、player_class、player_backstory。どれも AI は読み取り専用、player_name の初期値は Traveler)と、オープニングを 2 つ使います。1 つ目は「あなたは誰?」と聞く短い場面、2 つ目が本当の物語のオープニングです。
画面のコードは、変数を名前で読み書きします。パネル → フロントエンドコード で index.tsx を開き、デフォルトの return <Chat /> を置き換えます。
export default function MyWorld() {
const api = useYumina();
const [name, setName] = React.useState("");
const [selectedClass, setSelectedClass] = React.useState("");
const [backstory, setBackstory] = React.useState("");
// Once a class is saved, the form is done
const hasCreated = String(api.variables.player_class || "") !== "";
const classes = [
{ id: "Warrior", label: "Warrior", icon: "⚔️", desc: "Melee specialist, high HP" },
{ id: "Mage", label: "Mage", icon: "🔮", desc: "Ranged magic, high MP" },
{ id: "Rogue", label: "Rogue", icon: "🗡️", desc: "Agile and stealthy, high crit" },
];
const handleStart = async () => {
if (!selectedClass) return;
// Switch to the second opening FIRST: switching resets variables
// to that opening's starting values
await api.switchGreeting(1);
api.setVariable("player_name", name.trim() || "Traveler");
api.setVariable("player_class", selectedClass);
api.setVariable("player_backstory", backstory.trim());
};
const label = { fontSize: "13px", color: "#a5b4fc", marginBottom: "6px", fontWeight: "600" };
const field = {
width: "100%", padding: "10px 14px", background: "#0f172a",
border: "1px solid #334155", borderRadius: "8px", color: "#e2e8f0",
fontSize: "14px", outline: "none", boxSizing: "border-box", fontFamily: "inherit",
};
return (
<Chat renderBubble={(msg) => (
<div>
<div
style={{ color: "#e2e8f0", lineHeight: 1.7 }}
dangerouslySetInnerHTML={{ __html: msg.contentHtml }}
/>
{msg.messageIndex === 0 && !hasCreated && (
<div style={{
marginTop: "20px",
padding: "24px",
background: "linear-gradient(135deg, #1e1b4b 0%, #1a1a2e 100%)",
borderRadius: "16px",
border: "1px solid #312e81",
}}>
<div style={{ fontSize: "18px", fontWeight: "bold", color: "#c4b5fd", marginBottom: "20px", textAlign: "center" }}>
Create Your Character
</div>
<div style={{ marginBottom: "16px" }}>
<div style={label}>Character Name</div>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Leave blank for 'Traveler'"
style={field}
/>
</div>
<div style={{ marginBottom: "16px" }}>
<div style={{ ...label, marginBottom: "8px" }}>Choose a Class</div>
<div style={{ display: "flex", gap: "10px" }}>
{classes.map((cls) => {
const picked = selectedClass === cls.id;
return (
<button
key={cls.id}
onClick={() => setSelectedClass(cls.id)}
style={{
flex: 1,
padding: "14px 10px",
background: picked ? "linear-gradient(135deg, #4338ca, #6366f1)" : "#1e293b",
border: picked ? "2px solid #818cf8" : "1px solid #334155",
borderRadius: "10px",
color: picked ? "#e0e7ff" : "#94a3b8",
cursor: "pointer",
textAlign: "center",
}}
>
<div style={{ fontSize: "24px", marginBottom: "4px" }}>{cls.icon}</div>
<div style={{ fontSize: "14px", fontWeight: "bold", marginBottom: "2px" }}>{cls.label}</div>
<div style={{ fontSize: "11px", opacity: 0.7 }}>{cls.desc}</div>
</button>
);
})}
</div>
</div>
<div style={{ marginBottom: "20px" }}>
<div style={label}>Backstory (optional)</div>
<textarea
value={backstory}
onChange={(e) => setBackstory(e.target.value)}
placeholder="A few sentences about your character's past..."
rows={3}
style={{ ...field, resize: "vertical" }}
/>
</div>
<button
onClick={handleStart}
disabled={!selectedClass}
style={{
width: "100%",
padding: "14px",
background: selectedClass ? "linear-gradient(135deg, #7c3aed, #a855f7)" : "#374151",
border: "none",
borderRadius: "10px",
color: selectedClass ? "#f5f3ff" : "#6b7280",
fontSize: "16px",
fontWeight: "bold",
cursor: selectedClass ? "pointer" : "not-allowed",
}}
>
{selectedClass ? "Start Adventure" : "Pick a class first"}
</button>
</div>
)}
</div>
)} />
);
}コードがしていること:
msg.messageIndex === 0 && !hasCreatedで、フォームを最初のメッセージにだけ、しかもクラスが保存されるまでのあいだだけ出します- 3 つの
React.useStateは、プレイヤーが Start Adventure を押すまで、入力中の内容を持っておきます api.switchGreeting(1)は、最初のメッセージを 2 つ目のオープニングに差し替えます(オープニングは 0 から数えます)。切り替えると、変数はそのオープニングの初期値に戻ります。 そのためコードは切り替えが終わるのを待ってから変数を書き込みます。順番が逆だと、切り替えで消えてしまいますapi.setVariable("player_class", ...)は変数の名前で書き込みます
AI は次のプロンプトでこれらの変数を読みます。プロフィールを設定の文章として書き出したいなら、毎ターン送る設定にマクロを書くこともできます。
[Player character]
Name: {{player_name}}
Class: {{player_class}}
Backstory: {{player_backstory}}設定の中のマクロは、AI のプロンプトが組み立てられるたびに埋められるので、いつも現在の値になります。オープニングの中のマクロはそうではありません。オープニングはプレイが作られた時点で書き込まれるので、そこに書いた {{player_name}} は初期値(「Traveler」)を表示します。
パネル → フロントエンドコード のファイルの横の表示が、緑の OK になっていれば大丈夫です。
以降のメッセージにキャラクターを表示する
renderBubble の中、メッセージ本文の上に入れます。
{hasCreated && (
<div style={{ display: "flex", gap: "8px", marginBottom: "8px", fontSize: "12px", color: "#a5b4fc" }}>
<span>{String(api.variables.player_name)}</span>
<span style={{ opacity: 0.5 }}>|</span>
<span>{String(api.variables.player_class)}</span>
</div>
)}コードを自分で書きたくないときは
キャンバス右上の AIアシスタント を押して、ほしいフォームを説明してください。
さらに発展させる
クラスを増やす
テンプレートの方法なら、カード選択に選択肢を足し、対応する設定を追加します。コードの方法なら、classes の配列に項目を足します。
能力値
能力値を振る テンプレートは、6 ポイントを筋力・敏捷・魅力・知力に振り分けます。クラスのページと確認ページのあいだに置きましょう。
クラスごとに違う始まり方
クラスごとにまったく別の場所から始めたいなら、クラスごとにオープニングを書き、オープニングを選ぶ テンプレートを追加します(オープニングが 2 つ以上必要です)。オープニングに結びつけたシチュエーションは、そのオープニングから始めたプレイの中にだけ存在します。シチュエーションを参照してください。
早見表
| やりたいこと | やり方 |
|---|---|
| 物語の前にフォームを出す | プレイヤー画面 → テンプレート → オープニング · 物語が始まる前 |
| 名前だけ聞く | 名前を入力 テンプレート |
| いくつもの欄を埋めてもらう | キャラクターシート テンプレート |
| いくつかの選択肢から 1 つ選ぶ | 出自を選ぶ テンプレート(カード選択)を名前を変えて使う |
| 始める前にまとめを見せる | 開始前の確認 テンプレート。いつも最後 |
| 入力を必須にする | 開始ボタン → これが埋まるまで押せない |
| AI に選択を変えさせない | AI アクセス:AI は読み取り専用 |
| 選択ごとに設定を変える | 変数に条件を付けた設定 |
| 選択を設定の本文に入れる | 設定に {{player_name}} のようなマクロを書く(オープニングには書かない) |
| コード:オープニングを切り替えてから選択を保存 | await api.switchGreeting(1) のあとに api.setVariable(...) |
これはレシピ #4 です
ボタンからオープニングを切り替える方法と、設定の中でマクロを使う方法は、UI によるシーンジャンプと設定の切り替えで詳しく扱っています。キャラクター作成は、ほかのレシピの前段階としてよく使われます。クラスによってショップの所持金の初期値や、持ち物の最初のアイテムを決めることもできます。
