A point-and-click adventure engine built in Phaser. Games are defined as JSON files in data/ and selected from a launcher on startup.
No build step is required. The game runs directly from index.html. When you open the page, a game selector lists all available games from data/. Click a game to load it.
To create a new game you have two options: use the Maker Tool or write the JSON directly.
Open maker/index.html in your browser — a visual editor for building game worlds. No server or build step needed.
What you can do:
- Rooms — add, rename, delete; set colors, wall/floor tiles, player spawn point
- Objects — add interactive objects per room; set position, size, sprite, messages, pickup/block flags
- Dialogs — build branching
dialogTreeconversations per object: add/rename nodes, wire option targets, set the start node - Doors — connect rooms; set position, target, locked state
- World settings — choose the starting room, set opening text
- Import — load an existing
.jsonfile to edit - Export — copy the generated JSON, or download it as a file with the Download button
When you're done, export the JSON, save it as a new file in data/ (e.g. mygame.json), and add an entry to data/index.json:
{ "name": "My Game", "file": "mygame.json" }The game will appear in the launcher the next time you open index.html.
To add a new room:
- Add an entry to the
"rooms"object with objects, doors, and visual properties - Add a door in the starting room that links to it
- Reference the room's key in other doors'
targetRoomfields
To add a new object:
- Choose a unique
id - Place it at coordinates within the walkable area (wallThickness + 16 to 800 - wallThickness - 16)
- Define its messages, shape, and interaction properties
- If it should be hidden, add
hiddenByreferencing the parent object's id
All coordinates use the Phaser coordinate system with origin at top-left.
A room is a 800x600 area with walls, a floor, objects, and doors.
"living_room": {
"name": "Living Room",
"backgroundColor": "0x1a1a2e",
"wallColor": "0x4a3a2a",
"floorColor": "0x3a3a4a",
"wallThickness": 24,
"playerStart": { "x": 200, "y": 400 },
"objects": [ ... ],
"doors": [ ... ]
}| Field | Description |
|---|---|
name | Displayed in the top-left corner |
backgroundColor | Fill behind the walls |
wallColor | Tint applied to wall tiles |
floorColor | Tint applied to floor tiles |
wallThickness | Optional, defaults to 24 |
floorTile | Sprite name or frame index for the floor (default "floor") |
wallTile | Sprite name or frame index for walls (default "wallStone") |
playerStart | Where the player spawns when entering this room |
objects | Array of interactable objects |
doors | Array of exits to other rooms |
The walkable area is calculated as wallThickness + 16 inset from each edge. Objects with "blocks": true are treated as impassable by the pathfinder.
The floor and walls render as tiled sprites from the objects spritesheet (Sprite-0002.png). Use sprite names (from the spritesheet's frameTags) or direct frame indices.
Doors connect rooms. Clicking a door walks the player to it and transitions to the target room.
{
"id": "door_kitchen",
"x": 740, "y": 280,
"width": 12, "height": 60,
"color": "0xcc8833",
"label": "Kitchen",
"targetRoom": "kitchen",
"targetX": 80,
"targetY": 300
}targetX/targetY is where the player lands in the target room. Use the room's playerStart position for a natural entry.
Objects are the interactable elements in each room. Two shapes are supported: rect and circle.
{
"id": "potted_plant",
"type": "circle",
"x": 300, "y": 400,
"radius": 18,
"color": "0x44aa44",
"label": "Potted Plant",
"interactable": true,
"blocks": true,
"lookMessage": "A thriving basil plant."
}| Field | Description |
|---|---|
id | Unique string identifier. Used for combines, states, reveals |
type | "rect" or "circle" |
x, y | Center position in the room |
width, height | For rect objects |
radius | For circle objects |
color | Hex fill, e.g. "0x44aa44" |
strokeColor | Optional hex border |
label | Displayed below the object |
interactable | Must be true for clicks to register |
blocks | true makes the object impassable to the player |
mirror | true makes the object reflect the player. Approaching within 200px fades in a mirror image of the player at the object's position (scaled/flipped like the player). Can be set on any object |
hiddenBy | ID of another object. This object is invisible until revealed |
spriteFrame | Sprite name (from the spritesheet's frameTags) or frame index. When set, the object renders as a sprite instead of a colored shape |
stateFrames | Maps state names to sprite names or frame indices for visual state changes (see Sprites) |
alwaysOnTop | true keeps the object rendered above other objects regardless of Y position |
spriteAnim | Animation name (defined in world root animations) to play on the sprite instead of a static frame |
These define what text appears when the player uses a verb on the object.
| Field | Description |
|---|---|
lookMessage | Shown when selecting "Examine" |
useMessage | Shown when selecting "Use" |
openMessage | Shown when selecting "Open" |
talkMessage | Shown when selecting "Talk" (if no dialogTree) |
Each message can be overridden per-state (see States).
The action menu shows these verbs based on object properties:
| Verb | Condition |
|---|---|
| Examine | Always shown (default) |
| Take | Shown when "pickup": true |
| Open | Shown when openMessage is defined |
| Talk | Shown when talkMessage or dialogTree is defined |
| Use | Always shown (default) |
| Use with... | Shown for inventory items only |
Objects with "pickup": true can be taken to the inventory bar at the bottom of the screen. They are removed from the room and added to the player's inventory.
{
"id": "key",
"type": "circle",
"x": 400, "y": 370,
"radius": 8,
"color": "0xffd700",
"label": "Key",
"interactable": true,
"hiddenBy": "table",
"lookMessage": "A small golden key.",
"pickup": true,
"useMessage": "It might fit something around here."
}Objects can have multiple states that change their appearance, messages, and behavior. The default state is defined by "state" on the object.
{
"id": "stove",
"type": "rect",
"x": 600, "y": 400,
"color": "0x555555",
"label": "Stove",
"state": "off",
"lookMessage": "A cast iron stove. It's cold.",
"useMessage": "Nothing to light it with.",
"states": {
"on": {
"lookMessage": "The stove crackles with a warm flame.",
"useMessage": "The fire is already lit."
}
}
}When the stove's state is "off", examine shows "A cast iron stove. It's cold." When the state changes to "on", it shows "The stove crackles with a warm flame."
Each state can also display a full-screen text panel when entered:
"revealed": {
"lookMessage": "The secret is now visible.",
"showPanel": "A revelation washes over you. You finally understand..."
}| Field | Description |
|---|---|
lookMessage | Overrides the object's default examine text while in this state |
useMessage | Overrides the object's default use text while in this state |
showPanel | Full-screen narrative overlay shown when this state is entered |
Object animations are defined in the world root and referenced by name on individual objects.
{
"animations": {
"flicker": { "frames": [0, 1, 2], "frameRate": 8, "repeat": -1 },
"pulse": { "frames": "stoveOn", "frameRate": 5, "repeat": -1 }
}
}| Field | Description |
|---|---|
frames | Array of frame indices OR a sprite name (from frameTags) that expands to its frame range |
frameRate | Frames per second |
repeat | Number of repeats (-1 for infinite) |
Objects use the animation instead of a static sprite:
{
"id": "torch",
"spriteAnim": "flicker",
"lookMessage": "The torch flickers with an eerie blue flame."
}When spriteAnim is set, the object plays the animation in a loop. The animation key is prefixed internally with "obj_" to avoid collisions with player animations.
Objects and doors can use sprite frames from a spritesheet instead of colored shapes. Sprites are loaded from assets/Sprite-0002.png (objects) and assets/Sprite-0001.png (player).
{
"id": "stove",
"type": "rect",
"x": 600, "y": 400,
"spriteFrame": "stove",
"stateFrames": { "on": "stoveOn" }
}When spriteFrame is set, the object renders at 2x scale using that frame from the spritesheet. The type, color, and strokeColor fields are ignored for rendering, but width/height (for rect) or radius (for circle) are still used for hit-testing and pathfinding.
State-based frame changes use stateFrames to switch the displayed frame when the object's state changes. Use stateAnim to play an animation instead:
| Field | Description |
|---|---|
spriteFrame | Sprite name or frame index to render |
stateFrames | Object mapping state names to sprite names or frame indices, e.g. {"on": "stoveOn"} |
stateAnim | Object mapping state names to animation names, e.g. {"on": "pulse"}. Plays the animation when the state is entered |
Values can be either a sprite name (from the spritesheet's frameTags) or a direct numeric frame index. Using names is recommended since they survive spritesheet reordering.
Doors support spriteFrame the same way:
{
"id": "door_living",
"x": 280, "y": 500,
"width": 60, "height": 12,
"spriteFrame": "door",
"targetRoom": "living_room",
"targetX": 400, "targetY": 400
}The player's walk and idle animations are handled separately from object sprites and use frames from Sprite-0001.png (idle: frames 0-1, walk: frames 2-3).
State changes happen through:
- Combines with
"setState"in the result - Open verb with
"openSetsState"on the object
Hidden Objects
Objects can be hidden behind other objects. They only appear when revealed.
The parent object lists children to reveal:
{
"id": "table",
"lookMessage": "A wooden table. There's something underneath it.",
"examineReveals": ["key"]
}The child references the parent:
{
"id": "key",
"hiddenBy": "table",
"lookMessage": "A small golden key.",
"pickup": true
}Revealing happens through two mechanisms:
examineReveals— reveals children when the player examines the parent. Used when the player should discover something by looking (e.g. finding a key under a table).reveals— reveals children when the parent's state changes viasetObjState. Used when the player must perform an action (e.g. using a key on a desk) to reveal the child.
If you want both (examine reveals AND state-change reveals), include both fields.
Items can be combined with other objects or items. From the inventory, select "Use with..." on an item, then click the target object in the room or another inventory item.
Combine results are defined in combineMessages on either object. The key is the other object's id.
{
"id": "stove",
"combineMessages": {
"matches": {
"message": "You light the stove. A warm flame flickers to life.",
"setState": "on"
}
}
}This matches when matches are used on the stove. The result has:
message— displayed textsetState— changes the target object's state
Combine messages can also be plain strings (no setState):
{
"id": "key",
"combineMessages": {
"desk": "The key slides into the lock and turns. The desk drawer swings open."
}
}Plain strings only show the message without changing state. To also change state, the target object should define an object result instead.
The engine checks both directions: if object A has a combineMessages entry for B's ID, that matches. If not, it checks B's entry for A's ID. Object results are preferred over string results, and both are checked with the room object first, then the inventory item.
Combine results can require specific world state conditions:
{
"id": "old_photo",
"combineMessages": {
"stove": {
"requiresState": [{ "id": "stove", "state": "on" }],
"message": "The photo curls and blackens in the flames.",
"setState": "burnt"
},
"matches": {
"message": "You try to burn the photo but the flame is too small."
}
}
}requiresState is an array of {id, state} pairs. ALL conditions must be met for the result to apply. If conditions fail, the engine falls through to the next candidate. If no result matches, nothing happens.
Objects can become doors when state conditions are met. This allows secret passages that unlock via item progression.
{
"id": "statue",
"type": "rect",
"x": 530, "y": 340,
"color": "0x888888",
"label": "Statue",
"becomesDoor": {
"requiresState": [{ "id": "old_photo", "state": "burnt" }],
"targetRoom": "underground",
"targetX": 400,
"targetY": 300,
"openColor": "0x666688",
"message": "The statue grinds and slides aside, revealing dark stairs.",
"openLookMessage": "The statue has shifted aside, revealing a stairway down."
}
}| Field | Description |
|---|---|
requiresState | Conditions that must be true for the door to activate |
targetRoom | Room to transition to |
targetX, targetY | Spawn position in the target room |
openColor | Optional — the object changes to this color when activated |
message | Displayed when the door opens |
openLookMessage | Overrides lookMessage once open |
The door activates when the condition(s) are met by a combine result.
Objects with talkMessage or dialogTree support the Talk verb. A dialogTree creates a branching conversation:
{
"id": "old_man",
"label": "Old Man",
"lookMessage": "An old man with a long grey beard.",
"talkMessage": "He grunts and looks away.",
"dialogTree": {
"start": {
"text": "Evening, stranger.",
"options": [
{ "text": "Who are you?", "next": "who" },
{ "text": "Goodbye.", "next": null }
]
},
"who": {
"text": "I'm the caretaker.",
"options": [
{ "text": "Seen anything strange?", "next": "strange" },
{ "text": "I should go.", "next": null }
]
},
"strange": {
"text": "The study gives me the creeps.",
"options": [
{ "text": "Okay...", "next": null }
]
}
}
}Each node has text (NPC dialog) and options (player responses). Set next: null to end the conversation. dialogStart on the object overrides the default "start" entry point.
Objects with openMessage show the Open verb. If openSetsState is defined, the object's state changes when opened:
{
"id": "cupboard",
"state": "closed",
"lookMessage": "A wooden cupboard with a small drawer.",
"openMessage": "Inside the cupboard: a single matchbox.",
"openSetsState": "open",
"states": {
"open": {
"lookMessage": "The cupboard stands open, its drawer pulled out.",
"openMessage": "Already open."
}
}
}Opening an object can trigger reveals on it via setObjState, making hidden objects appear.
Some objects with "pickup": true are initially placed in rooms but hidden by hiddenBy or sitting in the open. When picked up, they move to the inventory bar and are no longer rendered in any room.
Full-screen overlay panels display narrative text. They can be triggered on game start via the world root, or on any object state change via the showPanel field (see States).
{
"startPanels": [
"A mysterious mansion looms before you...",
"Someone has been here recently."
]
}| Field | Description |
|---|---|
startPanels | Array of texts shown on game load. If more than one, a Continue button advances through them (clicking the overlay dismisses the rest). |
startPanel | Legacy single-panel text, still supported as a fallback when startPanels is absent |
To trigger a panel on a state change, add "showPanel" to the state definition on any object. The panel appears 1.2 seconds after the state transition.
The panel renders a centered bordered text box over a dark overlay. Click anywhere to dismiss it.