Files
BreakEscape/planning_notes/room-loading/README_ROOM_LOADING.md

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`