Skip to content

Modo Novela Visual

Convierte la interfaz de chat en una novela visual completa — fondos de escena, sprites de personajes, cuadros de diálogo, botones de elección, todo impulsado por directivas de IA. El Root Component (index.tsx) toma el control de toda la pantalla: no más <Chat />, solo tu propio layout a pantalla completa con <MessageInput /> integrado donde el jugador necesite hablar. Tailwind y estilos en línea — no se requiere biblioteca de componentes prefabricados.


Lo que vas a construir

Una interfaz de novela visual a pantalla completa:

  • Fondos de escena — la IA cambia las imágenes de fondo mediante directivas (aula, calle, cielo nocturno...), y el Root Component las muestra como un background-image a pantalla completa
  • Sprites de personajes — la IA establece el hablante actual y la emoción mediante directivas, y el Root Component renderiza el <img> de sprite correspondiente en el centro de la pantalla
  • Cuadro de diálogo — un cuadro semitransparente en la parte inferior de la pantalla muestra el nombre del personaje y el diálogo. El texto en cursiva se trata automáticamente como narración/monólogo interno; el texto plano es diálogo de personaje
  • Botones de elección — cuando la IA ofrece opciones, el Root Component superpone botones clicables en pantalla
  • Modo pantalla completa — el Root Component devuelve su propio layout a pantalla completa sin anidar <Chat />, por lo que no hay burbujas de chat regulares en absoluto

Cómo funciona

La IA controla la pantalla con directivas en cada respuesta:

AI's response:
[current_bg: set "classroom_morning.jpg"]
[current_speaker: set "Yuki"]
[speaker_emotion: set "happy"]

*The classroom is bathed in morning light. Cherry blossom petals occasionally drift in through the window.*

Yuki se gira hacia ti con una sonrisa:

¡Buenos días! Hoy has venido temprano.

Después de que el motor analiza estas directivas:

  1. current_bg se convierte en "classroom_morning.jpg" → el Root Component cambia background-image a un aula
  2. current_speaker se convierte en "Yuki" → el cuadro de diálogo muestra el nombre "Yuki"
  3. speaker_emotion se convierte en "happy" → el Root Component ensambla la ruta del sprite a partir de speaker + emotion y lo renderiza
  4. El Root Component analiza el texto del último mensaje — las secciones en cursiva se renderizan como narración, el texto plano se renderiza como diálogo de personaje
Flujo de procesamiento del motor:
  Respuesta de la IA → el motor extrae directivas → actualiza variables → el Root Component lee variables y redibuja
    → capa de fondo: <div style={{ backgroundImage: ... }} />
    → capa de sprite: <img src={`/sprites/${speaker}_${emotion}.png`} />
    → capa de cuadro de diálogo: distingue narración (cursiva gris) vs. diálogo (vertical blanco)
    → capa de elección: aparecen botones cuando show_choices = true

Paso a paso

Paso 1: Crea las variables

Necesitas 4 variables para controlar la visualización de la novela visual.

Editor → barra lateral → pestaña Variables → haz clic en "Add Variable" para cada una

Variable 1: Fondo actual

CampoValorPor qué
NombreFondo actualPara tu propia referencia
IDcurrent_bgLa IA usa [current_bg: set "xxx"] para cambiar fondos
TipoStringEl valor es una URL o nombre de archivo de imagen
Valor por defectodefault_bg.jpgEl fondo predeterminado cuando empieza una nueva sesión. Reemplaza con tu propia URL de imagen
CategoríaCustomCategoría dedicada al sistema VN
Reglas de comportamientoUsa [current_bg: set "imageURL"] para cambiar el fondo de la escena. Actualiza esta variable cada vez que cambie la escena.Le dice a la IA cuándo y cómo cambiar esta variable

Variable 2: Hablante actual

