Dialogue, Objectives, and Save States
What you'll learn
~60 min- Build a JSON-driven branching dialogue system with NPC portraits and choice buttons
- Implement an objective tracker with sidebar checklist and completion state
- Create a localStorage save/load system with chapter select
- Generate dialogue tree JSON using AI CLI prompts
What you’re building
Your prototype has combat, movement, a HUD, and an inventory. A player can fight enemies, collect items, and manage resources. What they cannot do is understand why they are fighting, what they are supposed to accomplish, or pick up where they left off after closing the browser. The game has mechanics but no context.
BloodRayne was an objective-driven game. Each level started with a mission briefing — infiltrate the Nazi compound, destroy the research lab, eliminate the target. NPCs gave you intel and sometimes offered choices. The game saved at checkpoints so dying did not mean starting over from scratch. These are not luxury features. They are the difference between “I’m mashing buttons in a test room” and “I’m a vampire agent on a mission.”
Forty-five minutes from now you will have a branching dialogue system driven by JSON files (so you can generate new conversations with a single AI prompt), an objective tracker that shows a sidebar checklist with live completion status, and a localStorage save/load system that lets the player resume from any chapter they have reached.
JSON dialogue trees + state machine for conversation flow + persistent storage for progress. This pattern is used in visual novels, RPGs, interactive fiction, chatbot flows, onboarding wizards, and any application where users navigate branching paths with persistent state.
The showcase
Here is the complete system you end up with:
- Dialogue box: Bottom of screen, dark panel with NPC portrait (colored rectangle placeholder), NPC name, dialogue text that types out letter-by-letter, and up to 3 choice buttons when branching occurs.
- Branching conversations: Dialogue trees stored as JSON. Each node has text, an optional speaker portrait, and optional choices that branch to different nodes. The AI generates these files — you describe the conversation, it writes the JSON.
- Objective tracker: Right sidebar, semi-transparent panel listing current objectives as a checklist. Completed objectives get a checkmark and strikethrough. New objectives added dynamically as dialogue triggers them.
- Save/load system: Auto-saves to localStorage on chapter transitions and objective completions. Manual save with a “Save” button in the pause screen. Load screen shows available saves with chapter name, timestamp, and completion percentage.
- Chapter select: From the title/load screen, choose any chapter the player has previously reached. Each chapter restores the player’s state (health, inventory, objectives) from the save data.
The prompt
Open your terminal, navigate to your game project folder, start your AI CLI tool, and paste this prompt:
Build a Phaser 3 dialogue, objective, and save/load system for aBloodRayne-inspired action game. The system is data-driven — dialoguetrees are JSON files that the AI can generate.
PROJECT STRUCTURE:dialogue-objectives-save/ package.json index.html src/ main.js (Phaser config, scene list) TitleScene.js (title screen with New Game, Load Game, Chapter Select) GameScene.js (simple gameplay scene with player, NPCs, trigger zones) DialogueScene.js (dialogue overlay scene, reads JSON trees) ObjectiveScene.js (objective tracker sidebar overlay) SaveManager.js (localStorage save/load utility) state.js (shared game state) data/ chapter1-briefing.json (opening mission briefing dialogue tree) chapter1-npc-ally.json (mid-mission NPC conversation with choices) chapter1-objectives.json (chapter 1 objective definitions)
REQUIREMENTS:
1. DIALOGUE TREE JSON FORMAT (data/*.json) Each dialogue file is a JSON object: { "id": "chapter1-briefing", "nodes": { "start": { "speaker": "Commander Wulf", "portrait": "#3b82f6", "text": "Rayne. We have a situation at the facility. Nazi scientists are developing something we cannot allow to exist. Your mission: infiltrate, gather intel, and eliminate the lead researcher.", "next": "response" }, "response": { "speaker": "Rayne", "portrait": "#8B5CF6", "text": "Any idea what they're building in there?", "choices": [ { "text": "I'll handle it quietly.", "next": "stealth_path" }, { "text": "I'll burn it all down.", "next": "assault_path" }, { "text": "What's the exit plan?", "next": "exit_info" } ] }, "stealth_path": { "speaker": "Commander Wulf", "portrait": "#3b82f6", "text": "Good. Use the ventilation system on the east side. Fewer guards. And Rayne — try not to feed until you're past the first checkpoint.", "next": "end", "setObjective": "infiltrate_east" }, "assault_path": { "speaker": "Commander Wulf", "portrait": "#3b82f6", "text": "I expected nothing less. Front entrance it is. There are at least twelve guards in the courtyard. Make it fast before reinforcements arrive.", "next": "end", "setObjective": "assault_front" }, "exit_info": { "speaker": "Commander Wulf", "portrait": "#3b82f6", "text": "Extraction helicopter on the roof. You have 20 minutes from first contact. After that, you're on your own.", "next": "response" }, "end": { "speaker": null, "text": null, "action": "close", "triggerSave": true } } }
Create chapter1-briefing.json with the above content. Create chapter1-npc-ally.json with a 6-node conversation where an allied agent gives intel about enemy patrols, with one branching choice. Create chapter1-objectives.json: { "chapter": 1, "title": "The Facility", "objectives": [ { "id": "infiltrate_east", "text": "Infiltrate through the east ventilation system", "optional": false }, { "id": "assault_front", "text": "Storm the front entrance", "optional": false }, { "id": "gather_intel", "text": "Find the research documents", "optional": false }, { "id": "eliminate_target", "text": "Eliminate Dr. Krieger", "optional": false }, { "id": "rescue_prisoners", "text": "Free the test subjects", "optional": true }, { "id": "reach_extraction", "text": "Reach the rooftop extraction point", "optional": false } ] }
2. TITLE SCENE (src/TitleScene.js) - Dark background (#09090b) - Title: "BLOODRAYNE REMAKE PROTOTYPE" in large white text (or similar) - Subtitle: "A Lora Studios Production" in smaller gray text - Three buttons (styled rectangles with text): - "NEW GAME" — clears state, starts Chapter 1 - "LOAD GAME" — shows save slots, click to load - "CHAPTER SELECT" — shows unlocked chapters, click to start from any - Load Game shows up to 3 save slots with: chapter name, date/time, completion % - Chapter Select shows chapters 1-3 (only unlocked ones are clickable) - Chapter 1 always unlocked, others unlock on completion
3. GAME SCENE (src/GameScene.js) Simple scene for testing dialogue and objectives: - 800x600 canvas, dark background (#18181b) - Player: 32x32 purple rectangle, WASD movement - 2 NPC rectangles: blue (#3b82f6) with "!" marker floating above - 3 trigger zones (invisible rectangles): - NPC 1 proximity: triggers chapter1-briefing.json dialogue - NPC 2 proximity: triggers chapter1-npc-ally.json dialogue - Exit zone (right edge): checks if required objectives are complete - Player walks near NPC → press E to talk → dialogue scene launches - "E to talk" prompt appears when player is within 50px of an NPC - Objective completion: for demo purposes, add colored rectangles as "objective items" — walk over them to mark objectives complete
4. DIALOGUE SCENE (src/DialogueScene.js) Overlay scene launched when dialogue triggers:
a. DIALOGUE BOX (bottom of screen) - Full-width dark panel, height 160px, background #111118, border-top 2px #8B5CF6 - Left side: 80x80 portrait rectangle filled with the speaker's portrait color - Speaker name: bold white text above the dialogue text - Dialogue text: white, types out letter-by-letter (30ms per character) - Click or press SPACE to instantly complete the current text - Click or press SPACE again to advance to next node
b. CHOICE BUTTONS - When a node has "choices" array, show 1-3 buttons below the text - Buttons: dark rectangles with white text, #8B5CF6 border - Hover: background lightens to #2a2a3e - Click: advance to the choice's "next" node - Keyboard: press 1, 2, or 3 to select a choice
c. ACTIONS - "setObjective" in a node: activate that objective in ObjectiveScene - "triggerSave" in a node: auto-save via SaveManager - "action": "close" — close dialogue scene, return control to GameScene
d. Load dialogue JSON via fetch() from the data/ directory
5. OBJECTIVE SCENE (src/ObjectiveScene.js) Persistent overlay on the right side of the screen:
a. PANEL - Right side, width 220px, semi-transparent background (#111118, alpha 0.8) - Title: "OBJECTIVES" in small white text, top of panel - Slide in/out animation (200ms) - Toggle visibility with TAB key
b. OBJECTIVE LIST - Each objective: checkbox icon + text - Incomplete: hollow square □ + white text - Complete: filled square ■ with checkmark + strikethrough text + green color - Optional objectives: text in italic, "(optional)" suffix - New objectives animate in (slide from right, 300ms)
c. COMPLETION - When all required objectives are complete, flash "ALL OBJECTIVES COMPLETE" - Play a subtle notification (screen border flash, brief text popup)
6. SAVE MANAGER (src/SaveManager.js) Utility class using localStorage:
a. SAVE - Key format: "lorastudio-save-{slot}" (slots 1-3) - Save data: { chapter, objectives (with completion status), inventory, health, rage, enemiesDefeated, timestamp, playTimeSeconds } - Auto-save: triggered by dialogue "triggerSave" actions and objective completions - Manual save: triggered from pause screen
b. LOAD - Read save data from localStorage - Restore state.js values from save data - Return chapter number for scene transition
c. CHAPTER SELECT - Scan all save slots for highest completed chapter - Chapters up to (highest completed + 1) are unlocked - Starting a chapter loads default state for that chapter
d. Methods: save(slot), load(slot), listSaves(), getUnlockedChapters(), deleteSave(slot), autoSave()
DEPENDENCIES: phaser (^3.80), viteAdd "dev" script: "vite"HTML page background: #09090bThe dialogue tree is just data. That means you can generate new conversations by describing them in plain English to the AI CLI. “Write a dialogue tree JSON where an NPC warns Rayne about a trap, with a choice to spring it or find another way around.” One prompt, one new conversation, no code changes needed. The engine reads whatever JSON you feed it.
What you get
After generation (typically 60-90 seconds):
dialogue-objectives-save/ package.json index.html src/ main.js TitleScene.js GameScene.js DialogueScene.js ObjectiveScene.js SaveManager.js state.js data/ chapter1-briefing.json chapter1-npc-ally.json chapter1-objectives.jsonFire it up
cd dialogue-objectives-savenpm installnpm run devOpen the URL. You should see the title screen with “NEW GAME” and “LOAD GAME” buttons. Click New Game. Walk the purple player square toward the blue NPC with the ”!” marker. When you see “E to talk,” press E. The dialogue box should slide in at the bottom of the screen with Commander Wulf’s briefing text typing out letter by letter.
Make a choice when the branching options appear. Watch the objective tracker on the right side update with your new objective. Walk over the objective items to complete them. Press ESC to pause and save, or let the auto-save trigger at chapter transitions.
If something is off
| Problem | Follow-up prompt |
|---|---|
| Dialogue JSON fails to load | The fetch() call to load dialogue JSON is failing with a 404. Make sure the data/ directory is served by Vite as a public static folder. Either move the JSON files to public/data/ or configure Vite to serve the root data/ directory. Update the fetch paths to match. |
| Text typewriter effect is instant | The dialogue text appears all at once instead of typing letter by letter. Use a Phaser timer event that adds one character every 30ms to a displayed text object. Track the current character index and increment it on each timer tick. SPACE should set the index to the full string length to complete instantly. |
| Choice buttons are not clickable | The choice buttons render but clicking them does nothing. Make sure each button rectangle has setInteractive() called on it, and add a pointerdown event listener that reads the choice's "next" property and advances the dialogue to that node. |
| Save data does not persist | After saving and refreshing the browser, Load Game shows no saves. Check that SaveManager uses localStorage.setItem() with JSON.stringify() for saving and localStorage.getItem() with JSON.parse() for loading. Verify the key format matches between save and load operations. |
Deep dive
Three systems are working together here, and the architecture is what matters.
JSON-driven dialogue is the key design decision. The dialogue engine does not contain any conversation content. It reads JSON files that describe who says what, what choices exist, and what happens when the player picks one. This means the AI CLI becomes your dialogue writer. You describe a conversation in plain English, it generates the JSON, and the engine plays it. No code changes. No recompilation. Just data.
The objective system is a checklist with triggers. Each objective has an ID. Dialogue nodes can activate objectives (the setObjective field). Game events can complete objectives (walking over a trigger zone). The objective scene reads the list and renders checkboxes. This is the same pattern as a task management system — items have states, transitions happen based on events, and the UI reflects current state.
localStorage is your save file. Browsers give you 5-10 MB of localStorage per domain. A save file for this game is maybe 2 KB. You could store hundreds of saves and never come close to the limit. The SaveManager serializes the entire game state to JSON and stores it under a key. Loading deserializes it back. This is identical to how mobile games handle saves before cloud sync was standard.
🔍Generating new dialogue with AI prompts
Here is the real workflow once this system is running. You want a new NPC conversation. Open your AI CLI and type:
Write a dialogue tree JSON in this format for a BloodRayne game.The conversation is between Rayne and a captured scientist who offersto help if Rayne frees him. Include a choice: trust him (he gives akeycard and sets objective "use_keycard") or refuse (he warns aboutguards and sets objective "find_alternate_route"). Use the same JSONschema as chapter1-briefing.json.The AI generates the JSON. You save it as chapter1-scientist.json in the data folder. You add a new NPC to the scene that triggers this dialogue file. No code changes to the dialogue engine. The content scales independently of the system. That separation is the whole point.
Customize it
Add dialogue portraits with expressions
Extend the dialogue JSON format to support portrait expressions.Each node can have a "portraitExpression" field: "neutral", "angry","surprised", "sad". In the dialogue scene, change the portraitrectangle's color intensity or add a simple expression indicator(e.g., "!" for angry, "?" for surprised) overlaid on the portrait.This adds emotional context without requiring actual art assets.Add a quest log
Add a quest log accessible from the pause screen. It shows allobjectives grouped by chapter: active objectives at the top,completed objectives below (grayed out with completion timestamp).Include a brief description for each objective pulled from theobjectives JSON. Add a "failed" state for objectives that can nolonger be completed (e.g., the optional rescue if you leave the area).Add dialogue history
Add a dialogue history log that records every line of dialoguethe player has seen. Accessible from the pause screen as a scrollablelist. Each entry shows: speaker name, text, and any choice the playermade (highlighted). This lets the player review what NPCs said withoutreplaying conversations. Store in the save data.Try it yourself
- Paste the main prompt and generate the project.
- Run
npm install && npm run devand start a new game. - Walk to the first NPC and trigger the briefing dialogue. Read through the conversation and make a choice at the branching point.
- Check the objective tracker — your choice should have activated the corresponding objective.
- Complete some objectives by walking over trigger items. Watch the checklist update.
- Press ESC, save the game. Refresh the browser. Load your save. Verify your progress was preserved.
- Write a new dialogue JSON by hand or by prompting your AI CLI. Add a new NPC in GameScene that triggers it. Test the new conversation without changing any engine code.
Key takeaways
- Data-driven dialogue means the engine and the content are separate. New conversations are JSON files, not code changes. The AI CLI generates the data, the engine plays it.
- Branching choices create replay value and player agency. The same BloodRayne mission plays differently depending on whether you chose stealth or assault.
- Objective tracking turns an open sandbox into a directed experience. Players know what to do, and the checklist provides constant feedback on progress.
- localStorage saves are simple, reliable, and free. No backend, no accounts, no server costs. The player’s progress persists across browser sessions.
- Chapter select from saves respects the player’s time. Nobody wants to replay 30 minutes of content they already completed because they closed a browser tab.
Every story-driven game needs a way to deliver exposition, present choices, and track consequences. This JSON-driven approach means you can prototype an entire game’s worth of narrative content in an afternoon using AI prompts, without touching the engine code. Write the story, generate the JSON, drop it in the data folder. That is the workflow.
What’s next
You have mechanics, UI, narrative, and persistence. The one thing you cannot do yet is design levels efficiently. In the next lesson you will build a browser-based level editor — a tile placement tool where you click to place ground, walls, platforms, hazards, spawns, and exits on a grid, then export the layout as JSON that Phaser can load directly. That is the tool that turns level design from “manually typing coordinates” into “click where you want things.”