Formulario de creación de personaje
El jugador abre una sesión y ve una pantalla de creación de personaje — escribe un nombre, elige una clase, escribe un trasfondo, hace clic en "Iniciar aventura", y el chat salta a la apertura real de la historia. Desde la primerísima respuesta de la IA en adelante, la IA conoce todo sobre el personaje del jugador.
Lo que vas a construir
El primer mensaje no es una historia — es un formulario de creación de personaje. El formulario es renderizado por el Root Component e incluye:
- Una entrada de texto — para que el jugador escriba el nombre de su personaje
- Tres botones de selección de clase — Guerrero / Mago / Pícaro
- Un área de texto — para que el jugador escriba un trasfondo
- Un botón "Iniciar aventura" — al hacer clic, guarda toda la información en variables y luego salta a la apertura real de la historia
Tras el salto, los macros {{player_name}}, {{player_class}} y {{player_backstory}} en tus entradas de lore se sustituyen automáticamente por el motor con lo que el jugador haya rellenado. Para cuando la IA escriba su primera respuesta, ya tiene el perfil completo del personaje.
Requisitos previos
Esta receta se basa directamente en dos técnicas centrales de la Receta #1:
| Técnica | Fuente | Cómo la usa esta receta |
|---|---|---|
switchGreeting(index) para saltar entre aperturas | Receta #1 Parte 1 | Tras rellenar el formulario, salta de la "pantalla de creación" a la "apertura de la historia" |
Sustitución de macros {{variableId}} en el contenido de la entrada | Receta #1 Parte 2 | Macros como {{player_name}} en las entradas se sustituyen con la entrada del jugador en el momento de construcción del prompt |
Si aún no has leído la Receta #1, empieza ahí: Salto de escenas y cambio de entradas mediante UI.
Cómo funciona
Secuencia completa:
1. El jugador inicia una nueva sesión → ve el saludo #1 (el formulario de creación de personaje)
2. El `<Chat renderBubble>` del Root Component detecta `msg.messageIndex === 0`, renderiza la UI del formulario
3. El jugador escribe un nombre, elige una clase, escribe un trasfondo
4. El jugador hace clic en "Iniciar aventura"
→ el código llama a api.setVariable("player_name", "Elara")
→ el código llama a api.setVariable("player_class", "Mage")
→ el código llama a api.setVariable("player_backstory", "Grew up in a wizard's tower...")
→ el código llama a api.switchGreeting(1)
→ el primer mensaje cambia instantáneamente al saludo #2 (la apertura real de la historia)
5. El jugador envía su primer mensaje
→ el motor construye el prompt → escanea las entradas en busca de macros {{...}}
→ {{player_name}} sustituido por "Elara"
→ {{player_class}} sustituido por "Mage"
→ {{player_backstory}} sustituido por "Grew up in a wizard's tower..."
→ La IA recibe el perfil completo del personaje → escribe su primera respuestaPunto clave: setVariable surte efecto inmediatamente, pero la IA solo ve el cambio la próxima vez que se construye el prompt. Así que el orden es: setVariable para almacenar valores primero → luego switchGreeting para saltar → el jugador envía un mensaje → la IA puede usar la información del personaje en su respuesta.
Paso a paso
Paso 1: Crea las variables
Necesitas tres variables de tipo string para almacenar la información del personaje del jugador.
Editor → barra lateral → pestaña Variables → haz clic en "Add Variable" y crea estas tres:
Variable 1: Nombre del personaje
| Campo | Valor | Por qué |
|---|---|---|
| Nombre visible | Nombre del personaje | Para tu propia referencia en el editor |
| ID | player_name | El macro {{player_name}} en las entradas busca este ID |
| Tipo | String | Porque un nombre es texto |
| Valor por defecto | Traveler | Si el jugador empieza sin rellenar un nombre, la IA lo llama "Traveler" |
| Categoría | Custom | Etiqueta organizativa, puramente para gestión |
| Reglas de comportamiento | No modifiques esta variable. La establece el jugador a través del formulario de creación de personaje. | Le dice a la IA que no cambie el nombre del personaje por sí misma |
Variable 2: Clase del personaje
| Campo | Valor | Por qué |
|---|---|---|
| Nombre visible | Clase del personaje | Para tu propia referencia |
| ID | player_class | El macro {{player_class}} en las entradas busca este ID |
| Tipo | String | Porque la clase es texto ("Warrior", "Mage", "Rogue") |
| Valor por defecto | déjalo vacío | Vacío significa aún no elegido. El Root Component comprueba este valor para decidir qué botón resaltar |
| Categoría | Custom | Etiqueta organizativa |
| Reglas de comportamiento | No modifiques esta variable. La establece el jugador a través del formulario de creación de personaje. | Le dice a la IA que no cambie la clase por sí misma |
Variable 3: Trasfondo del personaje
| Campo | Valor | Por qué |
|---|---|---|
| Nombre visible | Trasfondo del personaje | Para tu propia referencia |
| ID | player_backstory | El macro {{player_backstory}} en las entradas busca este ID |
| Tipo | String | Porque un trasfondo es texto |
| Valor por defecto | déjalo vacío | Vacío = el jugador no escribió un trasfondo. El lugar correspondiente en la entrada será una cadena vacía |
| Categoría | Custom | Etiqueta organizativa |
| Reglas de comportamiento | No modifiques esta variable. La establece el jugador a través del formulario de creación de personaje. | Le dice a la IA que no cambie el trasfondo por sí misma |
¿Por qué
player_nametiene un valor predeterminado pero los otros dos no? Porque un nombre es necesario en casi todos los escenarios — la IA tiene que llamar al personaje algo. Un valor de respaldo "Traveler" evita que la IA escriba un en blanco incómodo o "unnamed character" en sus respuestas. La clase y el trasfondo pueden estar vacíos — la IA puede razonablemente ignorarlos o improvisar.
Paso 2: Crea dos saludos en "First Message"
Abre el editor y haz clic en la pestaña First Message en la barra lateral.
Crea el primer saludo (pantalla de creación de personaje):
Haz clic en el botón "Create First Message". En el cuadro de texto, escribe:
*A warm glow envelops you. You feel yourself taking shape — but your identity is not yet defined.*
*An ancient voice echoes through the void:*
"Welcome, traveler. Before you step into this world, tell me — who are you?"Este texto es decoración atmosférica — la UI real del formulario es renderizada por el Root Component debajo de este texto. Lo que el jugador ve es: un pasaje ambiental arriba y un formulario interactivo de creación de personaje debajo.
Crea el segundo saludo (la apertura real de la historia):
Haz clic en el botón "Add Greeting" en la parte inferior. Cambia a la pestaña 2 y escribe la apertura real de la historia:
*{{player_name}} pushes open the gate of destiny.*
*You are a {{player_class}}, and this is your first time setting foot in the Elderlands. The silhouette of a distant city shimmers in the dawn light, and a cobblestone road stretches toward the unknown.*
*A breeze brushes your face, carrying the scent of grass and distant hearth-smoke. You take a deep breath — the adventure begins now.*
Three paths lie before you: a wide road leading to town, a narrow trail through the woods, and a slope descending to the river. Which way do you go?Los macros también funcionan en los saludos
Fíjate en {{player_name}} y {{player_class}} en el segundo saludo. Estos macros se sustituyen con el valor actual de la variable en el momento de visualización. Así que después de que el jugador rellene el formulario y las variables se actualicen mediante setVariable, cuando switchGreeting(1) cambie a este saludo, el jugador verá el nombre y la clase de su propio personaje en la apertura de la historia.
El orden de los saludos = índice
Pestaña 1 = índice 0 (la pantalla de creación de personaje, mostrada por defecto), Pestaña 2 = índice 1 (la apertura de la historia). La llamada switchGreeting(1) en el Root Component salta al segundo.
Paso 3: Crea una entrada de lore que use macros
Ahora crea una entrada que inyecte la información del personaje en cada prompt enviado a la IA.
Editor → pestaña Entries → crea una nueva entrada
| Campo | Valor | Por qué |
|---|---|---|
| Nombre | Perfil del personaje del jugador | Para tu propia referencia |
| Sección | System Presets | Las entradas en la sección de presets siempre se envían a la IA |
| Habilitada | Sí (activado) | Siempre activa — la información del personaje es algo que la IA necesita en todo momento |
Contenido:
[Player Character Profile]
Name: {{player_name}}
Class: {{player_class}}
Backstory: {{player_backstory}}
Always address the player by their character's name. Adjust interactions, available skills, and encounters based on their class and backstory.¿Qué pasa?
Cuando el motor construye el prompt, escanea este texto:
{{player_name}}→ sustituido por el valor actual de la variableplayer_name(por ejemplo, "Elara"){{player_class}}→ sustituido por el valor actual de la variableplayer_class(por ejemplo, "Mage"){{player_backstory}}→ sustituido por el valor actual de la variableplayer_backstory(por ejemplo, "Grew up in a wizard's tower")
Si una variable es una cadena vacía, el lugar correspondiente queda en blanco. Por ejemplo, si el jugador no escribió un trasfondo, la IA ve "Backstory:" seguido de nada — la IA típicamente ignorará el campo vacío o improvisará.
Paso 4: Construye el formulario de creación de personaje en el Root Component
Este es el paso central — renderizar un formulario interactivo de creación de personaje dentro del chat.
Editor → sección Custom UI → abre index.tsx → pega este código (reemplazando el predeterminado return <Chat />):
export default function MyWorld() {
const api = useYumina();
// ---- Estado del formulario ----
const [name, setName] = React.useState(
String(api.variables.player_name || "")
);
const [selectedClass, setSelectedClass] = React.useState(
String(api.variables.player_class || "")
);
const [backstory, setBackstory] = React.useState(
String(api.variables.player_backstory || "")
);
// Comprueba si la creación de personaje ya está hecha (clase establecida = formulario enviado)
const hasCreated = String(api.variables.player_class || "") !== "";
// Lista de clases
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" },
];
// Maneja "Start Adventure"
const handleStart = () => {
if (!selectedClass) return; // Hay que elegir una clase primero
api.setVariable("player_name", name.trim() || "Traveler");
api.setVariable("player_class", selectedClass);
api.setVariable("player_backstory", backstory.trim());
api.switchGreeting?.(1); // Salta al saludo #2 (apertura de la historia)
};
return (
<Chat renderBubble={(msg) => (
<div>
{/* Renderiza el texto del mensaje (la plataforma ya renderizó HTML, usa msg.contentHtml directamente) */}
<div
style={{ color: "#e2e8f0", lineHeight: 1.7 }}
dangerouslySetInnerHTML={{ __html: msg.contentHtml }}
/>
{/* Formulario de creación de personaje — solo en el primer mensaje y aún no creado */}
{msg.messageIndex === 0 && !hasCreated && (
<div
style={{
marginTop: "20px",
padding: "24px",
background: "linear-gradient(135deg, #1e1b4b 0%, #1a1a2e 100%)",
borderRadius: "16px",
border: "1px solid #312e81",
}}
>
{/* Título */}
<div
style={{
fontSize: "18px",
fontWeight: "bold",
color: "#c4b5fd",
marginBottom: "20px",
textAlign: "center",
}}
>
Create Your Character
</div>
{/* Entrada de nombre */}
<div style={{ marginBottom: "16px" }}>
<div
style={{
fontSize: "13px",
color: "#a5b4fc",
marginBottom: "6px",
fontWeight: "600",
}}
>
Character Name
</div>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter your name (leave blank for 'Traveler')"
style={{
width: "100%",
padding: "10px 14px",
background: "#0f172a",
border: "1px solid #334155",
borderRadius: "8px",
color: "#e2e8f0",
fontSize: "14px",
outline: "none",
boxSizing: "border-box",
}}
/>
</div>
{/* Selección de clase */}
<div style={{ marginBottom: "16px" }}>
<div
style={{
fontSize: "13px",
color: "#a5b4fc",
marginBottom: "8px",
fontWeight: "600",
}}
>
Choose a Class
</div>
<div style={{ display: "flex", gap: "10px" }}>
{classes.map((cls) => (
<button
key={cls.id}
onClick={() => setSelectedClass(cls.id)}
style={{
flex: 1,
padding: "14px 10px",
background:
selectedClass === cls.id
? "linear-gradient(135deg, #4338ca, #6366f1)"
: "#1e293b",
border:
selectedClass === cls.id
? "2px solid #818cf8"
: "1px solid #334155",
borderRadius: "10px",
color:
selectedClass === cls.id ? "#e0e7ff" : "#94a3b8",
cursor: "pointer",
textAlign: "center",
transition: "all 0.2s",
}}
>
<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>
{/* Trasfondo */}
<div style={{ marginBottom: "20px" }}>
<div
style={{
fontSize: "13px",
color: "#a5b4fc",
marginBottom: "6px",
fontWeight: "600",
}}
>
Backstory (optional)
</div>
<textarea
value={backstory}
onChange={(e) => setBackstory(e.target.value)}
placeholder="A few sentences about your character's history..."
rows={3}
style={{
width: "100%",
padding: "10px 14px",
background: "#0f172a",
border: "1px solid #334155",
borderRadius: "8px",
color: "#e2e8f0",
fontSize: "14px",
outline: "none",
resize: "vertical",
boxSizing: "border-box",
fontFamily: "inherit",
}}
/>
</div>
{/* Botón Comenzar aventura */}
<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",
transition: "all 0.2s",
}}
>
{selectedClass ? "Start Adventure" : "Pick a class first"}
</button>
</div>
)}
</div>
)} />
);
}Recorrido por el código
Gestión de estado:
const api = useYumina()— obtiene la API de Yumina para leer/escribir variables y cambiar saludosname/selectedClass/backstory— tres estados de React que siguen el campo de entrada, los botones de clase y el área de textoReact.useState(String(api.variables.player_name || ""))— los valores iniciales se leen de las variables. En una nueva sesión, estos son los predeterminados; en una sesión existente, se restauran desde las variables guardadashasCreated— comprueba siplayer_classes una cadena vacía. Vacío = personaje aún no creado; no vacío = ya creado, ocultar el formulario
UI del formulario:
msg.messageIndex === 0 && !hasCreated— solo muestra el formulario en el primer mensaje y solo antes de que se cree el personaje (msglo pasa<Chat renderBubble>)classes.map(...)— itera sobre la lista de clases, renderizando un botón para cada una. La clase seleccionada obtiene un borde resaltado y fondo con degradadoselectedClass === cls.id— comprueba si esta es la clase actualmente seleccionada, usado para resaltardisabled={!selectedClass}— el botón está en gris y no se puede hacer clic hasta que se seleccione una clase
Lógica de envío (handleStart):
api.setVariable("player_name", name.trim() || "Traveler")— almacena el nombre. Si el jugador lo dejó en blanco, recurre a "Traveler"api.setVariable("player_class", selectedClass)— almacena la claseapi.setVariable("player_backstory", backstory.trim())— almacena el trasfondoapi.switchGreeting?.(1)— salta al saludo #2. El encadenamiento opcional?.evita errores si la API no está disponible
¿Por qué este orden de llamada?
setVariable x 3 → switchGreeting(1)
↑ ↑
almacenar datos luego saltar
primeroDebes llamar a setVariable antes de switchGreeting. Los macros {{player_name}} y {{player_class}} del saludo se sustituyen inmediatamente al mostrarse — si saltas primero y almacenas después, los macros aún tendrán los valores antiguos (cadena vacía o predeterminado).
Paso 5: Guarda y prueba
- Haz clic en Save en la parte superior del editor
- Haz clic en Start Game o vuelve a la página de inicio e inicia una nueva sesión
- Ves el texto atmosférico del primer saludo con el formulario de creación de personaje debajo
- Escribe "Elara" en el campo de nombre
- Haz clic en el botón Mage — se resalta, y el botón inferior cambia a "Start Adventure"
- Escribe "Grew up in a wizard's tower and stumbled upon a portal to another world" en el cuadro de trasfondo
- Haz clic en Start Adventure
- El primer mensaje instantáneamente cambia a: "Elara pushes open the gate of destiny. You are a Mage..." — el formulario desaparece
- Envía un mensaje (por ejemplo, "I head toward the town") — la respuesta de la IA se dirige a ti como "Elara" y escribe interacciones basadas en la clase Mage
Verifica que la IA realmente obtuvo la información del personaje:
Tras enviar un mensaje, comprueba si la respuesta de la IA:
- Usa el nombre de tu personaje ("Elara" en lugar de "you" o "Traveler")
- Menciona detalles relevantes para la clase (Mage = magia, varas, hechizos, etc.)
- Si escribiste un trasfondo, la IA puede hacer referencia a él ("You recall your days in the wizard's tower...")
Si la IA no está usando esta información, comprueba la tabla de solución de problemas a continuación.
Solución de problemas
| Síntoma | Causa probable | Solución |
|---|---|---|
| No puedo ver el formulario de creación de personaje | El código del Root Component no se guardó o tiene un error de sintaxis | Comprueba el estado de compilación en la parte inferior de la sección Custom UI — debería mostrar un "OK" verde |
| Hacer clic en "Start Adventure" no hace nada | No se seleccionó una clase | El botón está en gris (disabled) cuando no se elige una clase — primero haz clic en una clase |
| Hice clic en el botón pero el saludo no cambió | Solo existe un saludo | Confirma que la pestaña First Message tiene 2 saludos (pestaña 1 y pestaña 2) |
El saludo cambió pero veo {{player_name}} como texto crudo | Los macros no se están sustituyendo | Comprueba que el ID de la variable esté correctamente escrito (player_name, no playerName) |
| La respuesta de la IA no usa el nombre del personaje | La entrada no está activa | Comprueba que la entrada de lore esté habilitada y que su contenido incluya {{player_name}} |
| La respuesta de la IA usa el predeterminado "Traveler" | Se llamó a setVariable después de switchGreeting | Confirma que el código llama a setVariable antes de switchGreeting |
| El formulario sigue mostrándose tras crear el personaje | La comprobación hasCreated es incorrecta | Confirma que player_class tiene una cadena vacía como valor predeterminado (no algún valor no vacío) |
Yendo más allá: ampliando la creación de personaje
Añadir más clases
Solo añade nuevos elementos al array classes:
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" },
{ id: "Cleric", label: "Cleric", icon: "✨", desc: "Healing and blessings, great support" },
{ id: "Ranger", label: "Ranger", icon: "🏹", desc: "Ranged attacks, expert tracker" },
];No se necesitan otros cambios de código — los botones aparecen automáticamente, y selectedClass será el id de la nueva clase cuando se seleccione.
Combinando con reglas de comportamiento
Al igual que en la Receta #1, puedes habilitar/deshabilitar automáticamente diferentes entradas de lore según la clase. Por ejemplo:
- Crea entradas "Warrior Lore", "Mage Lore" y "Rogue Lore" en el lorebook, deshabilitadas por defecto
- En la pestaña Behaviors, crea tres comportamientos que habiliten la entrada correspondiente cuando
player_classcoincida - Añade una llamada como
api.executeAction("choose-class-warrior")dentro dehandleStart
De esta forma cada clase no solo obtiene una etiqueta diferente — obtiene un worldbuilding y comportamiento de IA totalmente distintos.
Mostrar la información del personaje en los mensajes posteriores
Puedes añadir una "barra de información del personaje" al <Chat renderBubble> del Root Component que muestre el nombre y la clase del personaje en la parte superior de cada mensaje:
{/* En el return, arriba del contenido del mensaje */}
{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>
)}Referencia rápida
| Qué quieres | Cómo hacerlo |
|---|---|
| Almacenar texto ingresado por el jugador | Crea una variable de tipo string + api.setVariable("id", value) |
| Construir botones de selección | Sigue la selección en el estado de React + setSelectedClass(id) al hacer clic |
| Saltar a una apertura diferente tras enviar el formulario | Llama a setVariable para todos los valores primero, luego switchGreeting(index) |
| Hacer saber a la IA la información del personaje | Usa macros {{variableId}} en el contenido de la entrada — el motor los sustituye en el momento de construcción del prompt |
| Mostrar el formulario solo una vez | Comprueba una variable para hasCreated — el formulario desaparece tras la creación |
| Deshabilitar un botón hasta que se cumpla una condición | disabled={!condition} + estilos en gris coincidentes |
| Mostrar la información del personaje también en los saludos | Escribe {{player_name}} y otros macros directamente en el texto del saludo |
Pruébalo tú mismo — mundo demo importable
Descarga este JSON e impórtalo como un nuevo mundo para ver todo en acción:
Cómo importar:
- Ve a Yumina → My Worlds → Create New World
- En el editor, haz clic en More Actions → Import Package
- Selecciona el archivo
.jsondescargado - Se crea un nuevo mundo con todos los saludos, variables y Root Component preconfigurados
- Inicia una nueva sesión y pruébalo
Qué incluye:
- 2 saludos (formulario de creación de personaje + apertura de la historia)
- 3 variables (
player_namepara el nombre,player_classpara la clase,player_backstorypara el trasfondo) - 1 entrada de lore (perfil del personaje usando macros
{{player_name}},{{player_class}},{{player_backstory}}) - Un Root Component completo (UI del formulario de creación de personaje)
Esta es la Receta #4
La Receta #1 enseñó el cambio de saludos mediante botones y la sustitución de macros. Esta receta los combina en un flujo completo de creación de personaje. Las recetas futuras seguirán construyendo sobre esta base — asignación de puntos de atributo, selección de equipo, onboarding multietapa y más.