CampoValorPor qué
NombreHablante actualPara tu propia referencia
IDcurrent_speakerLa IA usa [current_speaker: set "name"] para cambiar hablantes
TipoStringEl valor es un nombre de personaje
Valor por defectoNarratorPor defecto al modo narración — ningún personaje específico habla
CategoríaCustomCategoría dedicada al sistema VN
Reglas de comportamientoUsa [current_speaker: set "characterName"] para establecer el hablante actual. Ponlo en "Narrator" para narración o monólogo interno.Le dice a la IA las reglas de uso

Variable 3: Emoción del hablante

CampoValorPor qué
NombreEmoción del hablantePara tu propia referencia
IDspeaker_emotionLa IA usa [speaker_emotion: set "happy"] para cambiar expresiones
TipoStringEl valor es una palabra clave de emoción
Valor por defectoneutralPor defecto a una expresión neutral
CategoríaCustomCategoría dedicada al sistema VN
Reglas de comportamientoUsa [speaker_emotion: set "emotion"] para cambiar la expresión del personaje. Emociones disponibles: neutral, happy, sad, angry, surprised, shy. Actualízala cada vez que cambie la emoción del personaje.Listar emociones disponibles evita que la IA invente expresiones inexistentes

Variable 4: Mostrar elecciones

CampoValorPor qué
NombreMostrar eleccionesPara tu propia referencia
IDshow_choicesLa IA usa [show_choices: set true] para mostrar botones de elección
TipoBooleanSolo dos estados: mostrar/ocultar
Valor por defectofalseLos botones de elección están ocultos por defecto
CategoríaCustomCategoría dedicada al sistema VN
Reglas de comportamientoUsa [show_choices: set true] cuando quieras ofrecer una elección al jugador. Manténlo en false en caso contrario.Le dice a la IA que solo habilite esto cuando se necesite una elección del jugador

¿Por qué dejar que la IA controle la pantalla con directivas?

Este es el diseño central de Yumina — la IA no ejecuta código. En su lugar, usa directivas estructuradas para decirle al motor qué hacer. El motor analiza las directivas, actualiza las variables, y el Root Component lee las variables para actualizar la pantalla. La cadena completa es: la IA escribe directivas → el motor analiza → las variables se actualizan → el Root Component re-renderiza.


Paso 2: Crea una entrada de lorebook — instrucciones del sistema VN

La IA necesita saber que está en un entorno de novela visual y cómo usar directivas para controlar la pantalla.

Editor → pestaña Lorebook → crea una nueva entrada

CampoValorPor qué
NombreInstrucciones del sistema de novela visualPara tu propia referencia
SecciónPresetsLas entradas en la sección Presets se envían a la IA cada vez
Habilitada (activado)Siempre activa

Contenido:

[Modo Novela Visual]
Estás generando contenido para un motor de novela visual. Cada respuesta debe incluir directivas para controlar la pantalla.

Reglas de formato:
1. Establece la escena con directivas al inicio de tu respuesta:
   [current_bg: set "backgroundImageURL"]
   [current_speaker: set "characterName"]
   [speaker_emotion: set "emotion"]

2. Formato del texto:
   - *Texto en cursiva* = narración o monólogo interno. Úsalo para describir entornos, acciones del personaje, pensamientos internos.
   - Texto plano (sin formato) = diálogo/voz del personaje.
   - No envuelvas el diálogo entre comillas — solo escribe texto plano.

3. Cuando quieras darle una opción al jugador:
   - Usa [show_choices: set true]
   - Lista las opciones al final del texto en este formato:
     A) Texto de la opción
     B) Texto de la opción
     C) Texto de la opción

4. Cada respuesta debe contener solo un fragmento de escena (3-5 oraciones). Mantén el ritmo ajustado, como una novela visual real.

5. Emociones disponibles: neutral, happy, sad, angry, surprised, shy

6. Actualiza siempre current_bg al cambiar de escena. Actualiza siempre current_speaker y speaker_emotion cuando un personaje hable.

¿Por qué tan detallado? Porque la IA no sabe cómo funciona tu Root Component. Tienes que decirle explícitamente "cursiva = narración, texto plano = diálogo" — de lo contrario, la IA podría usar formato aleatorio, y el componente no podrá distinguir narración de diálogo correctamente.


