diff --git a/src/main.js b/src/main.js index 1a3b98c..b944495 100644 --- a/src/main.js +++ b/src/main.js @@ -356,6 +356,43 @@ class HouseScene extends Phaser.Scene { window.addEventListener('pagehide', () => this.persist(), { once: true }) } + createPolygonMask(points) { + const shape = this.make.graphics({ add: false }) + shape.fillStyle(0xffffff, 1) + shape.beginPath() + shape.moveTo(points[0].x, points[0].y) + + for (let i = 1; i < points.length; i += 1) { + shape.lineTo(points[i].x, points[i].y) + } + + shape.closePath() + shape.fillPath() + + return shape.createGeometryMask() + } + + addMaskedTile(room, texture, points, depth, tileScale = 0.7, tint = null) { + const tile = this.add.tileSprite( + room.x + 7, + room.y + 7, + room.width - 14, + room.height - 14, + texture + ) + .setOrigin(0, 0) + .setDepth(depth) + + tile.setTileScale(tileScale, tileScale) + tile.setMask(this.createPolygonMask(points)) + + if (tint !== null) { + tile.setTint(tint) + } + + return tile + } + drawHouse() { const graphics = this.add.graphics().setDepth(0) @@ -371,66 +408,119 @@ class HouseScene extends Phaser.Scene { for (const room of ROOMS) { const skin = ROOM_SKINS[room.id] const inset = 7 - const wallTop = room.y + 8 - const wallBottom = room.walkBand.minY - const wallHeight = Math.max(36, wallBottom - wallTop) - const floorTop = wallBottom - 1 - const floorHeight = room.y + room.height - floorTop - 7 + const sideInset = Math.round(room.width * 0.18) + const wallTop = room.y + 9 + const backTop = wallTop + Math.max(10, Math.round(FLOOR_H * 0.04)) + const horizonY = room.walkBand.minY + const bottomY = room.y + room.height - 8 + const left = room.x + inset + const right = room.x + room.width - inset + const backLeft = room.x + sideInset + const backRight = room.x + room.width - sideInset - // Base room backing gives clean rounded corners behind all textures. + // Base backing remains rectangular so room selection and hold feedback + // always have a clean silhouette. graphics.fillStyle(room.color, 1) graphics.fillRoundedRect(room.x, room.y, room.width, room.height, 14) - // Wallpaper layer. - const wallpaper = this.add.tileSprite( - room.x + inset, - wallTop, - room.width - inset * 2, - wallHeight, + // Back wall: deliberately narrower than the room to create depth. + const backWall = this.add.tileSprite( + backLeft, + backTop, + backRight - backLeft, + Math.max(30, horizonY - backTop), skin.wallpaper ) .setOrigin(0, 0) .setDepth(1) - wallpaper.setTileScale(0.72, 0.72) + backWall.setTileScale(0.67, 0.67) - // Separate floor layer so the character always stands on a readable surface. - const floor = this.add.tileSprite( - room.x + inset, - floorTop, - room.width - inset * 2, - floorHeight, - skin.floor + // Angled side walls. They use the same wallpaper with subtle opposite + // tints so the corner reads clearly without needing baked perspective art. + this.addMaskedTile( + room, + skin.wallpaper, + [ + { x: left, y: wallTop }, + { x: backLeft, y: backTop }, + { x: backLeft, y: horizonY }, + { x: left, y: horizonY + Math.round(FLOOR_H * 0.08) } + ], + 2, + 0.67, + 0xffeaf3 ) - .setOrigin(0, 0) - .setDepth(2) - floor.setTileScale(0.62, 0.62) + this.addMaskedTile( + room, + skin.wallpaper, + [ + { x: backRight, y: backTop }, + { x: right, y: wallTop }, + { x: right, y: horizonY + Math.round(FLOOR_H * 0.08) }, + { x: backRight, y: horizonY } + ], + 2, + 0.67, + 0xf0f4ff + ) - // Shared architecture layer sits above both wallpaper and floor. + // Perspective floor: narrow at the back, full-width at the front. + this.addMaskedTile( + room, + skin.floor, + [ + { x: backLeft, y: horizonY }, + { x: backRight, y: horizonY }, + { x: right, y: bottomY }, + { x: left, y: bottomY } + ], + 3, + 0.54 + ) + + // Ceiling and skirting architecture. The central trims stay straight, + // while perspective guide lines make the side walls/floor read in 3D. this.add.image( room.x + room.width / 2, - room.y + 10, + backTop, 'architecture-ceilingTrim' ) - .setDisplaySize(room.width - 12, 18) - .setDepth(5) + .setDisplaySize(backRight - backLeft + 8, 16) + .setDepth(6) this.add.image( room.x + room.width / 2, - floorTop, + horizonY, 'architecture-skirting' ) - .setDisplaySize(room.width - 12, 14) - .setDepth(5) + .setDisplaySize(backRight - backLeft + 8, 13) + .setDepth(6) this.add.image( room.x + room.width / 2, - room.y + room.height - 7, + bottomY, 'architecture-floorEdge' ) - .setDisplaySize(room.width - 10, 16) - .setDepth(5) + .setDisplaySize(room.width - 10, 15) + .setDepth(6) + + graphics.lineStyle(3, 0xd2aabf, 0.95) + + // Rear vertical corners. + graphics.lineBetween(backLeft, backTop, backLeft, horizonY) + graphics.lineBetween(backRight, backTop, backRight, horizonY) + + // Ceiling perspective lines. + graphics.lineBetween(left, wallTop, backLeft, backTop) + graphics.lineBetween(right, wallTop, backRight, backTop) + + // Floor/skirting perspective lines. + graphics.lineBetween(left, horizonY + Math.round(FLOOR_H * 0.08), backLeft, horizonY) + graphics.lineBetween(right, horizonY + Math.round(FLOOR_H * 0.08), backRight, horizonY) + graphics.lineBetween(left, bottomY, backLeft, horizonY) + graphics.lineBetween(right, bottomY, backRight, horizonY) graphics.lineStyle(4, 0xd5b8c6, 1) graphics.strokeRoundedRect(room.x, room.y, room.width, room.height, 14) @@ -440,21 +530,21 @@ class HouseScene extends Phaser.Scene { fontSize: '16px', color: '#765466', fontStyle: 'bold', - backgroundColor: 'rgba(255,255,255,0.72)', + backgroundColor: 'rgba(255,255,255,0.78)', padding: { x: 6, y: 3 } - }).setDepth(8) + }).setDepth(9) if (room.id === 'disco') { this.add.text(room.x + room.width - 48, room.y + 18, '♪ ✦', { fontSize: '18px', color: '#9b65c6' - }).setDepth(8) + }).setDepth(9) } if (room.id === 'garage') { this.add.text(room.x + room.width - 44, room.y + 18, '🚗', { fontSize: '18px' - }).setDepth(8) + }).setDepth(9) } } @@ -1101,7 +1191,7 @@ const game = new Phaser.Game({ }) window.__DOLLHOUSE__ = { - version: '0.5.0', + version: '0.6.0', game, async resetAll() { await clearGameState()