fix: Resolve room layout issues for mixed sizes and east/west doors

This commit addresses three critical issues identified during testing:

1. **Fixed mixed room size door alignment (Issue #1)**
   - Updated room positioning logic in positionNorthMultiple, positionSouthMultiple,
     positionEastMultiple, and positionWestMultiple functions
   - Rooms are now positioned centered on their corresponding door positions
   - Ensures doors align properly between different-sized rooms (e.g., 1×1 GU closet
     connecting to 2×1 GU hallway)
   - Uses same door spacing logic as door placement (edgeInset + doorSpacing)

2. **Implemented East/West single-tile doors (Issue #2)**
   - Changed door_side_sheet_32 from image to spritesheet (6 frames, 32×32px each)
   - Updated door sprite creation to use single tile per room for east/west doors
   - West-facing doors (left room) now properly flip horizontally
   - Doors positioned 3 tiles down from top (TILE_SIZE * 2) as specified
   - Updated wall tile removal and animated door creation to use correct dimensions
   - Both sides of east/west connections now use matching door system

3. **Added connection limit validation (Issue #3)**
   - Created validateMultipleConnections function to check if connections fit
   - Validates that multiple connections won't cause rooms to overhang excessively
   - Logs detailed error messages with recommendations when connections don't fit
   - Example: 2GU room can't have 3 north connections (not enough width)
   - Helps developers identify invalid room layouts during scenario design

Files modified:
- js/core/game.js: Load door_side_sheet_32 as spritesheet
- js/core/rooms.js: Fix positioning, add validation
- js/systems/doors.js: Single-tile east/west doors with flipping
- js/systems/collision.js: Update wall tile removal for single-tile doors
This commit is contained in:
Z. Cliffe Schreuders
2025-11-17 08:49:50 +00:00
parent d92a8a5f18
commit 302449d8e1
4 changed files with 196 additions and 51 deletions

View File

@@ -54,7 +54,12 @@ export function preload() {
// Load tileset images referenced by the new Tiled map
this.load.image('office-updated', 'assets/tiles/rooms/room1.png');
this.load.image('door_sheet_32', 'assets/tiles/door_sheet_32.png');
this.load.image('door_side_sheet_32', 'assets/tiles/door_side_sheet_32.png');
// Load side door spritesheet for east/west doors (6 frames: closed, opening, open, etc.)
this.load.spritesheet('door_side_sheet_32', 'assets/tiles/door_side_sheet_32.png', {
frameWidth: 32,
frameHeight: 32
});
// Load table tileset images
this.load.image('desk-ceo1', 'assets/tables/desk-ceo1.png');

View File

@@ -969,6 +969,84 @@ function positionNorthSingle(currentRoom, connectedRoom, currentPos, dimensions)
return alignToGrid(x, y);
}
/**
* Validate if multiple connections can fit in the given direction
* Returns true if valid, false with console error if invalid
*/
function validateMultipleConnections(direction, currentRoom, connectedRooms, currentDim, dimensions) {
if (direction === 'north' || direction === 'south') {
// Check if rooms can fit side-by-side when centered on door positions
const edgeInset = TILE_SIZE * 1.5; // 48px
const availableWidth = currentDim.widthPx - (edgeInset * 2);
const doorCount = connectedRooms.length;
const doorSpacing = availableWidth / (doorCount - 1);
// Calculate total span of rooms when centered on doors
let minX = Infinity;
let maxX = -Infinity;
connectedRooms.forEach((roomId, index) => {
const connectedDim = dimensions[roomId];
const doorX = edgeInset + (doorSpacing * index);
const roomLeft = doorX - (connectedDim.widthPx / 2);
const roomRight = doorX + (connectedDim.widthPx / 2);
minX = Math.min(minX, roomLeft);
maxX = Math.max(maxX, roomRight);
});
const totalSpan = maxX - minX;
const overhang = Math.max(0, totalSpan - currentDim.widthPx);
if (overhang > GRID_UNIT_WIDTH_PX / 2) { // Allow some small overhang (half grid unit)
console.error(`❌ VALIDATION ERROR: Room "${currentRoom}" (${currentDim.gridWidth}×${currentDim.gridHeight} GU, ${currentDim.widthPx}px wide) has ${doorCount} ${direction} connections, but they don't fit!`);
console.error(` Connected rooms total span: ${totalSpan.toFixed(0)}px, overhang: ${overhang.toFixed(0)}px`);
console.error(` Recommendation: Reduce number of connections to ${Math.floor(doorCount * currentDim.widthPx / totalSpan)} or use a wider room (${Math.ceil(totalSpan / GRID_UNIT_WIDTH_PX)}+ GU)`);
connectedRooms.forEach((roomId, index) => {
const dim = dimensions[roomId];
console.error(` - ${roomId}: ${dim.gridWidth}×${dim.gridHeight} GU (${dim.widthPx}px wide)`);
});
return false;
}
} else if (direction === 'east' || direction === 'west') {
// Check if rooms can fit stacked vertically when centered on door positions
const topY = TILE_SIZE * 2;
const bottomY = currentDim.heightPx - (TILE_SIZE * 3);
const doorSpacing = (bottomY - topY) / (connectedRooms.length - 1);
// Calculate total span of rooms when centered on doors
let minY = Infinity;
let maxY = -Infinity;
connectedRooms.forEach((roomId, index) => {
const connectedDim = dimensions[roomId];
const doorY = topY + (doorSpacing * index);
// Door is positioned at 2 tiles from top of room
const roomTop = doorY - (TILE_SIZE * 2);
const roomBottom = roomTop + connectedDim.heightPx;
minY = Math.min(minY, roomTop);
maxY = Math.max(maxY, roomBottom);
});
const totalSpan = maxY - minY;
const overhang = Math.max(0, totalSpan - currentDim.heightPx);
if (overhang > GRID_UNIT_HEIGHT_PX / 2) { // Allow some small overhang (half grid unit)
console.error(`❌ VALIDATION ERROR: Room "${currentRoom}" (${currentDim.gridWidth}×${currentDim.gridHeight} GU, ${currentDim.heightPx}px tall) has ${connectedRooms.length} ${direction} connections, but they don't fit!`);
console.error(` Connected rooms total span: ${totalSpan.toFixed(0)}px, overhang: ${overhang.toFixed(0)}px`);
console.error(` Recommendation: Reduce number of connections or use a taller room`);
connectedRooms.forEach((roomId, index) => {
const dim = dimensions[roomId];
console.error(` - ${roomId}: ${dim.gridWidth}×${dim.gridHeight} GU (${dim.heightPx}px tall)`);
});
return false;
}
}
return true;
}
/**
* Position multiple rooms to the north of current room
*/
@@ -976,26 +1054,30 @@ function positionNorthMultiple(currentRoom, connectedRooms, currentPos, dimensio
const currentDim = dimensions[currentRoom];
const positions = {};
// Calculate total width of all connected rooms
const totalWidth = connectedRooms.reduce((sum, roomId) => {
return sum + dimensions[roomId].widthPx;
}, 0);
// CRITICAL: Position rooms based on where doors will be, not just centering widths
// This ensures doors align properly between different-sized rooms
// Determine starting X position (center the group)
const startX = currentPos.x + (currentDim.widthPx - totalWidth) / 2;
// Calculate where doors will be placed on current room's north wall
// (uses same logic as placeNorthDoorsMultiple in doors.js)
const edgeInset = TILE_SIZE * 1.5; // 48px
const availableWidth = currentDim.widthPx - (edgeInset * 2);
const doorCount = connectedRooms.length;
const doorSpacing = availableWidth / (doorCount - 1);
// Position each room left to right
let currentX = startX;
connectedRooms.forEach(roomId => {
connectedRooms.forEach((roomId, index) => {
const connectedDim = dimensions[roomId];
// Calculate where the door will be on current room's north wall
const doorX = currentPos.x + edgeInset + (doorSpacing * index);
// Center the connected room on this door position
const x = doorX - (connectedDim.widthPx / 2);
// Y position is based on stacking height
const y = currentPos.y - connectedDim.stackingHeightPx;
// Align to grid
positions[roomId] = alignToGrid(currentX, y);
currentX += connectedDim.widthPx;
positions[roomId] = alignToGrid(x, y);
});
return positions;
@@ -1023,26 +1105,30 @@ function positionSouthMultiple(currentRoom, connectedRooms, currentPos, dimensio
const currentDim = dimensions[currentRoom];
const positions = {};
// Calculate total width
const totalWidth = connectedRooms.reduce((sum, roomId) => {
return sum + dimensions[roomId].widthPx;
}, 0);
// CRITICAL: Position rooms based on where doors will be, not just centering widths
// This ensures doors align properly between different-sized rooms
// Determine starting X position (center the group)
const startX = currentPos.x + (currentDim.widthPx - totalWidth) / 2;
// Calculate where doors will be placed on current room's south wall
// (uses same logic as placeSouthDoorsMultiple in doors.js)
const edgeInset = TILE_SIZE * 1.5; // 48px
const availableWidth = currentDim.widthPx - (edgeInset * 2);
const doorCount = connectedRooms.length;
const doorSpacing = availableWidth / (doorCount - 1);
// Position each room left to right
let currentX = startX;
connectedRooms.forEach(roomId => {
connectedRooms.forEach((roomId, index) => {
const connectedDim = dimensions[roomId];
// Calculate where the door will be on current room's south wall
const doorX = currentPos.x + edgeInset + (doorSpacing * index);
// Center the connected room on this door position
const x = doorX - (connectedDim.widthPx / 2);
// Y position below current room
const y = currentPos.y + currentDim.stackingHeightPx;
// Align to grid
positions[roomId] = alignToGrid(currentX, y);
currentX += connectedDim.widthPx;
positions[roomId] = alignToGrid(x, y);
});
return positions;
@@ -1070,18 +1156,29 @@ function positionEastMultiple(currentRoom, connectedRooms, currentPos, dimension
const currentDim = dimensions[currentRoom];
const positions = {};
// CRITICAL: Position rooms based on where doors will be, not just stacking vertically
// This ensures doors align properly between different-sized rooms
// Position to the right
const x = currentPos.x + currentDim.widthPx;
// Stack vertically starting at current Y
let currentY = currentPos.y;
connectedRooms.forEach(roomId => {
// Calculate where doors will be placed on current room's east wall
// (uses same logic as placeEastDoorsMultiple in doors.js)
const topY = currentPos.y + (TILE_SIZE * 2);
const bottomY = currentPos.y + currentDim.heightPx - (TILE_SIZE * 3);
const doorSpacing = (bottomY - topY) / (connectedRooms.length - 1);
connectedRooms.forEach((roomId, index) => {
const connectedDim = dimensions[roomId];
// Align to grid
positions[roomId] = alignToGrid(x, currentY);
// Calculate where the door will be on current room's east wall
const doorY = topY + (doorSpacing * index);
currentY += connectedDim.stackingHeightPx;
// Center the connected room on this door position vertically
const y = doorY - (TILE_SIZE * 2); // Align with door at 2 tiles from top
// Align to grid
positions[roomId] = alignToGrid(x, y);
});
return positions;
@@ -1105,20 +1202,32 @@ function positionWestSingle(currentRoom, connectedRoom, currentPos, dimensions)
* Position multiple rooms to the west of current room
*/
function positionWestMultiple(currentRoom, connectedRooms, currentPos, dimensions) {
const currentDim = dimensions[currentRoom];
const positions = {};
// Stack vertically starting at current Y
let currentY = currentPos.y;
connectedRooms.forEach(roomId => {
// CRITICAL: Position rooms based on where doors will be, not just stacking vertically
// This ensures doors align properly between different-sized rooms
// Calculate where doors will be placed on current room's west wall
// (uses same logic as placeWestDoorsMultiple in doors.js)
const topY = currentPos.y + (TILE_SIZE * 2);
const bottomY = currentPos.y + currentDim.heightPx - (TILE_SIZE * 3);
const doorSpacing = (bottomY - topY) / (connectedRooms.length - 1);
connectedRooms.forEach((roomId, index) => {
const connectedDim = dimensions[roomId];
// Position to the left
const x = currentPos.x - connectedDim.widthPx;
// Align to grid
positions[roomId] = alignToGrid(x, currentY);
// Calculate where the door will be on current room's west wall
const doorY = topY + (doorSpacing * index);
currentY += connectedDim.stackingHeightPx;
// Center the connected room on this door position vertically
const y = doorY - (TILE_SIZE * 2); // Align with door at 2 tiles from top
// Align to grid
positions[roomId] = alignToGrid(x, y);
});
return positions;
@@ -1223,7 +1332,15 @@ export function calculateRoomPositions(gameInstance) {
const gridCoords = worldToGrid(position.x, position.y);
console.log(` ${roomId}: positioned at world(${position.x}, ${position.y}) = grid(${gridCoords.gridX}, ${gridCoords.gridY})`);
} else {
// Multiple room connections
// Multiple room connections - validate first
const currentDim = dimensions[currentRoomId];
const isValid = validateMultipleConnections(direction, currentRoomId, unprocessed, currentDim, dimensions);
if (!isValid) {
console.warn(`⚠️ Skipping invalid connections for ${currentRoomId} ${direction}. Layout may be broken.`);
// Still process them to avoid breaking the game, but with a warning
}
const newPositions = positionMultipleRooms(direction, currentRoomId, unprocessed, currentPos, dimensions);
unprocessed.forEach(roomId => {

View File

@@ -369,8 +369,10 @@ export function removeWallTilesForDoorInRoom(roomId, fromRoomId, direction, door
doorWidth = TILE_SIZE * 2;
doorHeight = TILE_SIZE;
} else if (direction === 'east' || direction === 'west') {
// For east/west connections, calculate Y position based on room configuration
doorY = roomPosition.y + roomHeight / 2; // Center of room
// For east/west connections: single tile per room, positioned 3 tiles down from top
// Doors are 3 tiles down from top (below top 2 wall tiles)
doorY = roomPosition.y + (TILE_SIZE * 2); // 2 tiles down from top (at tile 3)
if (direction === 'east') {
// Original door is east, so new door should be west
doorX = roomPosition.x + TILE_SIZE;
@@ -378,8 +380,9 @@ export function removeWallTilesForDoorInRoom(roomId, fromRoomId, direction, door
// Original door is west, so new door should be east
doorX = roomPosition.x + roomWidth - TILE_SIZE;
}
// Single tile per room for east/west doors
doorWidth = TILE_SIZE;
doorHeight = TILE_SIZE * 2;
doorHeight = TILE_SIZE;
} else {
console.log(`Unknown direction: ${direction}`);
return;

View File

@@ -402,27 +402,46 @@ export function createDoorSpritesForRoom(roomId, position) {
doorPositions.forEach(doorInfo => {
const { x: doorX, y: doorY, direction, connectedRoom } = doorInfo;
// Set door size based on direction
let doorWidth = TILE_SIZE;
let doorHeight = TILE_SIZE * 2;
// Set door size and texture based on direction
let doorWidth, doorHeight, doorTexture, flipX;
if (direction === 'east' || direction === 'west') {
doorWidth = TILE_SIZE * 2;
if (direction === 'north' || direction === 'south') {
// North/South doors: 1 tile wide, 2 tiles tall
doorWidth = TILE_SIZE;
doorHeight = TILE_SIZE * 2;
doorTexture = 'door_32';
flipX = false;
} else {
// East/West doors: 1 tile wide, 1 tile tall (single tile per room)
doorWidth = TILE_SIZE;
doorHeight = TILE_SIZE;
doorTexture = 'door_side_sheet_32';
// West-facing doors (left room) should be flipped horizontally
flipX = (direction === 'west');
}
console.log(`Creating door sprite at (${doorX}, ${doorY}) for ${roomId} -> ${connectedRoom} (${direction})`);
// Create a colored rectangle as a fallback if door texture fails
// Create door sprite with appropriate texture
let doorSprite;
try {
doorSprite = gameRef.add.sprite(doorX, doorY, 'door_32');
doorSprite = gameRef.add.sprite(doorX, doorY, doorTexture);
// Set the initial frame (frame 0 = closed)
doorSprite.setFrame(0);
// Apply horizontal flip for west-facing doors
if (flipX) {
doorSprite.setFlipX(true);
}
} catch (error) {
console.warn(`Failed to create door sprite with 'door_32' texture, creating colored rectangle instead:`, error);
console.warn(`Failed to create door sprite with '${doorTexture}' texture, creating colored rectangle instead:`, error);
// Create a colored rectangle as fallback
const graphics = gameRef.add.graphics();
graphics.fillStyle(0xff0000, 1); // Red color
graphics.fillRect(-TILE_SIZE/2, -TILE_SIZE, TILE_SIZE, TILE_SIZE * 2);
if (direction === 'north' || direction === 'south') {
graphics.fillRect(-TILE_SIZE/2, -TILE_SIZE, TILE_SIZE, TILE_SIZE * 2);
} else {
graphics.fillRect(-TILE_SIZE/2, -TILE_SIZE/2, TILE_SIZE, TILE_SIZE);
}
graphics.setPosition(doorX, doorY);
doorSprite = graphics;
}
@@ -740,7 +759,8 @@ function createAnimatedDoorOnOppositeSide(roomId, fromRoomId, direction, doorWor
doorWidth = TILE_SIZE * 2;
doorHeight = TILE_SIZE;
} else if (direction === 'east' || direction === 'west') {
doorWidth = TILE_SIZE * 2;
// Single tile per room for east/west doors
doorWidth = TILE_SIZE;
doorHeight = TILE_SIZE;
} else {
console.log(`Unknown direction: ${direction}`);