Paso 3: Prepara y sube recursos

Una novela visual necesita imágenes de fondo y sprites de personajes. Dos formas de proporcionarlos:

  • Opción A (recomendada): Sube al sistema de recursos de Yumina, obtén referencias @asset: — estables, no expiran
  • Opción B: Usa URLs de imágenes externas (imgur, tu propio servidor) — más simple pero puede fallar

Subir recursos a Yumina

  1. Abre el editor → barra lateral → pestaña Assets
  2. Arrastra y suelta tus archivos de imagen en el área de subida (o haz clic para navegar)
  3. Tras la subida, cada archivo obtiene una referencia @asset: (como @asset:a1b2c3d4-e5f6-7890)
  4. Haz clic en un recurso subido para copiar su referencia

¿Qué es una referencia @asset:? Es el identificador interno de recursos de Yumina. En el código TSX del Root Component, <img src="@asset:xxx" /> se resuelve automáticamente a una URL de CDN real en tiempo de renderizado. No necesitas convertirla manualmente — el componente lo gestiona. Las variables también pueden almacenar valores @asset:xxx y se resolverán automáticamente.

Recursos recomendados a preparar

Fondos (proporción 16:9 recomendada, 1920×1080 o superior):

EscenaNombre de archivo sugeridoPropósito
Aula (día)classroom_morning.jpgClase, escenas de conversación
Pasillo del colegiohallway.jpgEscenas de transición
Calle (atardecer)street_evening.jpgEscenas después de clase
Dormitorio (noche)room_night.jpgEscenas nocturnas

Tras subir, anota la referencia @asset: de cada fondo. Las pondrás en una entrada del lorebook para que la IA sepa qué referencia corresponde a qué escena.

Sprites de personajes (PNG transparente recomendado, 1000px+ de altura):

Prepara varios sprites de expresiones por personaje. Usa un formato de nombre consistente: characterName_emotion.png.

PersonajeNombres de archivo de ejemploReferencia de ejemplo
Yuki (feliz)yuki_happy.png@asset:abc123...
Yuki (triste)yuki_sad.png@asset:def456...
Profesor (neutral)teacher_neutral.png@asset:ghi789...

Decir a la IA qué recursos usar

Tras subir, añade una tabla de referencia de recursos a la entrada de instrucciones del sistema VN que creaste en el Paso 2. Esto le dice a la IA qué referencia @asset: corresponde a qué escena o personaje:

[Asset Reference Table]
Backgrounds:
- Classroom daytime: @asset:your-classroom-reference
- School hallway: @asset:your-hallway-reference
- Street evening: @asset:your-street-reference

Character sprites (format: @asset:reference):
- Yuki happy: @asset:your-yuki-happy-reference
- Yuki sad: @asset:your-yuki-sad-reference
- Teacher neutral: @asset:your-teacher-reference

When using directives, use the @asset: references above as values. For example:
[current_bg: set "@asset:your-classroom-reference"]

La IA lee esta tabla y usa las referencias @asset: correctas en sus directivas. El Root Component convierte automáticamente @asset: a URLs de imagen reales al mostrar.

¿Aún no tienes recursos? Todavía puedes probar

El Root Component muestra un fondo de color sólido cuando las imágenes fallan al cargar. Haz que la lógica funcione primero — añade recursos después. También puedes usar URLs de imágenes de stock gratuitas en lugar de referencias @asset: para prototipos rápidos.


Paso 4: Escribe el primer mensaje

El primer mensaje es la apertura de la novela visual. Necesita directivas para configurar la pantalla inicial.

Editor → pestaña First Message → crea un primer mensaje

[current_bg: set "classroom_morning.jpg"]
[current_speaker: set "Narrator"]
[speaker_emotion: set "neutral"]

*The first day of April. The tail end of cherry blossom season.*

*You push open the classroom door. The familiar smell of chalk dust and wood hits you. Most seats are still empty — ten minutes until class starts.*

