@proumeus/redhood-dialogue-sdk
v1.3.0
Published
A lightweight browser SDK for REDHOOD-style branching visual novel dialogue with pixel UI support.
Maintainers
Readme
@proumeus/redhood-dialogue-sdk
A lightweight browser SDK for REDHOOD-style branching visual novel dialogue.
Built with plain JavaScript and native DOM — no React, no Vue, no build step required.
Current version: 1.3.0 — variables, conditional choices, actions, and save-state support. See CHANGELOG.md.
About REDHOOD
REDHOOD is an independent dark fairy-tale 2D game inspired by Little Red Riding Hood. This SDK extends the game into a small creator kit for modders and web developers.
Play REDHOOD
https://proumeus.itch.io/redhood
Install
npm install @proumeus/redhood-dialogue-sdkFeatures
- Typewriter effect — characters appear one by one; click to skip
- Branching choices — multiple paths with
choicesandnextId - Dialogue variables — store boolean, number, and string state during play
- Conditional choices — show or hide options based on variables
- Built-in actions —
setVariable,changeVariable, andemit - Save / load — export and import dialogue runtime state
- Node-based API —
createDialogue()withstart(),restart(),destroy() - Legacy array API —
createDialogueScene()still supported - REDHOOD themes —
dark,blood,forest - Pixel font mode — set
font: "pixel"for retro UI - Input validation — invalid dialogue data logs clear
[RedHoodSDK]console errors - TypeScript definitions — full autocomplete in VS Code and Cursor
- Zero runtime dependencies — plain ES modules, works in any modern browser
Quick start
<div id="game"></div>
<script type="module">
import { createDialogue } from "@proumeus/redhood-dialogue-sdk";
const nodes = {
"intro-1": {
speaker: "Jack",
text: "A new adventurer?",
nextId: "intro-2",
},
"intro-2": {
speaker: "Jack",
text: "It's been a long time",
nextId: "end-1",
},
"end-1": {
speaker: "Jack",
text: "(nodded)",
},
};
const dialogue = createDialogue({
container: "#game",
startId: "intro-1",
nodes,
typeSpeed: 35,
font: "pixel",
onEnd() {
console.log("Dialogue ended");
},
});
dialogue.start();
</script>Load Press Start 2P for pixel font mode:
<link href="https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap" rel="stylesheet">Branching dialogue example
Use choices instead of nextId when the player should pick a path:
const nodes = {
"choice-1": {
speaker: "Jack",
text: "What will you do?",
choices: [
{ label: "Accept", nextId: "accept-1" },
{ label: "Cancel", nextId: "cancel-1" },
],
},
"accept-1": {
speaker: "RedHood",
text: "thanks",
nextId: "end-1",
},
"cancel-1": {
speaker: "RedHood",
text: "I want to look around first",
nextId: "end-1",
},
"end-1": {
speaker: "Jack",
text: "(nodded)",
},
};
const dialogue = createDialogue({
container: "#game",
startId: "choice-1",
nodes,
});
dialogue.start();Each choice needs label (or text) and nextId. A node with no nextId and no choices ends the dialogue.
Variables
Dialogue variables let you store lightweight game state while a conversation runs.
Supported types:
booleannumberstring
dialogue.setVariable("hasKey", true);
dialogue.setVariable("trust", 10);
const hasKey = dialogue.getVariable("hasKey");
const trust = dialogue.getVariable("trust");Behavior:
- Reading a variable that was never set returns
undefined - Pass a second argument to provide a default:
dialogue.getVariable("trust", 0) - Variables are stored internally and are not exposed as mutable references
- You can seed initial values with
defaultVariablesincreateDialogue()
Common errors:
- Setting a non-primitive value throws a
RedHoodError - Empty variable names throw a
RedHoodError
Conditional Choices
Add an optional condition to a choice to control whether it appears.
{
id: "open-door",
text: "Open the door",
nextNodeId: "door-opened",
condition: {
variable: "hasKey",
operator: "equals",
value: true
}
}Supported operators:
equalsnotEqualsgreaterThangreaterThanOrEquallessThanlessThanOrEqual
Default behavior:
- If a condition is not met, the option is hidden
- Options without a
conditionalways appear normally - Set
hiddenConditionBehavior: "disable"increateDialogue()to show failed options as disabled buttons instead
Type rules:
equals/notEqualswork with boolean, number, and string values- Ordering operators expect comparable types; missing numeric variables are treated as
0
Common errors:
- Unsupported operators are reported during validation
- Invalid condition shapes are logged when dialogue data is loaded
Actions and Events
Attach optional actions to a choice. Actions run after the choice is accepted and before the dialogue moves to the next node.
Execution order:
- Validate the selected choice is available
- Record the selected option id
- Run each action in array order
- Navigate to
nextId/nextNodeId
Supported action types:
{
id: "accept-quest",
text: "Accept the quest",
nextNodeId: "quest-accepted",
actions: [
{ type: "setVariable", variable: "questAccepted", value: true },
{ type: "changeVariable", variable: "trust", amount: 5 },
{ type: "emit", event: "startQuest", payload: { questId: "red-forest" } }
]
}setVariable
Sets a variable directly.
changeVariable
Adds a numeric amount to an existing number variable.
- If the variable does not exist yet, it starts from
0 - If the variable exists but is not a number, the action throws a clear error
emit
Dispatches an external event to listeners registered on the dialogue instance:
dialogue.on("startQuest", (payload) => {
console.log(payload);
});
dialogue.off("startQuest", handler);Notes:
- The SDK never executes string code or uses
eval - Selecting an unavailable or unknown choice does not run actions
- Keep
payloadJSON-serializable; do not store functions, DOM nodes, or other non-serializable values
Saving and Loading Dialogue State
Export the current runtime state for save games:
const saveData = dialogue.exportState();
localStorage.setItem("dialogue-save", JSON.stringify(saveData));Example shape:
{
version: 1,
currentNodeId: "forest-entry",
variables: {
hasKey: true,
trust: 10
},
selectedOptionIds: ["accept-quest"],
visitedNodeIds: ["start", "forest-entry"]
}Restore it later:
const saveData = JSON.parse(localStorage.getItem("dialogue-save"));
await dialogue.importState(saveData);Behavior:
exportState()returns a plain object safe forJSON.stringifyimportState()validates the full payload first, then applies it atomically- Invalid node ids, unsupported versions, or malformed fields throw
RedHoodError - If import fails, the previous runtime state is left unchanged
Data Validation
Dialogue data is validated when you call createDialogue() or createDialogueScene().
The validator checks:
- Duplicate or missing node ids
- Missing
startId - Invalid or missing
nextId/nextNodeIdtargets - Duplicate option ids
- Invalid conditions and operators
- Unsupported or incomplete actions
Example error:
[RedHoodSDK] Invalid nextNodeId "missing-node" in option "open-door". The target node does not exist. Suggestion: add node "missing-node" or fix the reference.Fix the reported field and try again. Runtime errors also log clearly and end the scene safely.
TypeScript Types
Type definitions ship with the package at src/index.d.ts.
Key exports:
DialogueVariableValue
DialogueVariables
DialogueCondition
DialogueConditionOperator
DialogueAction
SetVariableAction
ChangeVariableAction
EmitAction
DialogueOption
DialogueNode
DialogueData
DialogueState
DialogueStateV1Action types use a discriminated union:
type DialogueAction =
| SetVariableAction
| ChangeVariableAction
| EmitAction;This gives you autocomplete, invalid operator errors, and typed save-state exports in Cursor and VS Code.
Migration from v1.2.x
v1.3.0 is backward compatible with existing dialogue files.
What stays the same:
createDialogue(),start(),restart(), anddestroy()- Legacy
choiceswithlabel/textandnextId createDialogueScene()array-based scenes- Existing themes, typewriter behavior, and click-to-advance UX
What is new and optional:
conditionactionsidon choicesnextNodeIdalias fornextIdsetVariable(),getVariable(),on(),off(),exportState(),importState()
No changes are required unless you want the new features.
Console prefix update:
- Validation and runtime errors now use
[RedHoodSDK]instead of[redhood-dialogue-sdk]
API
createDialogue(options)
| Option | Default | Description |
| --- | --- | --- |
| container | — | CSS selector or DOM element |
| startId | — | First node id to display |
| nodes | — | Object map of dialogue nodes |
| typeSpeed | 35 | Typewriter speed (ms per character) |
| allowSkipTyping | true | Click to instantly finish typing |
| showSpeaker | true | Show or hide speaker name |
| theme | "dark" | Visual theme: dark, blood, forest |
| font | "serif" | Set to "pixel" for pixel font |
| onEnd | — | Called when dialogue ends |
| defaultVariables | — | Initial dialogue variables |
| hiddenConditionBehavior | "hide" | "hide" or "disable" for failed conditions |
Node format:
{
speaker: "Jack",
text: "Hello.",
nextId: "next-node",
choices: [
{
id: "accept-quest",
label: "Accept",
nextNodeId: "accepted",
condition: { variable: "trust", operator: "greaterThanOrEqual", value: 5 },
actions: [{ type: "emit", event: "questAccepted" }],
},
],
}Returns:
const dialogue = createDialogue({ ... });
dialogue.start();
dialogue.restart();
dialogue.destroy();
dialogue.setVariable("hasKey", true);
dialogue.getVariable("hasKey");
dialogue.on("doorOpened", (payload) => {});
dialogue.off("doorOpened", handler);
const saveData = dialogue.exportState();
await dialogue.importState(saveData);
dialogue.getCurrentNodeId();
dialogue.getAvailableChoices();
dialogue.selectChoice("open-door");Legacy API
createDialogueScene(containerId, sceneData, options?) is still available for array-based scenes.
Utilities
typeText(element, text, speed?)— typewriter effect with.skip()applyRedhoodStyles(sceneElement, options?)— apply REDHOOD stylesDialogueRuntime— headless runtime for custom integrations and testsvalidateDialogueNodes(nodes, startId)— validate node maps manually
Interaction
- While typing — click the dialogue box to finish the current line instantly
- After typing — click again to go to the next node
- Choices — appear only after typing finishes; click a choice button to branch
- End — when a node has no
nextIdand nochoices, dialogue ends andonEndruns
Demo
npm install
npm run devOpen:
- http://localhost:3456/demo/ — basic branching demo
- http://localhost:3456/demo/variables-demo.html — variables, conditions, actions, and save/load
Run tests:
npm testChangelog
See CHANGELOG.md for full version history.
License
MIT © Proumeus
