npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

simple-visual-novel

v0.2.3

Published

A minimal TypeScript visual novel engine

Readme

Simple Visual Novel Engine

A minimal TypeScript visual novel engine with vanilla DOM rendering, supporting dialogue, character visibility, and scene transitions.

Features

  • Class-based Script API - Build stories using a fluent, type-safe API
  • Dialogue System - Per-dialogue effects (fade, typewriter)
  • Character Management - Show/hide characters dynamically, change images mid-scene
  • Scene Navigation - Automatic progression through scenes
  • Game State - Variable management for flags and counters
  • Event System - Listen to scene changes and variable updates

Installation

npm (for ES modules)

npm install simple-visual-novel

Browser Bundle

A browser bundle is available via unpkg:

https://unpkg.com/simple-visual-novel@latest

Of a minified version:

https://unpkg.com/simple-visual-novel@latest/dist/simple-visual-novel.min.js

Or build it yourself:

npm install
npm run build
# The bundle will be in dist/simple-visual-novel.js

Development

# Build TypeScript
npm run build

# Watch mode
npm run dev

# Run tests
npm test

Usage

Browser (Script Tag)

Include the bundled version in your HTML. The bundle is available in dist/simple-visual-novel.js after building:

<!DOCTYPE html>
<html>
<head>
  <title>My Visual Novel</title>
</head>
<body>
  <div id="game-container"></div>
  
  <!-- Include the bundled library -->
  <script src="path/to/simple-visual-novel.js"></script>
  
  <script>
    // Access the library via the SimpleVN global
    const { Script, Scene, Character, VNEngine } = SimpleVN;
    
    // Create your story
    const script = new Script();
    const narrator = new Character("Narrator");
    const scene1 = new Scene("scene1", { background: "park.png" });
    scene1.add(narrator);
    narrator.say("Hello, world!");
    script.addScene(scene1);
    
    // Initialize the engine
    const engine = new VNEngine({
      script: script,
      container: "#game-container",
      startScene: "scene1",
      renderer: {
        assetsDirectory: "assets", // Optional: base directory for asset paths
        // With assetsDirectory set, "park.jpg" becomes "assets/park.jpg"
      },
    });
  </script>
</body>
</html>

Note: After installing via npm, the browser bundle will be in node_modules/simple-visual-novel/dist/simple-visual-novel.js. You can copy it to your project or use a bundler.

ES Modules

import { Script, Scene, Character, VNEngine } from "simple-visual-novel";

// Create script
const script = new Script();

const narrator = new Character("Narrator");
const protagonist = new Character("Protagonist", "protagonist.png"); // Character with sprite image

const scene1 = new Scene("scene1", { background: "park.png" });
scene1.add(narrator); // narrator is shown automatically
narrator.say("It was a sunny day.", { effect: "fade" });
narrator.say("The birds were chirping.");
scene1.add(protagonist); // protagonist is shown automatically
protagonist.say("I wonder what will happen?", { effect: "typewriter" });
script.addScene(scene1);

// Initialize engine (renderer is created automatically)
const engine = new VNEngine({
  script: script,
  container: "#game-container", // or a DOM element
  startScene: "scene1",
  renderer: {
    typewriterSpeed: 50, // characters per second
    assetsDirectory: "assets", // Optional: base directory for asset paths
  },
});

Story Script Format

Scenes progress automatically in the order they are added to the script. No explicit nextScene is needed.

const script = new Script();

const narrator = new Character("Narrator");
const protagonist = new Character("Protagonist");

const scene1 = new Scene("scene1", { background: "park.png" });
scene1.add(narrator);
narrator.say("It was a sunny day.");
narrator.say("The birds were chirping.");
scene1.add(protagonist);
protagonist.say("I wonder what will happen?");
narrator.hide();
script.addScene(scene1);

const scene2 = new Scene("scene2", { background: "cafe.png" });
scene2.add(protagonist);
protagonist.say("Let's go to the cafe!");
script.addScene(scene2);
// scene2 automatically follows scene1

Dialogue Effects

