Skip to content

How SVNE Works

SVNE is a Luau-first visual novel engine. Story authors write scripts that call a main vnm API. The engine provides UI, saves, sprites, sounds, choices, and chapter playback, but the story itself is still regular code.

Understanding that model helps explain why VNC does not copy SVNE’s source structure.

SVNE expects a fixed place layout.

  • DirectoryStarterPlayer
    • DirectoryStarterPlayerScripts
      • Story LocalScript
      • vnModule
  • DirectoryServerScriptService
    • vnRunnerServer
  • DirectoryReplicatedStorage
    • shared
    • network objects
  • DirectoryWorkspace
    • DirectoryvnSystem
      • images
      • sounds
      • themes
      • settings

The main responsibilities are:

  • StarterPlayerScripts holds the story LocalScript and client vnModule.
  • ServerScriptService holds vnRunnerServer, which initializes saves, settings, chapter unlocks, and text filtering.
  • ReplicatedStorage.shared holds shared helpers such as debug utilities.
  • The network object folder holds the RemoteFunctions and RemoteEvents SVNE uses during play.
  • Workspace.vnSystem holds images, sounds, theme presets, feature flags, and runtime settings.

That layout makes the engine easy to drop into a place, but it also means many story systems are coupled to a specific Roblox hierarchy.

SVNE stories are imperative Luau:

local function intro()
vnm.setBackground({ backgroundName = "baseplate" })
vnm.dialog({ subject = "Narrator", dialog = "Hello, world!" })
end
vnm.registerScene({
sceneName = "intro",
sceneFunction = intro,
})
vnm.registerChapter({
chapterIndex = 1,
chapterName = "Chapter One",
scenes = { "intro" },
})

A scene is a function. A chapter is an ordered list of scene names. Branching uses normal Luau control flow:

local choice = vnm.promptChoice({ choices = { "Red", "Green", "Blue" } })
if choice == 1 then
vnm.dialog({ dialog = "I like red." })
else
vnm.dialog({ dialog = "I picked something else." })
end

This is powerful, but it requires authors to understand code structure, variable lifetime, yields, and cleanup.

SVNE runs most story behavior on the client:

  1. vnm.init() initializes player data, GUI, sounds, story saves, and roaming support.
  2. The menu starts chapter 1 for a new game.
  3. playChapter() cancels current story execution, unlocks the chapter, hides the menu, optionally shows a title card, then plays each scene in order.
  4. playScene() clears the stage, cancels the previous scene thread, and spawns the registered scene function.
  5. The chapter loop waits until the scene thread finishes, then moves to the next scene.
  6. When the chapter chain ends, SVNE returns to the main menu.

SVNE uses task threads as the story execution engine. A scene finishes when its function returns.

vnm.dialog() controls the dialog box, typewriter text, portrait selection, scroll sounds, sprite emphasis, text cues, and advance behavior. If waitForAdvanceInput is true, the function yields until the player advances. If false, the line continues automatically.

vnm.promptChoice() creates choice buttons, waits for a selection, optionally stores the selected index in a story variable, and returns the selected number to the scene function.

This makes authoring compact, but the flow is hidden inside code. It is difficult to inspect the full branch graph without reading and mentally executing the script.

SVNE resolves assets from Workspace.vnSystem by name. Sprite images are usually ImageLabel templates under vnSystem.images.sprites; backgrounds, portraits, BGM, SFX, GUI sounds, and scroll sounds live in neighboring folders.

Sprite behavior includes:

  • cloning an image template into the stage,
  • optional auto-positioning,
  • emphasis and deemphasis,
  • transition-in and transition-out animations,
  • sprite methods such as setImage, reposition, resize, jump, and shake.

This gives authors direct power, but sprite setup depends on correctly named objects and template instance properties.

SVNE save data is scene-granular. A save slot records:

  • chapter index,
  • scene index,
  • timestamp,
  • story variables,
  • current background.

The built-in save UI supports up to 100 slots. Slot 1 is treated as autosave. Loading restores story variables and resumes from the start of the saved scene, not from the exact line or runtime state inside the scene.

Persistence uses a player data table, periodic saves, and a save on player leave.

  • The story graph is implicit in code.
  • Many story failures are spelling, ordering, or cleanup mistakes.
  • Saving resumes from scene starts, not precise node checkpoints.
  • Story execution is heavily client-driven.
  • Custom behavior often means editing or extending the same runtime scripts users depend on.
  • Long scenes are harder to maintain because save/load happens at scene boundaries.

SVNE is valuable because it proved the feature set Roblox VN authors want. VNC keeps that feature target, but changes the authoring and runtime architecture.