mirror of
https://github.com/cliffe/BreakEscape.git
synced 2026-02-20 13:50:46 +00:00
575 lines
16 KiB
Markdown
575 lines
16 KiB
Markdown
# Room Loading System Design
|
|
|
|
## Overview
|
|
|
|
The room loading system in BreakEscape coordinates two distinct data sources to create a complete room experience:
|
|
|
|
1. **Scenario JSON Files** (e.g., `ceo_exfil.json`) - Define game logic, item properties, and game state
|
|
2. **Tiled Map JSON Files** (e.g., `room_reception2.json`) - Define visual layout, sprite positions, and room structure
|
|
|
|
This document explains how these systems work together to load and render rooms.
|
|
|
|
---
|
|
|
|
## Architecture Overview
|
|
|
|
### Data Flow Diagram
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ Scenario JSON │
|
|
│ (rooms → room data → objects with properties) │
|
|
└──────────────┬──────────────────────────────────────────────────┘
|
|
│ Contains: name, type, takeable, readable, etc.
|
|
│
|
|
▼
|
|
┌──────────────────────┐
|
|
│ Matching Algorithm │
|
|
│ (Type-based lookup) │
|
|
└──────────┬───────────┘
|
|
│
|
|
┌──────────┴──────────┐
|
|
│ │
|
|
▼ ▼
|
|
┌─────────────────────┐ ┌──────────────────┐
|
|
│ Tiled Map Items │ │ Scene Objects │
|
|
│ (Position & Sprite)│ │ (Properties) │
|
|
└─────────────────────┘ └──────────────────┘
|
|
│ │
|
|
│ Merge Properties │
|
|
└─────────────┬───────┘
|
|
▼
|
|
┌──────────────────────┐
|
|
│ Final Game Object │
|
|
│ (Position + Props) │
|
|
└──────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Room Loading Process
|
|
|
|
### 1. **Initialization Phase**
|
|
|
|
When the game starts, the following steps occur:
|
|
|
|
1. **Scenario Loading**: `window.gameScenario` is populated with scenario data from the selected scenario JSON file
|
|
2. **Tilemap Preloading**: Tiled map files are preloaded in the Phaser scene's `preload()` function
|
|
3. **Room Position Calculation**: Room positions are calculated based on connections and layout
|
|
|
|
### 2. **Lazy Loading**
|
|
|
|
Rooms are loaded on-demand when:
|
|
- The player moves close to an adjacent room (detected via door sprite proximity)
|
|
- Eventually, this will be determined by a remote API request
|
|
|
|
```javascript
|
|
function loadRoom(roomId) {
|
|
const gameScenario = window.gameScenario;
|
|
const roomData = gameScenario.rooms[roomId];
|
|
const position = window.roomPositions[roomId];
|
|
|
|
createRoom(roomId, roomData, position);
|
|
revealRoom(roomId);
|
|
}
|
|
```
|
|
|
|
### 3. **Room Creation Phase**
|
|
|
|
The `createRoom()` function orchestrates the complete room setup:
|
|
|
|
#### Step 3a: Load Tilemap
|
|
- Create a Phaser tilemap from the preloaded Tiled JSON data
|
|
- Add tileset images to the map
|
|
- Initialize room data structure: `rooms[roomId]`
|
|
|
|
#### Step 3b: Create Tile Layers
|
|
- Iterate through all layers in the Tiled map (floor, walls, collision, etc.)
|
|
- Create sprite layers for each, positioned at the room's world coordinates
|
|
- Set depth values based on the Depth Layering Philosophy
|
|
- Skip the "doors" layer (handled by sprite-based doors system)
|
|
|
|
#### Step 3c: Create Door Sprites
|
|
- Parse scenario room connections
|
|
- Create interactive door sprites at appropriate positions
|
|
- Doors serve as transition triggers to adjacent rooms
|
|
|
|
#### Step 3d: Process Tiled Object Layers
|
|
The system processes five object layers from the Tiled map:
|
|
- **tables** - Static table/furniture objects that don't move
|
|
- **table_items** - Items placed on tables (phones, keyboards, etc.)
|
|
- **conditional_items** - Items in the main space that may be scenario-specific
|
|
- **conditional_table_items** - Table items that may be scenario-specific
|
|
- **items** - Regular items in the room (plants, chairs, etc.)
|
|
|
|
#### Step 3e: Match and Merge Objects
|
|
This is the **critical matching phase**:
|
|
|
|
1. **Collect Available Sprites**: Extract all objects from Tiled layers, organized by type
|
|
2. **Process Scenario Objects**: For each object defined in the scenario:
|
|
- Extract the object type (e.g., "key", "notes", "phone")
|
|
- Search for matching visual representation in this priority:
|
|
1. Regular items layer (items)
|
|
2. Conditional items layer (conditional_items)
|
|
3. Conditional table items layer (conditional_table_items)
|
|
- **Merge Properties**: Apply scenario properties to the matched sprite
|
|
- **Mark as Used**: Track which Tiled items have been consumed
|
|
3. **Process Remaining Sprites**: Create sprites for unused Tiled items with default properties
|
|
|
|
---
|
|
|
|
## Matching Algorithm
|
|
|
|
### Type-Based Matching
|
|
|
|
The system uses a **type-based matching** approach where each scenario object is matched to a Tiled sprite by type:
|
|
|
|
```
|
|
Scenario Object: { type: "key", name: "Office Key", takeable: true, ... }
|
|
▼
|
|
Search for matching type
|
|
▼
|
|
Tiled Item: { gid: 243, imageName: "key", x: 100, y: 150 }
|
|
▼
|
|
Match Found! Merge:
|
|
▼
|
|
Final Object: {
|
|
imageName: "key",
|
|
x: 100, y: 150, // Position from Tiled
|
|
name: "Office Key", // Name from Scenario
|
|
takeable: true, // Properties from Scenario
|
|
observations: "..."
|
|
}
|
|
```
|
|
|
|
### Image Name Extraction
|
|
|
|
The system extracts the base type from Tiled object image names:
|
|
|
|
```javascript
|
|
function extractBaseTypeFromImageName(imageName) {
|
|
// Examples:
|
|
// "key.png" → "key"
|
|
// "phone5.png" → "phone"
|
|
// "notes3.png" → "notes"
|
|
// "plant-large1.png" → "plant"
|
|
}
|
|
```
|
|
|
|
### Matching Priority
|
|
|
|
When looking for a Tiled sprite to match a scenario object:
|
|
|
|
1. **Regular Items Layer** - First choice (most commonly used items)
|
|
2. **Conditional Items Layer** - For items that might not always be present
|
|
3. **Conditional Table Items Layer** - For table-specific scenario items
|
|
|
|
This priority allows flexibility in where visual assets are placed while maintaining predictable matching behavior.
|
|
|
|
---
|
|
|
|
## Object Layer Details
|
|
|
|
### Table Structure (From Tiled)
|
|
|
|
**Purpose**: Define base furniture objects (desks, tables, etc.)
|
|
|
|
```json
|
|
{
|
|
"gid": 118,
|
|
"height": 47,
|
|
"name": "",
|
|
"rotation": 0,
|
|
"type": "",
|
|
"visible": true,
|
|
"width": 174,
|
|
"x": 75.67,
|
|
"y": 89.67
|
|
}
|
|
```
|
|
|
|
**Processing**:
|
|
- Tables are processed first to establish base positions
|
|
- Groups are created for table + table_items organization
|
|
- Tables act as anchor points for table items
|
|
|
|
### Table Items Structure (From Tiled)
|
|
|
|
**Purpose**: Items that should visually appear on or near tables
|
|
|
|
```json
|
|
{
|
|
"gid": 358,
|
|
"height": 23,
|
|
"name": "",
|
|
"x": 86,
|
|
"y": 64.5
|
|
}
|
|
```
|
|
|
|
**Processing**:
|
|
- Grouped with their closest table
|
|
- Set to same depth as table + slight offset for proper ordering
|
|
- Sorted north-to-south (lower Y values first)
|
|
|
|
### Conditional Items Structure (From Tiled)
|
|
|
|
**Purpose**: Items that appear conditionally based on scenario
|
|
|
|
```json
|
|
{
|
|
"gid": 227,
|
|
"name": "",
|
|
"x": 13.5,
|
|
"y": 51
|
|
}
|
|
```
|
|
|
|
**Processing**:
|
|
- Available for scenario matching
|
|
- Only rendered if a scenario object matches them
|
|
- Otherwise ignored (not rendered in the room)
|
|
|
|
### Items Structure (From Tiled)
|
|
|
|
**Purpose**: Always-present background objects (plants, chairs, etc.)
|
|
|
|
```json
|
|
{
|
|
"gid": 176,
|
|
"height": 21,
|
|
"name": "",
|
|
"x": 197.67,
|
|
"y": 45.67
|
|
}
|
|
```
|
|
|
|
**Processing**:
|
|
- Most numerous layer
|
|
- Rendered unless consumed by scenario matching
|
|
- Provide visual richness to the room
|
|
|
|
---
|
|
|
|
## Depth Layering Philosophy
|
|
|
|
All depth calculations use: **World Y Position + Layer Offset**
|
|
|
|
### Room Layers
|
|
|
|
```
|
|
Depth Priority (lowest to highest):
|
|
1. Floor: roomWorldY + 0.1
|
|
2. Collision: roomWorldY + 0.15
|
|
3. Walls: roomWorldY + 0.2
|
|
4. Props: roomWorldY + 0.3
|
|
5. Other: roomWorldY + 0.4
|
|
```
|
|
|
|
### Interactive Elements
|
|
|
|
```
|
|
Depth Priority (lowest to highest):
|
|
1. Doors: doorY + 0.45
|
|
2. Door Tops: doorY + 0.55
|
|
3. Animated Doors: doorBottomY + 0.45
|
|
4. Animated Door Tops: doorBottomY + 0.55
|
|
5. Player: playerBottomY + 0.5
|
|
6. Objects: objectBottomY + 0.5
|
|
```
|
|
|
|
**Key Principle**: The deeper (higher Y position) an object is in the room, the higher its depth value, ensuring natural layering.
|
|
|
|
---
|
|
|
|
## Property Application Flow
|
|
|
|
When a scenario object is matched to a Tiled sprite:
|
|
|
|
```javascript
|
|
// 1. Find matching Tiled sprite
|
|
const usedItem = regularItemsByType[scenarioObj.type].shift();
|
|
|
|
// 2. Create sprite at Tiled position
|
|
const sprite = gameRef.add.sprite(
|
|
Math.round(position.x + usedItem.x),
|
|
Math.round(position.y + usedItem.y - usedItem.height),
|
|
imageName
|
|
);
|
|
|
|
// 3. Apply scenario properties
|
|
sprite.scenarioData = scenarioObj;
|
|
sprite.interactable = true;
|
|
sprite.name = scenarioObj.name;
|
|
sprite.objectId = `${roomId}_${scenarioObj.type}_${index}`;
|
|
|
|
// 4. Apply visual properties from Tiled
|
|
if (usedItem.rotation) {
|
|
sprite.setRotation(Phaser.Math.DegToRad(usedItem.rotation));
|
|
}
|
|
|
|
// 5. Set depth and elevation
|
|
const objectBottomY = sprite.y + sprite.height;
|
|
const objectDepth = objectBottomY + 0.5 + elevation;
|
|
sprite.setDepth(objectDepth);
|
|
```
|
|
|
|
---
|
|
|
|
## Handling Missing Matches
|
|
|
|
If a scenario object has no matching Tiled sprite:
|
|
|
|
1. Create sprite at a **random valid position** in the room
|
|
2. Use the object type as the sprite name
|
|
3. Apply all scenario properties normally
|
|
4. Log a warning for debugging
|
|
|
|
**Fallback Position Logic**:
|
|
- Generate random coordinates within the room bounds
|
|
- Exclude padding areas (edge of room)
|
|
- Verify no overlap with existing objects
|
|
- Maximum 50 attempts before placement
|
|
|
|
---
|
|
|
|
## Room Visibility and Rendering
|
|
|
|
### Visibility State
|
|
|
|
1. **Hidden Initially**: All room elements are created but hidden (`setVisible(false)`)
|
|
2. **Revealed on Load**: When `revealRoom()` is called, elements become visible
|
|
3. **Controlled Updates**: Visibility changes based on player proximity and game state
|
|
|
|
### Room Reveal Logic
|
|
|
|
```javascript
|
|
function revealRoom(roomId) {
|
|
const room = rooms[roomId];
|
|
|
|
// Show all layers
|
|
Object.values(room.layers).forEach(layer => {
|
|
layer.setVisible(true);
|
|
layer.setAlpha(1);
|
|
});
|
|
|
|
// Show all objects
|
|
Object.values(room.objects).forEach(obj => {
|
|
obj.setVisible(true);
|
|
});
|
|
|
|
// Show door sprites
|
|
room.doorSprites.forEach(door => {
|
|
door.setVisible(true);
|
|
});
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Item Tracking and De-duplication
|
|
|
|
The system prevents the same visual sprite from being used twice through the `usedItems` Set:
|
|
|
|
```javascript
|
|
const usedItems = new Set();
|
|
|
|
// After using a sprite:
|
|
usedItems.add(imageName); // Full image name
|
|
usedItems.add(baseType); // Base type (key, phone, etc.)
|
|
|
|
// Before processing a Tiled sprite:
|
|
if (usedItems.has(imageName) || usedItems.has(baseType)) {
|
|
// Skip this sprite - already used
|
|
continue;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Example: Complete Scenario Object Processing
|
|
|
|
### Input: Scenario Definition
|
|
|
|
```json
|
|
{
|
|
"type": "key",
|
|
"name": "Office Key",
|
|
"takeable": true,
|
|
"key_id": "office1_key:40,35,38,32,10",
|
|
"observations": "A key to access the office areas"
|
|
}
|
|
```
|
|
|
|
### Input: Tiled Map Layer
|
|
|
|
```json
|
|
{
|
|
"name": "items",
|
|
"objects": [
|
|
{
|
|
"gid": 243,
|
|
"height": 21,
|
|
"width": 12,
|
|
"x": 100,
|
|
"y": 150
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
### Processing Steps
|
|
|
|
1. **Extract Type**: `scenarioObj.type = "key"`
|
|
2. **Extract Image**: `getImageNameFromObject(tiledObj)` → `"key"`
|
|
3. **Match**: Find tiled object with base type "key"
|
|
4. **Create Sprite**: At position (100, 150) with image "key.png"
|
|
5. **Merge Data**:
|
|
- Position: (100, 150) ← from Tiled
|
|
- Visual: "key.png" ← from Tiled
|
|
- Name: "Office Key" ← from Scenario
|
|
- Properties: takeable, key_id, observations ← from Scenario
|
|
6. **Set Depth**: Based on Y position and room layout
|
|
7. **Store**: In `rooms[roomId].objects[objectId]`
|
|
|
|
### Output: Interactive Game Object
|
|
|
|
```javascript
|
|
{
|
|
x: 100,
|
|
y: 150,
|
|
sprite: "key.png",
|
|
name: "Office Key",
|
|
type: "key",
|
|
takeable: true,
|
|
key_id: "office1_key:40,35,38,32,10",
|
|
observations: "A key to access the office areas",
|
|
interactive: true,
|
|
scenarioData: {...}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Collision and Physics
|
|
|
|
### Wall Collision
|
|
|
|
- Walls layer defines immovable boundaries
|
|
- Thin collision boxes created for each wall tile
|
|
- Player cannot pass through walls
|
|
|
|
### Door Transitions
|
|
|
|
- Door sprites detect player proximity
|
|
- When player is close enough, `loadRoom()` is triggered
|
|
- Adjacent room is loaded and revealed
|
|
|
|
### Object Interactions
|
|
|
|
- Interactive objects are clickable
|
|
- Interaction radius is defined by `INTERACTION_RANGE_SQ`
|
|
- Objects trigger appropriate minigames or dialogs
|
|
|
|
---
|
|
|
|
## Constants and Configuration
|
|
|
|
### Key Constants (from `js/utils/constants.js`)
|
|
|
|
```javascript
|
|
const TILE_SIZE = 32; // Base tile size in pixels
|
|
const DOOR_ALIGN_OVERLAP = 64; // Door alignment overlap
|
|
const GRID_SIZE = 32; // Grid size for pathfinding
|
|
const INTERACTION_RANGE_SQ = 5000; // Squared interaction range
|
|
const INTERACTION_CHECK_INTERVAL = 100; // Check interval in ms
|
|
```
|
|
|
|
### Object Scales (from `js/core/rooms.js`)
|
|
|
|
```javascript
|
|
const OBJECT_SCALES = {
|
|
'notes': 0.75,
|
|
'key': 0.75,
|
|
'phone': 1,
|
|
'tablet': 0.75,
|
|
'bluetooth_scanner': 0.7
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## Performance Considerations
|
|
|
|
### Lazy Loading Benefits
|
|
|
|
- Only rooms near the player are loaded
|
|
- Reduces memory usage and draw calls
|
|
- Faster initial game load time
|
|
|
|
### Optimization Strategies
|
|
|
|
1. **Layer Caching**: Tile layers are only created once per room
|
|
2. **Sprite Pooling**: Reuse sprites when possible (future optimization)
|
|
3. **Depth Sorting**: Calculated once at load time, updated when needed
|
|
4. **Visibility Culling**: Rooms far from player are not rendered
|
|
|
|
---
|
|
|
|
## Debugging and Logging
|
|
|
|
The system provides comprehensive console logging:
|
|
|
|
```javascript
|
|
console.log(`Creating room ${roomId} of type ${roomData.type}`);
|
|
console.log(`Collected ${layerName} layer with ${objects.length} objects`);
|
|
console.log(`Created ${objType} using ${imageName}`);
|
|
console.log(`Applied scenario data to ${objType}:`, scenarioObj);
|
|
```
|
|
|
|
Enable the browser console to see detailed room loading information.
|
|
|
|
---
|
|
|
|
## API Reference
|
|
|
|
### Main Functions
|
|
|
|
#### `loadRoom(roomId)`
|
|
- Loads a room from the scenario and Tiled map
|
|
- Called by door transition system
|
|
- Parameters: `roomId` (string)
|
|
|
|
#### `createRoom(roomId, roomData, position)`
|
|
- Creates all room elements (layers, objects, doors)
|
|
- Coordinates the complete room setup
|
|
- Parameters: `roomId`, `roomData` (scenario), `position` {x, y}
|
|
|
|
#### `revealRoom(roomId)`
|
|
- Makes room elements visible to the player
|
|
- Called after room creation completes
|
|
- Parameters: `roomId` (string)
|
|
|
|
#### `processScenarioObjectsWithConditionalMatching(roomId, position, objectsByLayer)`
|
|
- Internal: Matches scenario objects to Tiled sprites
|
|
- Returns: Set of used item identifiers
|
|
|
|
---
|
|
|
|
## Future Improvements
|
|
|
|
1. **Remote Room Loading**: Replace local scenario with API calls
|
|
2. **Dynamic Item Placement**: Algorithm-based positioning instead of Tiled layer placement
|
|
3. **Item Pooling**: Reuse sprite objects for better performance
|
|
4. **Streaming LOD**: Load distant rooms at reduced detail
|
|
5. **Narrative-based Visibility**: Show/hide items based on story state
|
|
|
|
---
|
|
|
|
## Related Files
|
|
|
|
- **Scenario Format**: See `README_scenario_design.md`
|
|
- **Tiled Map Format**: Tiled Editor documentation
|
|
- **Game State**: `js/systems/inventory.js`, `js/systems/interactions.js`
|
|
- **Visual Rendering**: `js/systems/object-physics.js`, `js/systems/player-effects.js`
|