diff --git a/README.md b/README.md index db15329..2d918d3 100644 --- a/README.md +++ b/README.md @@ -4,14 +4,16 @@ A small Phaser.js PWA built around the dollhouse concept art created in ChatGPT. ## Current prototype -- Four playable rooms: bedroom, bathroom, kitchen and living room +- Single open two-storey dollhouse environment +- Bedroom, bathroom, hall/stairs, kitchen, living and play spaces visible together - Keyboard and touch movement -- Furniture/prop dragging in Decorate mode -- Add/reset room controls -- Room layout and player position persisted in IndexedDB -- Offline-capable PWA +- Stair interaction to move between floors +- Pastel furniture sprite set layered over the house +- Furniture dragging in Decorate mode +- Add/reset furniture controls +- House layout and player position persisted in IndexedDB +- Offline-capable installable PWA - Service worker configured to take new versions automatically -- Runtime crops extracted from the production concept sheet so gameplay code is independent from future final sprite atlases ## Development @@ -28,7 +30,6 @@ npm run build The Dockerfile builds the Vite app and serves it with nginx on port 80. `/health` is provided for Coolify health checks. - ## Deployment Coolify tracks the `main` branch. The installed PWA is configured to take service-worker updates automatically after a new deployment. diff --git a/src/main.js b/src/main.js index f09c9d0..65abed3 100644 --- a/src/main.js +++ b/src/main.js @@ -33,61 +33,48 @@ window.addEventListener('appinstalled', () => { }) const WIDTH = 960 -const HEIGHT = 640 -const ROOM_X = 30 -const ROOM_Y = 78 -const ROOM_W = 900 -const ROOM_H = 470 -const FLOOR_TOP = 385 -const FLOOR_BOTTOM = 525 +const HEIGHT = 720 -const ROOM_CROPS = { - bedroom: { x: 15, y: 425, w: 248, h: 182 }, - bathroom: { x: 270, y: 425, w: 241, h: 182 }, - kitchen: { x: 521, y: 425, w: 239, h: 182 }, - living: { x: 774, y: 425, w: 241, h: 182 } +const HOUSE_BOUNDS = { + left: 38, + right: 922, + upper: { minY: 270, maxY: 354, homeY: 336 }, + lower: { minY: 515, maxY: 610, homeY: 585 } } -const ASSET_CROPS = { - player: { x: 17, y: 128, w: 63, h: 102 }, - bed: { x: 15, y: 671, w: 79, h: 113 }, - couch: { x: 540, y: 675, w: 101, h: 99 }, - armchair: { x: 644, y: 680, w: 67, h: 91 }, - table: { x: 708, y: 694, w: 68, h: 78 }, - bookcase: { x: 847, y: 675, w: 61, h: 106 }, - teddy: { x: 16, y: 839, w: 49, h: 53 }, - ball: { x: 224, y: 842, w: 46, h: 49 } +const STAIRS_X = 675 +const PLAYER_CROP = { x: 17, y: 128, w: 63, h: 102 } + +const FURNITURE_CROPS = { + bed: { x: 28, y: 68, w: 392, h: 425 }, + couch: { x: 449, y: 143, w: 545, h: 337 }, + armchair: { x: 1025, y: 132, w: 355, h: 365 }, + table: { x: 32, y: 686, w: 440, h: 255 }, + bookcase: { x: 554, y: 516, w: 346, h: 454 }, + wardrobe: { x: 1036, y: 497, w: 353, h: 482 } } -const ROOM_LABELS = { - bedroom: 'Bedroom', - bathroom: 'Bathroom', - kitchen: 'Kitchen', - living: 'Living Room' +const FURNITURE_ORDER = ['bed', 'couch', 'armchair', 'table', 'bookcase', 'wardrobe'] +const FURNITURE_NAMES = { + bed: 'Bed', + couch: 'Sofa', + armchair: 'Chair', + table: 'Table', + bookcase: 'Shelf', + wardrobe: 'Wardrobe' } const DEFAULT_STATE = { - version: 1, - activeRoom: 'bedroom', - player: { x: 480, y: 492 }, - rooms: { - bedroom: [ - { id: 'bed-1', type: 'bed', x: 260, y: 460, scale: 1.15 }, - { id: 'bookcase-1', type: 'bookcase', x: 745, y: 445, scale: 1.05 }, - { id: 'teddy-1', type: 'teddy', x: 600, y: 500, scale: 1.15 } - ], - bathroom: [ - { id: 'ball-1', type: 'ball', x: 700, y: 500, scale: 1.15 } - ], - kitchen: [ - { id: 'table-1', type: 'table', x: 575, y: 470, scale: 1.2 } - ], - living: [ - { id: 'couch-1', type: 'couch', x: 310, y: 465, scale: 1.15 }, - { id: 'armchair-1', type: 'armchair', x: 650, y: 475, scale: 1.15 }, - { id: 'table-2', type: 'table', x: 480, y: 500, scale: 1.0 } - ] - } + version: 2, + player: { x: 510, y: HOUSE_BOUNDS.lower.homeY, floor: 'lower' }, + items: [ + { id: 'bed-1', type: 'bed', x: 168, y: 346, scale: 0.42, floor: 'upper' }, + { id: 'wardrobe-1', type: 'wardrobe', x: 356, y: 348, scale: 0.32, floor: 'upper' }, + { id: 'couch-1', type: 'couch', x: 560, y: 596, scale: 0.31, floor: 'lower' }, + { id: 'table-1', type: 'table', x: 510, y: 612, scale: 0.28, floor: 'lower' }, + { id: 'armchair-1', type: 'armchair', x: 705, y: 596, scale: 0.30, floor: 'lower' }, + { id: 'bookcase-1', type: 'bookcase', x: 850, y: 598, scale: 0.28, floor: 'lower' } + ] } function cloneDefaultState() { @@ -95,322 +82,304 @@ function cloneDefaultState() { } function roundedButton(scene, x, y, width, height, label, onClick, options = {}) { - const bg = scene.add.rectangle(x, y, width, height, options.fill ?? 0xffeef5, 1) - .setStrokeStyle(2, options.stroke ?? 0xe790b3) + const bg = scene.add.rectangle(x, y, width, height, options.fill ?? 0xfff2f7, options.alpha ?? 0.94) + .setStrokeStyle(2, options.stroke ?? 0xe58daf) .setInteractive({ useHandCursor: true }) - .setDepth(500) + .setScrollFactor(0) + .setDepth(1000) + const text = scene.add.text(x, y, label, { fontFamily: 'Arial Rounded MT Bold, ui-rounded, sans-serif', - fontSize: options.fontSize ?? '17px', + fontSize: options.fontSize ?? '15px', color: options.color ?? '#6b3b59', fontStyle: 'bold' - }).setOrigin(0.5).setDepth(501) + }).setOrigin(0.5).setScrollFactor(0).setDepth(1001) + bg.on('pointerdown', () => { - scene.tweens.add({ targets: [bg, text], scaleX: 0.95, scaleY: 0.95, duration: 55, yoyo: true }) + scene.tweens.add({ + targets: [bg, text], + scaleX: 0.95, + scaleY: 0.95, + duration: 55, + yoyo: true + }) onClick() }) + return { bg, text } } -function makeCropTexture(scene, key, rect, removeBackground = false) { +function cropTexture(scene, sourceKey, key, rect) { if (scene.textures.exists(key)) return - const source = scene.textures.get('production').getSourceImage() + const source = scene.textures.get(sourceKey).getSourceImage() const canvas = document.createElement('canvas') canvas.width = rect.w canvas.height = rect.h - const ctx = canvas.getContext('2d', { willReadFrequently: removeBackground }) + const ctx = canvas.getContext('2d') ctx.drawImage(source, rect.x, rect.y, rect.w, rect.h, 0, 0, rect.w, rect.h) - - if (removeBackground) { - const imageData = ctx.getImageData(0, 0, rect.w, rect.h) - const data = imageData.data - const w = rect.w - const h = rect.h - const seen = new Uint8Array(w * h) - const stack = [] - - const candidate = (idx) => { - const i = idx * 4 - const r = data[i] - const g = data[i + 1] - const b = data[i + 2] - const max = Math.max(r, g, b) - const min = Math.min(r, g, b) - return max >= 206 && max - min <= 48 - } - - const push = (x, y) => { - if (x < 0 || y < 0 || x >= w || y >= h) return - const idx = y * w + x - if (seen[idx] || !candidate(idx)) return - seen[idx] = 1 - stack.push(idx) - } - - for (let x = 0; x < w; x++) { - push(x, 0) - push(x, h - 1) - } - for (let y = 0; y < h; y++) { - push(0, y) - push(w - 1, y) - } - - while (stack.length) { - const idx = stack.pop() - data[idx * 4 + 3] = 0 - const x = idx % w - const y = Math.floor(idx / w) - push(x + 1, y) - push(x - 1, y) - push(x, y + 1) - push(x, y - 1) - } - ctx.putImageData(imageData, 0, 0) - } - scene.textures.addCanvas(key, canvas) } +function floorFromY(y) { + return y < 435 ? 'upper' : 'lower' +} + +function floorBounds(floor) { + return floor === 'upper' ? HOUSE_BOUNDS.upper : HOUSE_BOUNDS.lower +} + class HouseScene extends Phaser.Scene { constructor() { super('House') this.state = cloneDefaultState() this.decorateMode = false - this.roomObjects = [] - this.roomButtons = [] this.moveIntent = { left: false, right: false, up: false, down: false } this.saveTimer = null + this.itemSprites = [] + this.addIndex = 0 + this.stairCooldown = 0 } preload() { + this.load.image('house', '/assets/open-dollhouse.png') + this.load.image('furniture-sheet', '/assets/furniture-sheet.png') this.load.image('production', '/assets/production-sheet.png') } async create() { - Object.entries(ROOM_CROPS).forEach(([key, rect]) => makeCropTexture(this, 'room-' + key, rect)) - Object.entries(ASSET_CROPS).forEach(([key, rect]) => makeCropTexture(this, key, rect, true)) + cropTexture(this, 'production', 'player', PLAYER_CROP) + Object.entries(FURNITURE_CROPS).forEach(([key, rect]) => { + cropTexture(this, 'furniture-sheet', 'furniture-' + key, rect) + }) const saved = await loadGameState() - if (saved?.version === 1) this.state = saved + this.state = saved?.version === 2 ? saved : cloneDefaultState() - this.cameras.main.setBackgroundColor('#fff4f9') - this.buildChrome() + this.cameras.main.setBackgroundColor('#b7e3ff') + this.background = this.add.image(0, 0, 'house') + .setOrigin(0) + .setDisplaySize(WIDTH, HEIGHT) + .setDepth(0) + + this.buildUi() this.buildControls() this.bindInput() - this.renderRoom() + this.spawnAllFurniture() + this.spawnPlayer() + this.updateUi() + this.events.once(Phaser.Scenes.Events.SHUTDOWN, () => this.persist()) window.addEventListener('pagehide', () => this.persist(), { once: true }) } - buildChrome() { - this.add.rectangle(480, 32, 960, 64, 0xffe7f0).setDepth(450) - this.add.text(24, 17, 'My Dollhouse', { + buildUi() { + this.add.rectangle(480, 29, 960, 58, 0xffeff5, 0.88) + .setScrollFactor(0) + .setDepth(990) + + this.add.text(18, 13, 'My Dollhouse', { fontFamily: 'Arial Rounded MT Bold, ui-rounded, sans-serif', - fontSize: '28px', - color: '#c84f82', + fontSize: '25px', + color: '#c64f80', fontStyle: 'bold' - }).setDepth(500) + }).setScrollFactor(0).setDepth(1001) - ;['bedroom', 'bathroom', 'kitchen', 'living'].forEach((room, index) => { - const button = roundedButton( - this, - 400 + index * 132, - 32, - 120, - 38, - ROOM_LABELS[room], - () => this.switchRoom(room), - { fontSize: '14px' } - ) - button.room = room - this.roomButtons.push(button) - }) - - this.modeButton = roundedButton(this, 820, 588, 220, 52, '✨ Decorate', () => { + this.modeButton = roundedButton(this, 430, 29, 174, 40, '✨ Decorate', () => { this.decorateMode = !this.decorateMode - this.updateMode() - }, { fill: 0xf9bfd3, stroke: 0xdc6d9b }) + this.updateUi() + }, { fill: 0xf8bed2 }) - this.addButton = roundedButton(this, 594, 588, 190, 52, '+ Add something', () => this.addSomething(), { - fontSize: '15px' - }) - this.resetButton = roundedButton(this, 480, 588, 82, 52, 'Reset', () => this.resetRoom(), { - fontSize: '14px', - fill: 0xffffff + this.addButton = roundedButton(this, 616, 29, 178, 40, '+ Add Bed', () => { + if (!this.decorateMode) this.decorateMode = true + this.addFurniture() + this.updateUi() }) - this.roomTitle = this.add.text(50, 92, '', { - fontFamily: 'Arial Rounded MT Bold, ui-rounded, sans-serif', - fontSize: '24px', - color: '#663d59', - fontStyle: 'bold' - }).setDepth(50) + this.resetButton = roundedButton(this, 744, 29, 70, 40, 'Reset', () => this.resetHouse(), { + fill: 0xffffff, + fontSize: '13px' + }) - this.helpText = this.add.text(50, 120, '', { + this.helpText = this.add.text(795, 29, '', { fontFamily: 'Arial, sans-serif', - fontSize: '14px', - color: '#7d5a70' - }).setDepth(50) + fontSize: '12px', + color: '#6f5366', + align: 'center' + }).setOrigin(0, 0.5).setScrollFactor(0).setDepth(1001) + + this.add.rectangle(480, 688, 960, 64, 0xfff4f8, 0.82) + .setScrollFactor(0) + .setDepth(990) + + this.stairsHint = this.add.text(480, 684, 'Use the stairs on the right to change floors ↑ ↓', { + fontFamily: 'Arial Rounded MT Bold, ui-rounded, sans-serif', + fontSize: '13px', + color: '#7a526b' + }).setOrigin(0.5).setScrollFactor(0).setDepth(1001) } buildControls() { - const makePad = (x, y, label, direction) => { - const circle = this.add.circle(x, y, 29, 0xffffff, 0.88) - .setStrokeStyle(2, 0xd989ac) + const makePad = (x, label, direction) => { + const circle = this.add.circle(x, 687, 25, 0xffffff, 0.94) + .setStrokeStyle(2, 0xd889aa) .setInteractive() - .setDepth(520) - this.add.text(x, y - 1, label, { + .setScrollFactor(0) + .setDepth(1010) + + this.add.text(x, 686, label, { fontFamily: 'Arial, sans-serif', - fontSize: '26px', - color: '#b95b84', + fontSize: '23px', + color: '#b75c83', fontStyle: 'bold' - }).setOrigin(0.5).setDepth(521) + }).setOrigin(0.5).setScrollFactor(0).setDepth(1011) + + const off = () => { this.moveIntent[direction] = false } circle.on('pointerdown', () => { this.moveIntent[direction] = true }) - circle.on('pointerup', () => { this.moveIntent[direction] = false }) - circle.on('pointerout', () => { this.moveIntent[direction] = false }) + circle.on('pointerup', off) + circle.on('pointerout', off) } - makePad(72, 588, '◀', 'left') - makePad(136, 588, '▶', 'right') - makePad(200, 588, '▲', 'up') - makePad(264, 588, '▼', 'down') + makePad(44, '◀', 'left') + makePad(100, '▶', 'right') + makePad(156, '▲', 'up') + makePad(212, '▼', 'down') } bindInput() { this.cursors = this.input.keyboard.createCursorKeys() this.keys = this.input.keyboard.addKeys('W,A,S,D') + this.input.on('drag', (_pointer, object, dragX, dragY) => { if (!this.decorateMode || object.getData('kind') !== 'furniture') return - object.x = Phaser.Math.Clamp(dragX, ROOM_X + 35, ROOM_X + ROOM_W - 35) - object.y = Phaser.Math.Clamp(dragY, 215, FLOOR_BOTTOM) + object.x = Phaser.Math.Clamp(dragX, HOUSE_BOUNDS.left + 28, HOUSE_BOUNDS.right - 28) + object.y = Phaser.Math.Clamp(dragY, 250, HOUSE_BOUNDS.lower.maxY) object.setDepth(Math.floor(object.y)) }) + this.input.on('dragend', (_pointer, object) => { if (object.getData('kind') !== 'furniture') return - const item = this.state.rooms[this.state.activeRoom].find((entry) => entry.id === object.getData('id')) + const item = this.state.items.find((entry) => entry.id === object.getData('id')) if (!item) return + + const floor = floorFromY(object.y) + const bounds = floorBounds(floor) + object.y = Phaser.Math.Clamp(object.y, bounds.minY + 18, bounds.maxY) item.x = Math.round(object.x) item.y = Math.round(object.y) + item.floor = floor + object.setDepth(Math.floor(object.y)) this.scheduleSave() }) } - renderRoom() { - this.roomObjects.forEach((object) => object.destroy()) - this.roomObjects = [] - this.background?.destroy() - this.player?.destroy() - - this.background = this.add.image(ROOM_X, ROOM_Y, 'room-' + this.state.activeRoom) - .setOrigin(0) - .setDisplaySize(ROOM_W, ROOM_H) - .setDepth(0) - - const border = this.add.rectangle(ROOM_X + ROOM_W / 2, ROOM_Y + ROOM_H / 2, ROOM_W, ROOM_H) - .setFillStyle(0xffffff, 0) - .setStrokeStyle(5, 0xffffff, 0.75) - .setDepth(2) - this.roomObjects.push(border) - - const items = this.state.rooms[this.state.activeRoom] ?? [] - items.forEach((item) => this.spawnItem(item)) - - const playerPos = this.state.player - this.player = this.add.image(playerPos.x, playerPos.y, 'player') - .setOrigin(0.5, 1) - .setScale(1.35) - .setDepth(Math.floor(playerPos.y) + 10) - - this.roomTitle.setText(ROOM_LABELS[this.state.activeRoom]) - this.updateMode() - this.updateRoomButtons() + spawnAllFurniture() { + this.itemSprites.forEach((sprite) => sprite.destroy()) + this.itemSprites = [] + this.state.items.forEach((item) => this.spawnFurniture(item)) } - spawnItem(item) { - if (!this.textures.exists(item.type)) return - const object = this.add.image(item.x, item.y, item.type) + spawnFurniture(item) { + const key = 'furniture-' + item.type + if (!this.textures.exists(key)) return null + + const sprite = this.add.image(item.x, item.y, key) .setOrigin(0.5, 1) - .setScale(item.scale ?? 1) + .setScale(item.scale ?? 0.3) .setDepth(Math.floor(item.y)) .setData('kind', 'furniture') .setData('id', item.id) - object.setInteractive({ cursor: 'grab', pixelPerfect: false }) - this.input.setDraggable(object) - this.roomObjects.push(object) - return object + .setInteractive({ cursor: 'grab' }) + + this.input.setDraggable(sprite) + this.itemSprites.push(sprite) + return sprite } - switchRoom(room) { - if (room === this.state.activeRoom) return - this.capturePlayer() - this.state.activeRoom = room - this.state.player = { x: 480, y: 492 } - this.persist() - this.renderRoom() + spawnPlayer() { + this.player?.destroy() + this.player = this.add.image(this.state.player.x, this.state.player.y, 'player') + .setOrigin(0.5, 1) + .setScale(0.95) + .setDepth(Math.floor(this.state.player.y) + 40) } - updateRoomButtons() { - this.roomButtons.forEach((button) => { - const active = button.room === this.state.activeRoom - button.bg.setFillStyle(active ? 0xf39abc : 0xfff3f7) - button.text.setColor(active ? '#ffffff' : '#6b3b59') + updateUi() { + this.modeButton.text.setText(this.decorateMode ? '✓ Done decorating' : '✨ Decorate') + const nextType = FURNITURE_ORDER[this.addIndex % FURNITURE_ORDER.length] + this.addButton.text.setText('+ Add ' + FURNITURE_NAMES[nextType]) + this.helpText.setText(this.decorateMode ? 'drag furniture' : 'walk & play') + + this.itemSprites.forEach((sprite) => { + sprite.setAlpha(this.decorateMode ? 1 : 0.98) }) + this.player?.setAlpha(this.decorateMode ? 0.68 : 1) } - updateMode() { - const label = this.decorateMode ? '✓ Done decorating' : '✨ Decorate' - this.modeButton.text.setText(label) - this.helpText.setText( - this.decorateMode - ? 'Drag the furniture around. Add more things whenever you like.' - : 'Walk with the arrows, WASD, or the big buttons below.' - ) - this.addButton.bg.setAlpha(this.decorateMode ? 1 : 0.55) - this.addButton.text.setAlpha(this.decorateMode ? 1 : 0.55) - this.player?.setAlpha(this.decorateMode ? 0.72 : 1) - } + addFurniture() { + const type = FURNITURE_ORDER[this.addIndex % FURNITURE_ORDER.length] + this.addIndex += 1 - addSomething() { - if (!this.decorateMode) { - this.decorateMode = true - this.updateMode() - return + const floor = this.state.player.floor ?? floorFromY(this.state.player.y) + const bounds = floorBounds(floor) + const scaleByType = { + bed: 0.38, + couch: 0.29, + armchair: 0.29, + table: 0.27, + bookcase: 0.27, + wardrobe: 0.29 } - const choicesByRoom = { - bedroom: ['teddy', 'bookcase', 'bed'], - bathroom: ['ball', 'armchair'], - kitchen: ['table', 'bookcase'], - living: ['armchair', 'table', 'bookcase', 'teddy'] - } - const choices = choicesByRoom[this.state.activeRoom] - const type = choices[Math.floor(Math.random() * choices.length)] + const item = { id: type + '-' + Date.now(), type, - x: 480 + Phaser.Math.Between(-130, 130), - y: 470 + Phaser.Math.Between(-10, 35), - scale: type === 'teddy' || type === 'ball' ? 1.25 : 1 + floor, + x: Phaser.Math.Clamp(this.state.player.x + 95, 100, 860), + y: bounds.homeY + 8, + scale: scaleByType[type] } - this.state.rooms[this.state.activeRoom].push(item) - this.spawnItem(item) + + this.state.items.push(item) + this.spawnFurniture(item) this.scheduleSave() } - async resetRoom() { - const defaults = cloneDefaultState() - this.state.rooms[this.state.activeRoom] = defaults.rooms[this.state.activeRoom] - this.state.player = { x: 480, y: 492 } + async resetHouse() { + this.state = cloneDefaultState() await this.persist() - this.renderRoom() + this.spawnAllFurniture() + this.spawnPlayer() + this.updateUi() + } + + switchFloor(targetFloor) { + if (!this.player || targetFloor === this.state.player.floor) return + + const target = floorBounds(targetFloor) + this.state.player.floor = targetFloor + this.stairCooldown = 650 + + this.tweens.add({ + targets: this.player, + x: 760, + y: target.homeY, + duration: 360, + ease: 'Sine.easeInOut', + onUpdate: () => { + this.player.setDepth(Math.floor(this.player.y) + 40) + }, + onComplete: () => { + this.capturePlayer() + this.scheduleSave() + } + }) } capturePlayer() { if (!this.player) return - this.state.player = { - x: Math.round(this.player.x), - y: Math.round(this.player.y) - } + this.state.player.x = Math.round(this.player.x) + this.state.player.y = Math.round(this.player.y) + this.state.player.floor = floorFromY(this.player.y) } scheduleSave() { @@ -424,13 +393,29 @@ class HouseScene extends Phaser.Scene { } update(_time, delta) { - if (!this.player || this.decorateMode) return + if (!this.player || this.decorateMode || this.tweens.isTweening(this.player)) return + + this.stairCooldown = Math.max(0, this.stairCooldown - delta) const left = this.cursors.left.isDown || this.keys.A.isDown || this.moveIntent.left const right = this.cursors.right.isDown || this.keys.D.isDown || this.moveIntent.right const up = this.cursors.up.isDown || this.keys.W.isDown || this.moveIntent.up const down = this.cursors.down.isDown || this.keys.S.isDown || this.moveIntent.down + const floor = this.state.player.floor ?? floorFromY(this.player.y) + const bounds = floorBounds(floor) + + if (this.stairCooldown <= 0 && this.player.x >= STAIRS_X) { + if (up && floor === 'lower') { + this.switchFloor('upper') + return + } + if (down && floor === 'upper') { + this.switchFloor('lower') + return + } + } + let dx = Number(right) - Number(left) let dy = Number(down) - Number(up) if (dx === 0 && dy === 0) return @@ -439,15 +424,24 @@ class HouseScene extends Phaser.Scene { dx /= length dy /= length - const speed = 220 - const depthSpeed = 105 - this.player.x = Phaser.Math.Clamp(this.player.x + dx * speed * delta / 1000, ROOM_X + 38, ROOM_X + ROOM_W - 38) - this.player.y = Phaser.Math.Clamp(this.player.y + dy * depthSpeed * delta / 1000, FLOOR_TOP, FLOOR_BOTTOM) - this.player.setFlipX(dx < 0) - this.player.setDepth(Math.floor(this.player.y) + 10) + this.player.x = Phaser.Math.Clamp( + this.player.x + dx * 210 * delta / 1000, + HOUSE_BOUNDS.left, + HOUSE_BOUNDS.right + ) + this.player.y = Phaser.Math.Clamp( + this.player.y + dy * 76 * delta / 1000, + bounds.minY, + bounds.maxY + ) - const depthScale = Phaser.Math.Linear(1.18, 1.42, (this.player.y - FLOOR_TOP) / (FLOOR_BOTTOM - FLOOR_TOP)) - this.player.setScale(depthScale) + if (Math.abs(dx) > 0.05) this.player.setFlipX(dx < 0) + + const depthT = Phaser.Math.Clamp((this.player.y - bounds.minY) / (bounds.maxY - bounds.minY), 0, 1) + this.player.setScale(Phaser.Math.Linear(0.84, 1.02, depthT)) + this.player.setDepth(Math.floor(this.player.y) + 40) + + this.capturePlayer() this.scheduleSave() } } @@ -457,7 +451,7 @@ const game = new Phaser.Game({ parent: 'game', width: WIDTH, height: HEIGHT, - backgroundColor: '#fff4f9', + backgroundColor: '#b7e3ff', render: { antialias: true, pixelArt: false }, scale: { mode: Phaser.Scale.FIT, @@ -470,7 +464,7 @@ const game = new Phaser.Game({ }) window.__DOLLHOUSE__ = { - version: '0.1.0', + version: '0.2.0', game, async resetAll() { await clearGameState()