beasty-visual-novel / scripting / controllers.md

Controladores

Los tres MonoBehaviours que llamas desde código: BeastyManager (el rig), VNGameController (el estado de la aplicación y los guardados), VisualNovelController (una historia en ejecución). Todos en Beasty.VN.Runtime.

A cuál llamo

TareaLlama a
Alcanzar cualquier managerBeastyManager.Instance
Retrasar el juego hasta que mi sistema esté listoBeastyManager.RegisterBootBarrier
Moverse entre estados de la aplicaciónVNGameController
Guardar o cargar una ranura orientada al jugadorVNGameController
Leer el estado del juego fuera de una historiaVNGameController.SharedVariables
Iniciar, restaurar o detener una historiaVisualNovelController
Avanzar, retroceder, leer variables dentro de una historiala API estática VN

Regla general: VNGameController decide QUÉ se está ejecutando, VisualNovelController lo ejecuta, VN habla con ello.

BeastyManager

El único objeto en la jerarquía. Posee a todos los demás managers como subcomponentes ocultos en su propio GameObject, así que hay exactamente uno de cada uno y nada que conectar manualmente.

public static BeastyManager Instance { get; }
public event Action Ready;
public bool IsReady { get; }
public void MarkReady();
public void RegisterBootBarrier(Func<bool> isDone);
public void EnsureManagers();

Accesores de managers, todos re-resueltos en tiempo de ejecución si una referencia se pierde:

public VNGameController        Game            { get; }
public VisualNovelController   VN              { get; }
public StageController         Stage           { get; }
public FreeRoamController      FreeRoam        { get; }
public FreeRoamScreenController FreeRoamScreens { get; }
public VNAudioManager          Audio           { get; }
public VNBackgroundMusicController BackgroundMusic { get; }
public VNMenuManager           Menus           { get; }

Más las piezas de escena y la configuración que posee: StageRoot, MainCanvas, MainCamera, Loading, InputConfig, TimeConfig.

Nota BeastyManager.VN es el componente VisualNovelController. La API estática VN es un tipo distinto en el mismo namespace. Dentro de una clase que usa ambos, califica la estática como Beasty.VN.Runtime.VN.

Barreras de arranque (boot barriers)

Al arrancar, BeastyManager muestra la pantalla de carga, espera un frame para que cada subsistema haya despertado, y luego mantiene el overlay arriba hasta que cada barrera registrada devuelve true — o hasta que transcurre maxBootSeconds, lo que ocurra primero. Luego oculta el overlay, establece IsReady y lanza Ready.

Una barrera de arranque es cómo pones tu propio arranque lento dentro de esa ventana: una consulta remota de configuración, un login, una migración de guardado, un calentamiento de tu propio sistema de assets. Sin una, el jugador ve tus sistemas aparecer de golpe después de que el juego ya ha comenzado. Regístrala en AwakeStart en el manager corre después de un frame y deja de aceptar barreras una vez que el arranque ha terminado.

using System.Threading.Tasks;
using Beasty.VN.Runtime;
using UnityEngine;

public sealed class RemoteConfig : MonoBehaviour
{
    private bool _loaded;

    private void Awake()
    {
        BeastyManager.Instance?.RegisterBootBarrier(() => _loaded);
        _ = FetchAsync();
    }

    private async Task FetchAsync()
    {
        await Task.Delay(1500);   // tu solicitud real
        _loaded = true;           // la pantalla de carga ya puede desaparecer
    }
}

Limita la espera de forma realista. Una barrera que nunca devuelve true solo retrasa el juego en maxBootSeconds; no lo cuelga.

VNGameController

Posee el estado de la aplicación y el almacén de variables compartido. Una instancia: VNGameController.Instance.

public static VNGameController Instance { get; }
public VNAppState State { get; }
public event Action<VNAppState> StateChanged;
public event Action<FreeRoamSaveState> EnteredFreeRoam;
public VariableStore SharedVariables { get; }
public VNTimeConfig TimeConfig { get; }
public VNSaveManager Saves { get; }

SharedVariables ES el estado del juego: tus variables, variables de personaje, tiempo, misiones, inventario, overrides del diccionario. Se transporta a través de todos los modos, por eso el progreso sobrevive a un cambio de proyecto y por eso todo dentro de él se guarda y se rebobina gratis.

Moverse entre modos

public void GoToMainMenu();
public void StartNewGame();
public void EnterVisualNovel(string nodeId = null);
public void EnterVisualNovel(DialogueScene project, string nodeId = null);
public void PlayVisualNovelThenReturn(DialogueScene project, string nodeId,
                                      FreeRoamSaveState freeRoamReturn, Sprite roomBackground = null);
public bool PresentTalkMenu(string characterId);
public bool PresentTalkMenuThenReturn(string characterId, FreeRoamSaveState freeRoamReturn,
                                      Sprite roomBackground = null);
public void EnterFreeRoam(FreeRoamSaveState position = null);
public void EnterCustom();
  • StartNewGame limpia la partida, reinicia el escenario persistente y entra al proyecto anfitrión de la VN.
  • EnterVisualNovel(project, nodeId) cambia de proyecto conservando el almacén compartido, así que el progreso se traslada.
  • PlayVisualNovelThenReturn es el puente con FreeRoam: reproduce una escena, y luego vuelve a la sala que pasaste. El roomBackground opcional siembra el fondo, así que un diálogo sin su propio bloque Backdrop muestra la sala en la que el jugador está parado.
  • PresentTalkMenu devuelve false cuando el menú del personaje se resuelve sin entradas visibles. Maneja eso — no asumas que se abrió.
  • EnterFreeRoam(null) entra en la sala de entrada del mapa.
  • EnterCustom entrega el juego a tu código. Ver Modo personalizado.