*In the seat by the window, a girl you've never seen before is quietly gazing outside.*

[current_speaker: set "Narrator"]
*A transfer student? You don't remember anyone like her in your class.*

¿Por qué poner directivas también en el primer mensaje? Porque el Root Component depende de las variables para decidir qué mostrar. Las directivas del primer mensaje son analizadas por el motor, configurando el fondo inicial y el estado del personaje. Sin directivas, los valores predeterminados entran en juego (default_bg.jpg + Narrator + neutral), pero la pantalla puede no coincidir con la escena de apertura.


Paso 5: Escribe el Root Component de la novela visual

Este es el paso central. El Root Component toma el control de toda la pantalla — en lugar de anidar <Chat />, pinta su propio fondo, sprites, cuadro de diálogo y botones de elección. La entrada del jugador la maneja <MessageInput /> colocado en la parte inferior de la pantalla.

Editor → sección Custom UI → abre index.tsx → pega lo siguiente (reemplaza el predeterminado return <Chat />):

tsx
export default function MyWorld() {
  const api = useYumina();
  const renderMarkdown = api.renderMarkdown;
  const msgs = api.messages || [];
  const lastMsg = msgs[msgs.length - 1];
  const content = lastMsg ? String(lastMsg.content || "") : "";

  // ---- Lee las variables ----
  const bgUrl = String(api.variables.current_bg || "default_bg.jpg");
  const speaker = String(api.variables.current_speaker || "Narrator");
  const emotion = String(api.variables.speaker_emotion || "neutral");
  const showChoices = Boolean(api.variables.show_choices);

  // ---- Limpia el contenido: elimina las líneas de directiva, deja solo el texto narrativo ----
  const cleanContent = content
    .split("\n")
    .filter((line) => !line.trim().match(/^\[.+:\s*(set|add|subtract|multiply|toggle|append|merge|push|delete)\s+.+\]$/) && !line.trim().match(/^\[.+:\s*[+-]?\d+\]$/))
    .join("\n")
    .trim();

  // ---- Analiza el texto: distingue narración (cursiva) de diálogo (texto plano) ----
  // Divide el texto en párrafos y clasifica cada uno
  const paragraphs = cleanContent
    .split("\n\n")
    .map((p) => p.trim())
    .filter((p) => p.length > 0);

  const parsed = paragraphs.map((p) => {
    // Si el párrafo entero está envuelto en *, o cada línea empieza con *, es narración
    const isNarration = /^\*[^*].*[^*]\*$/.test(p.trim())
      || p.trim().startsWith("*");
    // Comprueba si es una línea de elección (formato A) B) C))
    const isChoice = /^[A-Z]\)\s/.test(p.trim());
    return { text: p, isNarration, isChoice };
  });

  // ---- URL del sprite (ensamblada a partir del nombre del personaje y la emoción) ----
  const spriteUrl = speaker !== "Narrator"
    ? `/sprites/${speaker.toLowerCase()}_${emotion}.png`
    : null;

  // ---- Extrae las elecciones ----
  const choices = parsed
    .filter((p) => p.isChoice)
    .map((p) => p.text.replace(/^[A-Z]\)\s*/, ""));

  // ---- Render ----
  return (
    <div style={{
      position: "relative",
      width: "100%",
      minHeight: "500px",
      borderRadius: "12px",
      overflow: "hidden",
      background: "#000",
    }}>
      {/* ===== Capa de fondo ===== */}
      <div style={{
        position: "absolute",
        inset: 0,
        backgroundImage: `url(${bgUrl})`,
        backgroundSize: "cover",
        backgroundPosition: "center",
        filter: "brightness(0.7)",
        transition: "background-image 0.8s ease",
      }} />

      {/* ===== Capa de sprite del personaje ===== */}
      {spriteUrl && (
        <div style={{
          position: "absolute",
          bottom: "120px",
          left: "50%",
          transform: "translateX(-50%)",
          zIndex: 2,
          transition: "opacity 0.5s ease",
        }}>
          <img
            src={spriteUrl}
            alt={`${speaker} - ${emotion}`}
            style={{
              maxHeight: "350px",
              objectFit: "contain",
              filter: "drop-shadow(0 4px 12px rgba(0,0,0,0.5))",
            }}
            onError={(e) => { e.target.style.display = "none"; }}
          />
        </div>
      )}

      {/* ===== Capa del cuadro de diálogo ===== */}
      <div style={{
        position: "absolute",
        bottom: 0,
        left: 0,
        right: 0,
        zIndex: 3,
        background: "linear-gradient(transparent, rgba(0,0,0,0.85) 30%)",
        padding: "60px 24px 24px",
      }}>
        {/* Etiqueta con el nombre del personaje */}
        {speaker !== "Narrator" && (
          <div style={{
            display: "inline-block",
            padding: "4px 16px",
            marginBottom: "8px",
            background: "rgba(99,102,241,0.8)",
            borderRadius: "6px 6px 0 0",
            color: "#e0e7ff",
            fontSize: "14px",
            fontWeight: "bold",
            letterSpacing: "0.05em",
          }}>
            {speaker}
          </div>
        )}

        {/* Contenido del texto */}
        <div style={{
          background: "rgba(15,23,42,0.9)",
          borderRadius: speaker !== "Narrator" ? "0 12px 12px 12px" : "12px",
          padding: "16px 20px",
          border: "1px solid rgba(148,163,184,0.2)",
          minHeight: "80px",
        }}>
          {parsed
            .filter((p) => !p.isChoice)
            .map((p, i) => (
              <p key={i} style={{
                margin: i > 0 ? "10px 0 0" : "0",
                color: p.isNarration ? "#94a3b8" : "#e2e8f0",
                fontStyle: p.isNarration ? "italic" : "normal",
                fontSize: "15px",
                lineHeight: 1.8,
              }}
              dangerouslySetInnerHTML={{
                __html: renderMarkdown(
                  p.isNarration
                    ? p.text.replace(/^\*|\*$/g, "")
                    : p.text
                ),
              }}
              />
            ))
          }
        </div>
      </div>

      {/* ===== Capa de botones de elección ===== */}
      {showChoices && choices.length > 0 && (
        <div style={{
          position: "absolute",
          top: "50%",
          left: "50%",
          transform: "translate(-50%, -50%)",
          zIndex: 4,
          display: "flex",
          flexDirection: "column",
          gap: "10px",
          width: "80%",
          maxWidth: "400px",
        }}>
          {choices.map((choice, i) => (
            <button
              key={i}
              onClick={() => {
                api.setVariable("show_choices", false);
                api.sendMessage(choice);
              }}
              style={{
                padding: "14px 20px",
                background: "rgba(30,27,75,0.9)",
                border: "1px solid rgba(99,102,241,0.6)",
                borderRadius: "10px",
                color: "#c7d2fe",
                fontSize: "15px",
                fontWeight: "600",
                cursor: "pointer",
                textAlign: "left",
                transition: "all 0.2s ease",
                backdropFilter: "blur(8px)",
              }}
              onMouseEnter={(e) => {
                e.target.style.background = "rgba(67,56,202,0.8)";
                e.target.style.borderColor = "#818cf8";
              }}
              onMouseLeave={(e) => {
                e.target.style.background = "rgba(30,27,75,0.9)";
                e.target.style.borderColor = "rgba(99,102,241,0.6)";
              }}
            >
              {choice}
            </button>
          ))}
        </div>
      )}

      {/* ===== Capa de entrada del jugador ===== */}
      {/* Sin envoltorio <Chat /> — inserta <MessageInput /> directamente para que el jugador aún pueda escribir */}
      <div style={{
        position: "absolute",
        bottom: 0,
        left: 0,
        right: 0,
        zIndex: 5,
      }}>
        <MessageInput />
      </div>
    </div>
  );
}

