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-imagea 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:
current_bgse convierte en"classroom_morning.jpg"→ el Root Component cambiabackground-imagea un aulacurrent_speakerse convierte en"Yuki"→ el cuadro de diálogo muestra el nombre "Yuki"speaker_emotionse convierte en"happy"→ el Root Component ensambla la ruta del sprite a partir despeaker + emotiony lo renderiza- 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 = truePaso 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
| Campo | Valor | Por qué |
|---|---|---|
| Nombre | Fondo actual | Para tu propia referencia |
| ID | current_bg | La IA usa [current_bg: set "xxx"] para cambiar fondos |
| Tipo | String | El valor es una URL o nombre de archivo de imagen |
| Valor por defecto | default_bg.jpg | El fondo predeterminado cuando empieza una nueva sesión. Reemplaza con tu propia URL de imagen |
| Categoría | Custom | Categoría dedicada al sistema VN |
| Reglas de comportamiento | Usa [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
| Campo | Valor | Por qué |
|---|---|---|
| Nombre | Hablante actual | Para tu propia referencia |
| ID | current_speaker | La IA usa [current_speaker: set "name"] para cambiar hablantes |
| Tipo | String | El valor es un nombre de personaje |
| Valor por defecto | Narrator | Por defecto al modo narración — ningún personaje específico habla |
| Categoría | Custom | Categoría dedicada al sistema VN |
| Reglas de comportamiento | Usa [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
| Campo | Valor | Por qué |
|---|---|---|
| Nombre | Emoción del hablante | Para tu propia referencia |
| ID | speaker_emotion | La IA usa [speaker_emotion: set "happy"] para cambiar expresiones |
| Tipo | String | El valor es una palabra clave de emoción |
| Valor por defecto | neutral | Por defecto a una expresión neutral |
| Categoría | Custom | Categoría dedicada al sistema VN |
| Reglas de comportamiento | Usa [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
| Campo | Valor | Por qué |
|---|---|---|
| Nombre | Mostrar elecciones | Para tu propia referencia |
| ID | show_choices | La IA usa [show_choices: set true] para mostrar botones de elección |
| Tipo | Boolean | Solo dos estados: mostrar/ocultar |
| Valor por defecto | false | Los botones de elección están ocultos por defecto |
| Categoría | Custom | Categoría dedicada al sistema VN |
| Reglas de comportamiento | Usa [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
| Campo | Valor | Por qué |
|---|---|---|
| Nombre | Instrucciones del sistema de novela visual | Para tu propia referencia |
| Sección | Presets | Las entradas en la sección Presets se envían a la IA cada vez |
| Habilitada | Sí (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
- Abre el editor → barra lateral → pestaña Assets
- Arrastra y suelta tus archivos de imagen en el área de subida (o haz clic para navegar)
- Tras la subida, cada archivo obtiene una referencia
@asset:(como@asset:a1b2c3d4-e5f6-7890) - 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:xxxy se resolverán automáticamente.
Recursos recomendados a preparar
Fondos (proporción 16:9 recomendada, 1920×1080 o superior):
| Escena | Nombre de archivo sugerido | Propósito |
|---|---|---|
| Aula (día) | classroom_morning.jpg | Clase, escenas de conversación |
| Pasillo del colegio | hallway.jpg | Escenas de transición |
| Calle (atardecer) | street_evening.jpg | Escenas después de clase |
| Dormitorio (noche) | room_night.jpg | Escenas 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.
| Personaje | Nombres de archivo de ejemplo | Referencia 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 />):
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 limpio —
cleanContentfiltra 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 formatoA)) - Capa de fondo — usa
backgroundImagepara mostrar el fondo de escena actual.filter: brightness(0.7)lo oscurece ligeramente para mantener el texto en primer plano legible.transitionañade una animación crossfade al cambiar fondos - Capa de sprite — ensambla la ruta del archivo de sprite a partir de
speakeryemotion.onErrorgestiona 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
speakerno 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_choicesestruey el texto contiene elecciones en formatoA)B)C), aparecen botones centrados en la pantalla. Al hacer clic en un botón se ocultan automáticamente las elecciones (show_choicespuesto afalse) 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 schoolResultado 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:
| Formato | Reconocido como | Estilo de visualización | Propósito |
|---|---|---|---|
*This is italic text* | Narración | Gris (#94a3b8), cursiva | Descripciones de entorno, acciones de personaje, monólogo interno |
This is plain text | Diálogo | Blanco (#e2e8f0), vertical | Lo que el personaje dice |
A) Choice text | Elección | Botón | Selecció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
- 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
- Deberías ver una visualización VN a pantalla completa — fondo + cuadro de diálogo + narración de apertura
- Escribe un mensaje en el cuadro de entrada (por ejemplo, "Say hello to her")
- 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
- Si la IA ofrece elecciones, aparecen botones en el centro de la pantalla. Haz clic en uno para probarlo
- Continúa la conversación y observa si la IA actualiza naturalmente
current_bgal cambiar escenas, ycurrent_speakeryspeaker_emotioncuando los personajes hablan
Si algo sale mal:
| Síntoma | Causa probable | Solución |
|---|---|---|
| El fondo es negro | La URL de la imagen es incorrecta o la imagen no existe | Comprueba 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 sprite | La ruta del archivo de sprite no coincide | Comprueba 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 pantalla | El 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álogo | La IA no sigue las reglas de formato | Comprueba 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 aparecen | show_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 completa | El elemento raíz no llena el área visible | Añ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é quieres | Cómo hacerlo |
|---|---|
| Cambiar fondo | La IA envía [current_bg: set "imageURL"] |
| Cambiar hablante | La IA envía [current_speaker: set "characterName"] |
| Cambiar expresión | La IA envía [speaker_emotion: set "emotion"] |
| Mostrar botones de elección | La 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 completa | El 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 personajes | Prepara archivos characterName_emotion.png en el directorio /sprites/ |
| Enviar mensaje cuando el jugador hace clic en una elección | El 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:
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 todas las variables, entradas, comportamientos y el Root Component preconfigurados
- Inicia una nueva sesión y pruébalo
Qué incluye:
- 4 variables (
current_bgfondo,current_speakerhablante,speaker_emotionemoción,show_choicesinterruptor 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.
