Skip to content

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écnicaFuenteCómo la usa esta receta
switchGreeting(index) para saltar entre aperturasReceta #1 Parte 1Tras 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 entradaReceta #1 Parte 2Macros 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 respuesta

Punto 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

CampoValorPor qué
Nombre visibleNombre del personajePara tu propia referencia en el editor
IDplayer_nameEl macro {{player_name}} en las entradas busca este ID
TipoStringPorque un nombre es texto
Valor por defectoTravelerSi el jugador empieza sin rellenar un nombre, la IA lo llama "Traveler"
CategoríaCustomEtiqueta organizativa, puramente para gestión
Reglas de comportamientoNo 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

CampoValorPor qué
Nombre visibleClase del personajePara tu propia referencia
IDplayer_classEl macro {{player_class}} en las entradas busca este ID
TipoStringPorque la clase es texto ("Warrior", "Mage", "Rogue")
Valor por defectodéjalo vacíoVacío significa aún no elegido. El Root Component comprueba este valor para decidir qué botón resaltar
CategoríaCustomEtiqueta organizativa
Reglas de comportamientoNo 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

CampoValorPor qué
Nombre visibleTrasfondo del personajePara tu propia referencia
IDplayer_backstoryEl macro {{player_backstory}} en las entradas busca este ID
TipoStringPorque un trasfondo es texto
Valor por defectodéjalo vacíoVacío = el jugador no escribió un trasfondo. El lugar correspondiente en la entrada será una cadena vacía
CategoríaCustomEtiqueta organizativa
Reglas de comportamientoNo 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_name tiene 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

CampoValorPor qué
NombrePerfil del personaje del jugadorPara tu propia referencia
SecciónSystem PresetsLas entradas en la sección de presets siempre se envían a la IA
Habilitada (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 variable player_name (por ejemplo, "Elara")
  • {{player_class}} → sustituido por el valor actual de la variable player_class (por ejemplo, "Mage")
  • {{player_backstory}} → sustituido por el valor actual de la variable player_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 />):

tsx
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 saludos
  • name / selectedClass / backstory — tres estados de React que siguen el campo de entrada, los botones de clase y el área de texto
  • React.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 guardadas
  • hasCreated — comprueba si player_class es 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 (msg lo 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 degradado
  • selectedClass === cls.id — comprueba si esta es la clase actualmente seleccionada, usado para resaltar
  • disabled={!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 clase
  • api.setVariable("player_backstory", backstory.trim()) — almacena el trasfondo
  • api.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
  primero

Debes 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

  1. Haz clic en Save en la parte superior del editor
  2. Haz clic en Start Game o vuelve a la página de inicio e inicia una nueva sesión
  3. Ves el texto atmosférico del primer saludo con el formulario de creación de personaje debajo
  4. Escribe "Elara" en el campo de nombre
  5. Haz clic en el botón Mage — se resalta, y el botón inferior cambia a "Start Adventure"
  6. Escribe "Grew up in a wizard's tower and stumbled upon a portal to another world" en el cuadro de trasfondo
  7. Haz clic en Start Adventure
  8. El primer mensaje instantáneamente cambia a: "Elara pushes open the gate of destiny. You are a Mage..." — el formulario desaparece
  9. 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íntomaCausa probableSolución
No puedo ver el formulario de creación de personajeEl código del Root Component no se guardó o tiene un error de sintaxisComprueba 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 nadaNo se seleccionó una claseEl 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 saludoConfirma 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 crudoLos macros no se están sustituyendoComprueba que el ID de la variable esté correctamente escrito (player_name, no playerName)
La respuesta de la IA no usa el nombre del personajeLa entrada no está activaComprueba 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 switchGreetingConfirma que el código llama a setVariable antes de switchGreeting
El formulario sigue mostrándose tras crear el personajeLa comprobación hasCreated es incorrectaConfirma 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:

tsx
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:

  1. Crea entradas "Warrior Lore", "Mage Lore" y "Rogue Lore" en el lorebook, deshabilitadas por defecto
  2. En la pestaña Behaviors, crea tres comportamientos que habiliten la entrada correspondiente cuando player_class coincida
  3. Añade una llamada como api.executeAction("choose-class-warrior") dentro de handleStart

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:

tsx
{/* 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é quieresCómo hacerlo
Almacenar texto ingresado por el jugadorCrea una variable de tipo string + api.setVariable("id", value)
Construir botones de selecciónSigue la selección en el estado de React + setSelectedClass(id) al hacer clic
Saltar a una apertura diferente tras enviar el formularioLlama a setVariable para todos los valores primero, luego switchGreeting(index)
Hacer saber a la IA la información del personajeUsa 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 vezComprueba una variable para hasCreated — el formulario desaparece tras la creación
Deshabilitar un botón hasta que se cumpla una condicióndisabled={!condition} + estilos en gris coincidentes
Mostrar la información del personaje también en los saludosEscribe {{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:

recipe-4-demo.json

Cómo importar:

  1. Ve a Yumina → My WorldsCreate New World
  2. En el editor, haz clic en More ActionsImport Package
  3. Selecciona el archivo .json descargado
  4. Se crea un nuevo mundo con todos los saludos, variables y Root Component preconfigurados
  5. 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_name para el nombre, player_class para la clase, player_backstory para 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.