Explicación bloque por bloque:

  • Contenido limpiocleanContent filtra líneas de directivas como [current_bg: set "xxx"] (coincidiendo con todos los tipos de operación: set/add/subtract/multiply/toggle/append/merge/push/delete, más directivas abreviadas como [hp: -10]). Las directivas ya las analizó el motor, así que el componente no necesita mostrarlas
  • Analizar párrafos — divide el texto en líneas en blanco en párrafos, clasificando cada uno como narración (empieza con *), diálogo (texto plano), o una elección (empieza con formato A))
  • Capa de fondo — usa backgroundImage para mostrar el fondo de escena actual. filter: brightness(0.7) lo oscurece ligeramente para mantener el texto en primer plano legible. transition añade una animación crossfade al cambiar fondos
  • Capa de sprite — ensambla la ruta del archivo de sprite a partir de speaker y emotion. onError gestiona imágenes faltantes (las oculta silenciosamente). No se muestra sprite en modo Narrator
  • Capa de cuadro de diálogo — un cuadro semitransparente en la parte inferior. Cuando speaker no es "Narrator", aparece una etiqueta con el nombre del personaje encima del cuadro de diálogo. El texto de narración es gris y cursiva; el texto de diálogo es blanco y vertical
  • Capa de botón de elección — cuando show_choices es true y el texto contiene elecciones en formato A) B) C), aparecen botones centrados en la pantalla. Al hacer clic en un botón se ocultan automáticamente las elecciones (show_choices puesto a false) y se envía la selección del jugador

