Skip to content

Latest commit

History

27 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

Simple Adventure Engine

A point-and-click adventure engine built in Phaser. Games are defined as JSON files in data/ and selected from a launcher on startup.

Table of Contents

Quick Start

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.

Maker Tool

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 dialogTree conversations 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 .json file 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:

  1. Add an entry to the "rooms" object with objects, doors, and visual properties
  2. Add a door in the starting room that links to it
  3. Reference the room's key in other doors' targetRoom fields

To add a new object:

  1. Choose a unique id
  2. Place it at coordinates within the walkable area (wallThickness + 16 to 800 - wallThickness - 16)
  3. Define its messages, shape, and interaction properties
  4. If it should be hidden, add hiddenBy referencing the parent object's id

All coordinates use the Phaser coordinate system with origin at top-left.

World Structure

Rooms

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": [ ... ]
}
FieldDescription
nameDisplayed in the top-left corner
backgroundColorFill behind the walls
wallColorTint applied to wall tiles
floorColorTint applied to floor tiles
wallThicknessOptional, defaults to 24
floorTileSprite name or frame index for the floor (default "floor")
wallTileSprite name or frame index for walls (default "wallStone")
playerStartWhere the player spawns when entering this room
objectsArray of interactable objects
doorsArray 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

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

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."
}

Common Properties

FieldDescription
idUnique string identifier. Used for combines, states, reveals
type"rect" or "circle"
x, yCenter position in the room
width, heightFor rect objects
radiusFor circle objects
colorHex fill, e.g. "0x44aa44"
strokeColorOptional hex border
labelDisplayed below the object
interactableMust be true for clicks to register
blockstrue makes the object impassable to the player
mirrortrue 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
hiddenByID of another object. This object is invisible until revealed
spriteFrameSprite name (from the spritesheet's frameTags) or frame index. When set, the object renders as a sprite instead of a colored shape
stateFramesMaps state names to sprite names or frame indices for visual state changes (see Sprites)
alwaysOnToptrue keeps the object rendered above other objects regardless of Y position
spriteAnimAnimation name (defined in world root animations) to play on the sprite instead of a static frame

Messages

Messages

These define what text appears when the player uses a verb on the object.

FieldDescription
lookMessageShown when selecting "Examine"
useMessageShown when selecting "Use"
openMessageShown when selecting "Open"
talkMessageShown when selecting "Talk" (if no dialogTree)

Each message can be overridden per-state (see States).

Verbs

The action menu shows these verbs based on object properties:

VerbCondition
ExamineAlways shown (default)
TakeShown when "pickup": true
OpenShown when openMessage is defined
TalkShown when talkMessage or dialogTree is defined
UseAlways shown (default)
Use with...Shown for inventory items only

Pickups

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."
}

States

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..."
}
FieldDescription
lookMessageOverrides the object's default examine text while in this state
useMessageOverrides the object's default use text while in this state
showPanelFull-screen narrative overlay shown when this state is entered

Animations

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 }
}
}
FieldDescription
framesArray of frame indices OR a sprite name (from frameTags) that expands to its frame range
frameRateFrames per second
repeatNumber 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.

Sprites

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:

FieldDescription
spriteFrameSprite name or frame index to render
stateFramesObject mapping state names to sprite names or frame indices, e.g. {"on": "stoveOn"}
stateAnimObject 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:

  1. 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).

  2. reveals — reveals children when the parent's state changes via setObjState. 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.

Combines

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 text
  • setState — 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.

Conditional Combines

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.

Object Doors

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."
}
}
FieldDescription
requiresStateConditions that must be true for the door to activate
targetRoomRoom to transition to
targetX, targetYSpawn position in the target room
openColorOptional — the object changes to this color when activated
messageDisplayed when the door opens
openLookMessageOverrides lookMessage once open

The door activates when the condition(s) are met by a combine result.

Dialogs

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.

OpenVerb and openSetsState

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.

Stock Objects

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.

Text Panels

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."
]
}
FieldDescription
startPanelsArray of texts shown on game load. If more than one, a Continue button advances through them (clicking the overlay dismisses the rest).
startPanelLegacy 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.

About

A point-and-click adventure engine built on Phaser.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Contributors

Languages