gameplay/

Gameplay — Architecture

The entity + behavior pattern, data model, area system, and how the gameplay layer fits on top of the DN 2D core.

Gameplay Layer — Architecture

The gameplay layer adds a data-driven entity + behavior system on top of the DN 2D core. Games are defined as data (areas, entities, behaviors) rather than code. The architecture mirrors the Decentraland framework track — the same design patterns work in both runtimes with different rendering backends.

Core Concept: Entity = Properties + Behaviors

Every interactive object in a game is an entity with two parts:

  • Properties — pure data describing what the entity looks like and where it is. No logic. (x, y, sprite, collision, tags)
  • Behaviors — logic classes attached to the entity. Split into:
    • Interaction behaviors — player-triggered (show a prompt when in range, open a popup on interact). Examples: SellerBehavior, DialogueBehavior, LootBehavior.
    • World behaviors — autonomous per-frame logic, no prompt. Examples: MovementBehavior, HealthBehavior, TriggerZoneBehavior.

This keeps rendering and collision systems simple (they only read properties) while keeping all game logic in swappable behavior classes.

Data Model

EntityDef

The static blueprint for an entity — defined in area data files, never mutated at runtime.

const goblin = {
  id:    'goblin_01',
  type:  'enemy',            // 'npc' | 'item' | 'structure' | 'enemy' | 'trigger' | 'player'
  x: 12, y: 8,              // tile position
  tags:  ['enemy', 'damageable', 'quest_target'],

  // Visual (optional)
  visual: { sprite: SPRITE.GOBLIN, width: 1, height: 1 },

  // Collision (optional)
  collision: { mode: 'solid', width: 1, height: 1 },

  // Behaviors (the interesting part)
  health:   { hp: 30, maxHp: 30, faction: 'goblin', xpReward: 25,
              loot: [{ itemId: 'gold_coin', count: 5 }] },
  movement: { mode: 'wander', speed: 1.5, radius: 4 },
  // (also: dialogue, seller, buyer, crafter, loot, farmPlot, fishing, triggerZone, etc.)
};

AreaDefinition

An area is a tilemap + a list of entity defs. Areas load/unload independently — the player can move between them.

const rpg_world = {
  id:        'rpg_world',
  mapData:   TILE_ARRAY,           // flat tile index array
  mapWidth:  40,
  mapHeight: 30,
  tileSize:  32,

  walkabilityBlocked: [             // tiles the player cannot enter
    { x: 5, y: 3 }, { x: 5, y: 4 },
  ],

  entities: [ goblin, smelter, bjorn_npc, ... ],

  // Optional
  music:   'dungeon_theme.mp3',
  ambientLight: 0.4,
};

The Five Systems

1. AreaLoader (areaLoader.js)

Reads an AreaDefinition, spawns all entity instances, runs the update/render loop. Handles:

  • Entity proximity detection (shows interaction prompts)
  • Canvas rendering (tiles + entity sprites + UI overlays)
  • Click-to-interact (click_entity mode)
  • Area transitions via TransportBehavior
const loader = new AreaLoader({ canvas, ctx, gameMgr });
loader.loadArea(rpg_world);

2. Behaviors (behaviors.js)

All behavior classes live here. Each behavior class:

  • Accepts a config object matching its BehaviorDef interface
  • Implements update(entity, playerPos, gameMgr) — called each frame (world behaviors)
  • Implements interact(entity, playerPos, gameMgr) — called when player presses E (interaction behaviors)
  • Optionally exposes promptText — the label shown above the entity when in range

3. PopupManager + PopupRenderer (popupManager.js, popupRenderer.js)

Manages popup state (only one popup open at a time). The renderer produces HTML overlaid on the canvas. Popup types: loot, choice, crafting, farm_plot, fishing, notice_board, interactive.

The interactive popup uses an adaptive tab system: only tabs that match the entity’s behaviors appear (Talk / Missions / Craft / Refine / Buy / Sell).

4. Inventory + Economy (inventory.js, marketManager.js)

PlayerInventory holds items, currencies, and player stats. Currencies (like gold) are registered separately from items — they support fractional amounts and have their own display logic.

MarketManager holds currency exchange rates and getPriceFor(itemId) — used by SellerBehavior and BuyerBehavior when priceMode: 'dynamic'.

5. QuestManager (questState.js)

Multi-phase quest state machine. See Gameplay — Quests for full details.

Area Loading and Unloading

Areas load and unload cleanly. Entity instances (with any mutable state, like HP or growth progress) are destroyed on unload. Entity definitions stay in the area definition — when the area reloads, entities start fresh unless their state is saved via gameMgr.saveGame().

loader.unloadArea();
loader.loadArea(village_exterior);   // transition to a new area

AABB Collision

The 2D collision system uses AABB (axis-aligned bounding boxes) for solid entities:

  • collision.mode === 'solid' — player is pushed out on overlap (wall, furniture, NPC)
  • collision.mode === 'walkable' — no blocking, but isWalkable() check still applies
  • walkabilityBlocked on the area definition — specific tiles blocked regardless of entity collision

Collision resolves per-axis: the player slides along walls instead of stopping dead.

Game Manager (game.js in each game)

GameManager (or gameMgr) is the central singleton each game builds. It owns:

  • PlayerInventory
  • QuestManager
  • flags — a Map<string, boolean> for world state (has the bridge been rebuilt? has the boss been defeated?)
  • saveGame() / loadGame() — localStorage persistence
  • Player stats (HP, shield, XP, level, equipment slots)
  • playerAttack(), takeDamage(), transport(destination)

The framework provides the building blocks; each game’s game.js wires them together for that game’s specific needs.