Retroceder a través de un límite de modo:

public bool RollbackFromFreeRoam();
public bool RollbackFromCustom();
public void PushFreeRoamRoom(FreeRoamSaveState state);
public void PushCustomRollback();

Ambos métodos Rollback* devuelven false cuando no hay nada a lo que retroceder.

Guardado

public bool SaveToSlot(string slot, string saveName = null, Texture2D thumbnail = null);
public bool LoadSlot(string slot);
public VNSlotLoadOutcome LoadSlotDetailed(string slot);
public bool RestoreSlotBackup(string slot);
public Task<bool> SaveToSlotAsync(string slot, string saveName = null, Texture2D thumbnail = null);
public Task<VNSlotLoadOutcome> LoadSlotDetailedAsync(string slot);
public VisualNovelSaveData CaptureCurrent();
public bool RestoreFrom(VisualNovelSaveData data);

Las ranuras (slots) están nombradas: manual_0, manual_1, …, auto_0, … LoadSlotDetailed devuelve VNSlotLoadOutcome, uno de Loaded, Failed o FailedBackupAvailable — el tercero es lo que le permite a una pantalla de guardado ofrecer “restaurar el backup” en vez de simplemente fallar. RestoreSlotBackup sobrescribe una ranura dañada con su respaldo .bak.

SaveToSlot funciona desde CUALQUIER estado: etiqueta la instantánea con el VNAppState activo y escribe el almacén compartido, la cola de rollback entre modos, el estado de escena de tus componentes BeastySaveable y tu customStateJson. Devuelve false cuando el momento no se puede capturar — un menú de conversación abierto es uno de esos momentos — y no se escribe nada.

Las variantes asíncronas hacen la E/S de archivos fuera del hilo principal. Aun así capturan en el hilo principal, porque leer objetos de Unity lo requiere. Ver Guardado asíncrono para lo que eso realmente te da.

using Beasty.VN.Runtime;

public static class QuickSave
{
    public static bool Save() => VNGameController.Instance.SaveToSlot("manual_0", "Quick save");

    public static bool Load()
    {
        var outcome = VNGameController.Instance.LoadSlotDetailed("manual_0");
        if (outcome == VNSlotLoadOutcome.FailedBackupAvailable)
            return VNGameController.Instance.RestoreSlotBackup("manual_0")
                   && VNGameController.Instance.LoadSlot("manual_0");
        return outcome == VNSlotLoadOutcome.Loaded;
    }
}

Para el panorama completo — política de autoguardado, miniaturas, qué contiene una ranura — ver Guardado y carga.

VisualNovelController

Aloja una historia. VisualNovelController.Instance, o BeastyManager.Instance.VN.

public static VisualNovelController Instance { get; }
public VNSession Session { get; }
public bool IsRunning { get; }
public bool EndedNaturally { get; }
public DialogueScene Project { get; }
public VariableStore SharedVariableStore { get; set; }
public StageMemory PersistentStage { get; }

Iniciar y detener:

public void StartVisualNovel(string nodeId = null, Sprite roomBackground = null);
public void StartVisualNovel(DialogueScene overrideProject, string nodeId = null, Sprite roomBackground = null);
public bool RestoreVisualNovel(VisualNovelSaveData data);
public bool RestoreVisualNovel(DialogueScene overrideProject, VisualNovelSaveData data);
public void StartVisualNovelTalkMenu(DialogueScene carrierProject,
                                     IReadOnlyList<ResolvedTalkEntry> entries,
                                     string promptKey = null, string promptSpeakerName = null,
                                     Sprite roomBackground = null, Sprite characterSprite = null);
public void StopVisualNovel();
public void Advance();
public void Back();
public bool Save();
public bool Load();
public void ApplyRoomBackdrop(Sprite roomBackground);

RestoreVisualNovel devuelve false cuando el nodo guardado ya no existe en la historia — la ranura es de un build anterior. Repórtalo; no lo trates como una carga exitosa.

Eventos, reflejados como UnityEvents en el Inspector (OnVisualNovelStarted, OnVisualNovelEnded):

public event Action Started;
public event Action Ended;
public event Action<string, string> FreeRoamRequested;         // scenarioId, roomId
public event Action FreeRoamReturnRequested;
public event Action<FreeRoamMapGraph, List<string>> ChooseRoomRequested;
public event Action<DialogueScene, string> SwitchProjectRequested;
public event Action<ResolvedTalkEntry> TalkEntryChosen;
public event Action<string, string> TalkMenuRequested;         // characterId, fallbackNodeId
public event Action BackAtStart;

VNGameController ya se suscribe a todos estos — así es como un bloque Flow sale de la novela. Suscríbete solo si estás reemplazando el anfitrión.

El almacén de variables compartido

SharedVariableStore es asignado por VNGameController al arrancar, con su propio SharedVariables. Cada sesión que este controlador construye luego lee y escribe ese único almacén. Déjalo tal cual a menos que estés ejecutando un VisualNovelController fuera del rig normal, en cuyo caso un almacén null hace que cada sesión obtenga uno aislado.

El escenario persistente

PersistentStage (un StageMemory) contiene el fondo, los personajes y los objetos (props). Vive en el controlador, no en la sesión, así que el escenario sobrevive a un cambio de nodo, un cambio de proyecto y una excursión a FreeRoam o a tu modo Custom: lo visual se limpia mientras estás fuera y se vuelve a renderizar cuando la VN se reanuda. StartNewGame en el anfitrión llama a PersistentStage.Reset() para un inicio en blanco.

Ver también