Dialogue can have optional effects:

  • { effect: "typewriter" } - Character-by-character animation
  • { effect: "fade" } - Fade in animation
  • No effect - Display immediately

Changing Character Images

Characters can have their images changed dynamically during a scene by setting the image property. This is useful for showing different character expressions or outfits:

const character = new Character("Alice", "alice-neutral.png");

scene.add(character);
character.say("Hello!");
character.image = "alice-happy.png"; // Change to happy expression
character.say("I'm so happy to see you!");
character.image = "alice-surprised.png"; // Change to surprised expression
character.say("Wait, what's that?!");

When the character is in a scene, setting image automatically queues an action to update the displayed sprite. If the character is not yet in a scene, it just updates the internal state.

Character Positioning and Sizing

Characters can be positioned and sized using various methods:

Position Types

  • Named positions: "left", "center", "right", "far-left", "far-right"
  • Normalized coordinates (0.0-1.0): { x: 0.5, y: 1.0 } - Converted to percentages
  • Pixel values: { x: "100px", y: "50px" }
  • Percentage strings: { x: "30%", y: "75%" }

Size Types

  • Normalized values (0.0-1.0): { width: 0.3, height: 0.6 } - Converted to percentages
  • Pixel values: { width: "300px", height: "400px" }
  • Percentage strings: { width: "50%", height: "60%" }

Usage Examples

const character = new Character("Alice", "alex.png");

// Method 1: Using getters/setters
character.position = "left";
character.size = { width: "300px", height: "400px" };
character.show();

// Method 2: When adding to scene
scene.add(character, { position: "left", size: { width: "300px" } });

// Method 3: Normalized coordinates (0.0-1.0)
character.position = { x: 0.3, y: 0.8 };
character.size = { width: 0.3, height: 0.6 };
scene.add(character, { position: { x: 0.7, y: 1.0 }, size: { width: 0.4, height: 0.7 } });

// Method 4: Pixel values
character.position = { x: "100px", y: "50px" };
character.size = { width: "200px", height: "300px" };
scene.add(character, { position: { x: "200px", y: "0" }, size: { width: "250px" } });

// Method 5: Mixed (percentage strings)
character.position = { x: "30%", y: "80%" };
character.size = { width: "50%", height: "60%" };

// Method 6: Override in show() or scene.add()
character.position = "center";
character.show({ position: "left", size: { width: "400px" } }); // Overrides position to left, size to 400px width

Position Coordinate System

  • X-axis: 0.0 = left edge, 0.5 = center, 1.0 = right edge
  • Y-axis: 0.0 = top edge, 1.0 = bottom edge (characters are typically bottom-aligned)
  • Characters are positioned using CSS left and bottom properties
  • Percentage-based x positioning uses transform: translateX(-50%) for center alignment
  • Default position when not specified: center-bottom

Named Position Mappings

  • "far-left": x = 10% from left
  • "left": x = 25% from left
  • "center": x = 50% from left (centered)
  • "right": x = 75% from left
  • "far-right": x = 90% from left
  • All named positions default to y = 0 (bottom-aligned)

HTML Structure

The renderer creates the following DOM structure:

<div id="game-container">
  <div class="vn-background-layer"></div>
  <div class="vn-character-layer">
    <div class="vn-character" data-character-name="..."></div>
  </div>
  <div class="vn-dialogue-box">
    <div class="vn-speaker-name"></div>
    <div class="vn-dialogue-text"></div>
  </div>
</div>

Project Structure

simple-visual-novel/
├── src/
│   ├── core/
│   │   ├── engine.ts          # Main engine class
│   │   ├── types.ts           # TypeScript interfaces and classes
│   │   └── state.ts           # Game state management
│   ├── renderer/
│   │   ├── renderer.ts        # DOM rendering logic
│   │   └── effects.ts         # Text effects (typewriter, fade)
│   ├── examples/
│   │   └── exampleNovel.ts    # Example story script
│   └── index.ts               # Entry point
├── tests/                      # Test files
├── example/
│   ├── index.html             # Main HTML file
│   └── styles.css             # Styling
└── package.json

License

MIT