Personalizando rutas de sprites

El código usa /sprites/${speaker.toLowerCase()}_${emotion}.png para ensamblar las rutas de sprites. Puedes cambiar esto a cualquier formato de URL — enlaces CDN, rutas de archivo locales, o una tabla de búsqueda. Si los nombres de tus personajes contienen caracteres no ASCII, recuerda codificarlos en URL o usar IDs en inglés.


Paso 6: No se necesita alternar — el Root Component ya es de pantalla completa

Una novela visual naturalmente llena la pantalla. Mientras tu elemento raíz index.tsx use width: "100%" + minHeight: "500px" (o 100vh) — lo cual el código anterior ya hace — tu TSX toma el control de todo el lienzo. No hay un interruptor separado de "pantalla completa" que activar, porque el Root Component es el punto de entrada de la UI del mundo.

Dos patrones lado a lado:

  • Chat normal + burbuja personalizada: return <Chat renderBubble={...} /> — mantén el shell de chat de la plataforma, solo restiliza las burbujas
  • Novela visual pura (esta receta): return <div>...todos los elementos VN...<MessageInput /></div> — sin <Chat /> en absoluto; cada píxel es tuyo

¿Quieres alternar entre chat y VN? Si quieres la experiencia de "chat normal la mayor parte del tiempo, VN durante momentos especiales", ramifica dentro del Root Component en una variable (por ejemplo, vn_mode). Devuelve <Chat /> cuando esté apagado y el layout VN a pantalla completa cuando esté encendido. La IA cambia la variable con una directiva y tú alternas entre modos a mitad de sesión.


Paso 7: Cómo la IA controla la pantalla — ejemplos de directivas

Veamos cómo la IA controla naturalmente la pantalla de novela visual durante una conversación real.

Escena 1: Apertura (modo Narrator)

Respuesta de la IA:

[current_bg: set "classroom_morning.jpg"]
[current_speaker: set "Narrator"]
[speaker_emotion: set "neutral"]

*An April morning. The air carries the sweet scent of cherry blossoms.*

*You walk into the classroom and find a girl you don't recognize sitting by the window. She's resting her chin on her hand, staring outside, lost in thought.*

Resultado renderizado: fondo de aula + sin sprite + texto de narración en cursiva gris.

Escena 2: Diálogo de personaje

Respuesta de la IA:

[current_speaker: set "Yuki"]
[speaker_emotion: set "surprised"]

*She seems to notice you looking and turns her head.*

Oh, hello. Are you in this class too?

[speaker_emotion: set "shy"]

Sorry, I just transferred here today... I don't really know anyone yet.

Resultado renderizado: fondo sin cambios (sin directiva current_bg significa que mantiene el valor anterior) + el sprite de Yuki muestra expresión sorprendida y luego cambia a tímida + el cuadro de diálogo muestra el nombre "Yuki" + alternan narración en cursiva y diálogo vertical.

Escena 3: Dando al jugador una elección

Respuesta de la IA:

[current_speaker: set "Narrator"]
[show_choices: set true]

*Yuki looks at you, a hint of expectation in her eyes.*

*What do you do?*

A) Introduce yourself and start a conversation
B) Nod briefly and head back to your seat
C) Offer to show her around the classroom and school

Resultado renderizado: texto de narración + tres botones clicables aparecen en el centro de la pantalla. Cuando el jugador hace clic en uno, los botones desaparecen y el texto seleccionado se envía como respuesta del jugador a la IA.

Escena 4: Transición de escena

Respuesta de la IA:

[current_bg: set "hallway.jpg"]
[current_speaker: set "Narrator"]

*The bell rings. The hallway instantly comes alive as students stream out in pairs and small groups.*

[current_speaker: set "Yuki"]
[speaker_emotion: set "happy"]

¿Quieres almorzar juntos en la azotea? Encontré un lugar realmente lindo.

Resultado renderizado: el fondo transiciona al pasillo (con una animación crossfade) + narración + sprite feliz de Yuki + diálogo.


Paso 8: Narración en cursiva vs. diálogo plano — reglas de parsing

El Root Component distingue dos tipos de texto con una regla simple:

FormatoReconocido comoEstilo de visualizaciónPropósito
*This is italic text*NarraciónGris (#94a3b8), cursivaDescripciones de entorno, acciones de personaje, monólogo interno
This is plain textDiálogoBlanco (#e2e8f0), verticalLo que el personaje dice
A) Choice textElecciónBotónSelección clicable del jugador

A la IA ya se le dijeron estas reglas en la entrada del lorebook. Pero si la IA ocasionalmente equivoca el formato (por ejemplo, usa cursiva para el diálogo), la lógica de fallback trata el texto incierto como diálogo — así que al menos nada se rompe.

¿Por qué no usar las citas > de Markdown o **negrita** para distinguir? Porque *italic* es el marcado más natural — la mayoría de los modelos de IA en escenarios de roleplay ya usan por defecto cursiva para narración y descripciones de acción sin entrenamiento extra. Elige un formato que la IA tenga más probabilidades de seguir consistentemente, y ahórrate el dolor de cabeza de luchar con el modelo.


Paso 9: 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. Deberías ver una visualización VN a pantalla completa — fondo + cuadro de diálogo + narración de apertura
  4. Escribe un mensaje en el cuadro de entrada (por ejemplo, "Say hello to her")
  5. La respuesta de la IA debería incluir directivas — el fondo podría cambiar, aparece un personaje y el diálogo se muestra en el cuadro
  6. Si la IA ofrece elecciones, aparecen botones en el centro de la pantalla. Haz clic en uno para probarlo
  7. Continúa la conversación y observa si la IA actualiza naturalmente current_bg al cambiar escenas, y current_speaker y speaker_emotion cuando los personajes hablan

Si algo sale mal:

SíntomaCausa probableSolución
El fondo es negroLa URL de la imagen es incorrecta o la imagen no existeComprueba que el valor de current_bg sea una URL de imagen válida. Intenta abrir la URL directamente en un navegador para confirmar que la imagen carga
No se ve spriteLa ruta del archivo de sprite no coincideComprueba que la ruta /sprites/characterName_emotion.png sea correcta. onError oculta silenciosamente las imágenes que fallan al cargar
Las líneas de directivas se muestran en pantallaEl formato de directiva no es estándar y la regex no coincidióConfirma que el formato sea [variableName: set "value"] — nota el espacio tras los dos puntos
Todo el texto es narración / todo el texto es diálogoLa IA no sigue las reglas de formatoComprueba que las instrucciones de formato de la entrada del lorebook sean claras. Puedes reforzarlas en las reglas de comportamiento
Los botones de elección no aparecenshow_choices no se estableció a true, o no hay elecciones en formato A)Comprueba que la respuesta de la IA incluya [show_choices: set true] y elecciones en formato A)
La pantalla no es de pantalla completaEl elemento raíz no llena el área visibleAñade minHeight: "100vh" o height: "100%" al div más externo del Root Component, y asegúrate de que el contenedor padre tenga altura

Consejos avanzados

Diálogo multipersonaje

Puedes cambiar entre múltiples personajes en la misma respuesta:

[current_speaker: set "Yuki"]
[speaker_emotion: set "happy"]
The weather is so nice today!

[current_speaker: set "Teacher"]
[speaker_emotion: set "neutral"]
Alright everyone, class is starting. Please take your seats.

[current_speaker: set "Narrator"]
*The classroom falls silent in an instant.*

El Root Component procesa estos en orden; la pantalla final muestra el sprite del último current_speaker. Si quieres que cada segmento de diálogo muestre el sprite del personaje correspondiente, puedes modificar el componente para analizar la directiva [current_speaker: set ...] más cercana precedente para cada párrafo.

Efectos de transición

El CSS de la capa de fondo incluye transition: background-image 0.8s ease, dándole a los cambios de fondo un efecto crossfade. También puedes usar diferentes transiciones para diferentes tipos de escena:

  • Cambio normal: crossfade (ya implementado)
  • Flashback/recuerdo: añade un overlay de destello blanco
  • Escena tensa: añade una animación de sacudida de pantalla

Combinando con sonido y BGM

Combinado con el sistema de audio de la Receta #9 (ciclo día-noche), puedes asignar BGM a diferentes escenas. Añade a tus reglas de comportamiento: cuando current_bg cambia, reproduce la BGM de la escena correspondiente.


Referencia rápida

Qué quieresCómo hacerlo
Cambiar fondoLa IA envía [current_bg: set "imageURL"]
Cambiar hablanteLa IA envía [current_speaker: set "characterName"]
Cambiar expresiónLa IA envía [speaker_emotion: set "emotion"]
Mostrar botones de elecciónLa IA envía [show_choices: set true] + elecciones en formato A) B) C)
Distinguir narración de diálogo*italic* = narración, texto plano = diálogo
Experiencia VN a pantalla completaEl Root Component index.tsx devuelve directamente el layout VN a pantalla completa (sin <Chat />), con <MessageInput /> en la parte inferior para la entrada del jugador
Sprites de personajesPrepara archivos characterName_emotion.png en el directorio /sprites/
Enviar mensaje cuando el jugador hace clic en una elecciónEl onClick del botón llama a api.sendMessage(choiceText)

Pruébalo tú mismo — mundo demo importable

Descarga este archivo JSON e impórtalo para experimentar el efecto completo:

recipe-10-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 todas las variables, entradas, comportamientos y el Root Component preconfigurados
  5. Inicia una nueva sesión y pruébalo

Qué incluye:

  • 4 variables (current_bg fondo, current_speaker hablante, speaker_emotion emoción, show_choices interruptor de elección)
  • 1 entrada de lorebook (instrucciones del sistema VN diciéndole a la IA cómo usar directivas y formato de texto)
  • 1 primer mensaje (apertura VN con directivas iniciales)
  • Un Root Component (interfaz VN completa: fondo + sprites + cuadro de diálogo + botones de elección + <MessageInput />)

Esta es la Receta #10

El modo novela visual muestra a Yumina en su forma más poderosa — la IA no es solo un compañero de chat, es un motor narrativo. Al impulsar la pantalla con directivas, usar convenciones de formato para distinguir tipos de texto, y dejar que el Root Component remodele toda la interfaz, puedes convertir un cuadro de chat ordinario en cualquier experiencia interactiva que puedas imaginar. El mismo enfoque funciona para juegos de aventura, cómics interactivos, o incluso simuladores de gestión.