Turn room and roof levels into side scrollers
This commit is contained in:
parent
41bde2fccc
commit
4e0d0a6efe
335
src/main.js
335
src/main.js
@ -1261,44 +1261,21 @@ class PlayableLevelScene extends Phaser.Scene {
|
||||
constructor(key) {
|
||||
super(key)
|
||||
this.playerDragging = false
|
||||
this.playerDragTarget = null
|
||||
this.playerDragTargetX = null
|
||||
this.lastDragX = null
|
||||
this.playerAnimMode = 'idle'
|
||||
this.playerAnimFrame = 0
|
||||
this.playerAnimClock = 0
|
||||
this.walkBounds = null
|
||||
this.groundY = 0
|
||||
this.exiting = false
|
||||
}
|
||||
|
||||
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()
|
||||
}
|
||||
|
||||
addMaskedLevelImage(texture, points, depth, tint = null) {
|
||||
const image = this.add.image(WIDTH / 2, HEIGHT / 2, texture)
|
||||
.setDisplaySize(WIDTH, HEIGHT)
|
||||
.setDepth(depth)
|
||||
.setMask(this.createPolygonMask(points))
|
||||
|
||||
if (tint !== null) image.setTint(tint)
|
||||
return image
|
||||
}
|
||||
|
||||
createLevelPlayer(x, y, bounds, scale = 0.64) {
|
||||
createLevelPlayer(x, groundY, bounds, scale = 0.66) {
|
||||
this.walkBounds = bounds
|
||||
this.groundY = groundY
|
||||
|
||||
this.player = this.add.image(x, y, 'player-idle-0')
|
||||
this.player = this.add.image(x, groundY, 'player-idle-0')
|
||||
.setOrigin(0.5, 1)
|
||||
.setScale(scale)
|
||||
.setDepth(300)
|
||||
@ -1318,35 +1295,37 @@ class PlayableLevelScene extends Phaser.Scene {
|
||||
if (object !== this.player || this.exiting) return
|
||||
|
||||
const targetX = Phaser.Math.Clamp(
|
||||
pointer.x,
|
||||
pointer.worldX,
|
||||
this.walkBounds.left,
|
||||
this.walkBounds.right
|
||||
)
|
||||
|
||||
const targetY = Phaser.Math.Clamp(
|
||||
pointer.y + 76,
|
||||
this.walkBounds.top,
|
||||
this.walkBounds.bottom
|
||||
)
|
||||
|
||||
const previousX = this.lastDragX ?? this.player.x
|
||||
if (Math.abs(targetX - previousX) > 1) {
|
||||
this.player.setFlipX(targetX < previousX)
|
||||
}
|
||||
|
||||
this.lastDragX = targetX
|
||||
this.playerDragTarget = { x: targetX, y: targetY }
|
||||
this.playerDragTargetX = targetX
|
||||
})
|
||||
|
||||
this.input.on('dragend', (_pointer, object) => {
|
||||
if (object !== this.player) return
|
||||
this.playerDragging = false
|
||||
this.lastDragX = null
|
||||
this.playerDragTargetX = null
|
||||
if (!this.exiting) this.animatePlayer('idle', 999)
|
||||
})
|
||||
|
||||
this.cursors = this.input.keyboard.createCursorKeys()
|
||||
this.keys = this.input.keyboard.addKeys('W,A,S,D')
|
||||
this.keys = this.input.keyboard.addKeys('A,D')
|
||||
}
|
||||
|
||||
configureSideScroller(worldWidth, worldHeight = HEIGHT) {
|
||||
this.cameras.main.setBounds(0, 0, worldWidth, worldHeight)
|
||||
this.cameras.main.startFollow(this.player, true, 0.12, 0.12)
|
||||
this.cameras.main.setDeadzone(300, HEIGHT)
|
||||
this.cameras.main.setFollowOffset(0, 0)
|
||||
}
|
||||
|
||||
animatePlayer(mode, delta) {
|
||||
@ -1373,7 +1352,9 @@ class PlayableLevelScene extends Phaser.Scene {
|
||||
this.player.setTexture('player-' + mode + '-' + this.playerAnimFrame)
|
||||
}
|
||||
|
||||
addHouseExit(x, y, width = 126, height = 250) {
|
||||
addHouseExit(x, groundY, width = 136, height = 260) {
|
||||
const y = groundY - height / 2
|
||||
|
||||
const door = this.add.rectangle(x, y, width, height, 0xfff6fa, 1)
|
||||
.setStrokeStyle(6, 0xd094af)
|
||||
.setDepth(80)
|
||||
@ -1408,7 +1389,7 @@ class PlayableLevelScene extends Phaser.Scene {
|
||||
fontStyle: 'bold'
|
||||
}).setOrigin(0.5).setDepth(82)
|
||||
|
||||
const label = this.add.text(x, y + height / 2 + 28, 'House', {
|
||||
const label = this.add.text(x, groundY + 34, 'House', {
|
||||
fontFamily: 'Arial Rounded MT Bold, ui-rounded, sans-serif',
|
||||
fontSize: '22px',
|
||||
color: '#6d4c61',
|
||||
@ -1421,11 +1402,31 @@ class PlayableLevelScene extends Phaser.Scene {
|
||||
label.on('pointerdown', exit)
|
||||
}
|
||||
|
||||
addLevelHud(title, subtitle = 'Drag left or right to explore') {
|
||||
this.add.text(42, 48, title, {
|
||||
fontFamily: 'Arial Rounded MT Bold, ui-rounded, sans-serif',
|
||||
fontSize: '42px',
|
||||
color: '#684b5d',
|
||||
fontStyle: 'bold',
|
||||
backgroundColor: 'rgba(255,247,251,0.88)',
|
||||
padding: { x: 14, y: 8 }
|
||||
}).setScrollFactor(0).setDepth(1000)
|
||||
|
||||
this.add.text(54, 108, subtitle, {
|
||||
fontFamily: 'Arial Rounded MT Bold, ui-rounded, sans-serif',
|
||||
fontSize: '19px',
|
||||
color: '#7d6574',
|
||||
backgroundColor: 'rgba(255,247,251,0.82)',
|
||||
padding: { x: 10, y: 6 }
|
||||
}).setScrollFactor(0).setDepth(1000)
|
||||
}
|
||||
|
||||
returnToHouse() {
|
||||
if (this.exiting) return
|
||||
this.exiting = true
|
||||
this.playerDragging = false
|
||||
this.playerDragTarget = null
|
||||
this.playerDragTargetX = null
|
||||
this.cameras.main.stopFollow()
|
||||
this.cameras.main.fadeOut(180, 255, 247, 251)
|
||||
|
||||
this.time.delayedCall(200, () => {
|
||||
@ -1436,19 +1437,15 @@ class PlayableLevelScene extends Phaser.Scene {
|
||||
update(_time, delta) {
|
||||
if (!this.player || !this.walkBounds || this.exiting) return
|
||||
|
||||
if (this.playerDragging && this.playerDragTarget) {
|
||||
if (this.playerDragging && this.playerDragTargetX !== null) {
|
||||
const follow = 1 - Math.pow(1 - PLAYER_DRAG_FOLLOW, delta / 16.667)
|
||||
|
||||
this.player.x = Phaser.Math.Linear(
|
||||
this.player.x,
|
||||
this.playerDragTarget.x,
|
||||
follow
|
||||
)
|
||||
this.player.y = Phaser.Math.Linear(
|
||||
this.player.y,
|
||||
this.playerDragTarget.y,
|
||||
this.playerDragTargetX,
|
||||
follow
|
||||
)
|
||||
this.player.y = this.groundY
|
||||
|
||||
this.animatePlayer('walk', delta)
|
||||
return
|
||||
@ -1456,32 +1453,18 @@ class PlayableLevelScene extends Phaser.Scene {
|
||||
|
||||
const left = this.cursors.left.isDown || this.keys.A.isDown
|
||||
const right = this.cursors.right.isDown || this.keys.D.isDown
|
||||
const up = this.cursors.up.isDown || this.keys.W.isDown
|
||||
const down = this.cursors.down.isDown || this.keys.S.isDown
|
||||
const dx = Number(right) - Number(left)
|
||||
|
||||
let dx = Number(right) - Number(left)
|
||||
let dy = Number(down) - Number(up)
|
||||
const moving = dx !== 0 || dy !== 0
|
||||
|
||||
this.animatePlayer(moving ? 'walk' : 'idle', delta)
|
||||
if (!moving) return
|
||||
|
||||
const length = Math.hypot(dx, dy) || 1
|
||||
dx /= length
|
||||
dy /= length
|
||||
this.animatePlayer(dx !== 0 ? 'walk' : 'idle', delta)
|
||||
if (dx === 0) return
|
||||
|
||||
this.player.x = Phaser.Math.Clamp(
|
||||
this.player.x + dx * 320 * delta / 1000,
|
||||
this.player.x + dx * 360 * delta / 1000,
|
||||
this.walkBounds.left,
|
||||
this.walkBounds.right
|
||||
)
|
||||
this.player.y = Phaser.Math.Clamp(
|
||||
this.player.y + dy * 150 * delta / 1000,
|
||||
this.walkBounds.top,
|
||||
this.walkBounds.bottom
|
||||
)
|
||||
|
||||
if (Math.abs(dx) > 0.05) this.player.setFlipX(dx < 0)
|
||||
this.player.y = this.groundY
|
||||
this.player.setFlipX(dx < 0)
|
||||
}
|
||||
}
|
||||
|
||||
@ -1498,112 +1481,82 @@ class RoomScene extends PlayableLevelScene {
|
||||
create() {
|
||||
const room = ROOM_BY_ID.get(this.roomId) ?? ROOM_BY_ID.get('living')
|
||||
const skin = ROOM_SKINS[room.id] ?? ROOM_SKINS.living
|
||||
const graphics = this.add.graphics().setDepth(0)
|
||||
const worldWidth = 3100
|
||||
const wallTop = 170
|
||||
const floorTop = 1015
|
||||
const floorBottom = 1515
|
||||
const groundY = 1410
|
||||
|
||||
this.cameras.main.setBackgroundColor('#ccecff')
|
||||
this.cameras.main.fadeIn(220, 255, 247, 251)
|
||||
|
||||
const left = 64
|
||||
const right = WIDTH - 64
|
||||
const backLeft = 186
|
||||
const backRight = WIDTH - 186
|
||||
const top = 156
|
||||
const backTop = 210
|
||||
const horizonY = 930
|
||||
const bottomY = 1470
|
||||
const graphics = this.add.graphics().setDepth(0)
|
||||
|
||||
// Long interior wall.
|
||||
graphics.fillStyle(0xfffbfd, 1)
|
||||
graphics.fillRoundedRect(42, 126, WIDTH - 84, 1372, 30)
|
||||
graphics.fillRect(0, 110, worldWidth, floorBottom - 110)
|
||||
|
||||
this.add.image(
|
||||
WIDTH / 2,
|
||||
backTop + (horizonY - backTop) / 2,
|
||||
this.add.tileSprite(
|
||||
worldWidth / 2,
|
||||
wallTop + (floorTop - wallTop) / 2,
|
||||
worldWidth,
|
||||
floorTop - wallTop,
|
||||
skin.wallpaper
|
||||
)
|
||||
.setDisplaySize(backRight - backLeft, horizonY - backTop)
|
||||
.setDepth(1)
|
||||
).setDepth(1)
|
||||
|
||||
this.addMaskedLevelImage(
|
||||
skin.wallpaper,
|
||||
[
|
||||
{ x: left, y: top },
|
||||
{ x: backLeft, y: backTop },
|
||||
{ x: backLeft, y: horizonY },
|
||||
{ x: left, y: bottomY }
|
||||
],
|
||||
2,
|
||||
0xffedf5
|
||||
)
|
||||
// Long floor strip.
|
||||
this.add.tileSprite(
|
||||
worldWidth / 2,
|
||||
floorTop + (floorBottom - floorTop) / 2,
|
||||
worldWidth,
|
||||
floorBottom - floorTop,
|
||||
skin.floor
|
||||
).setDepth(2)
|
||||
|
||||
this.addMaskedLevelImage(
|
||||
skin.wallpaper,
|
||||
[
|
||||
{ x: backRight, y: backTop },
|
||||
{ x: right, y: top },
|
||||
{ x: right, y: bottomY },
|
||||
{ x: backRight, y: horizonY }
|
||||
],
|
||||
2,
|
||||
0xf2f6ff
|
||||
)
|
||||
// Rear skirting and front floor edge run the whole stage.
|
||||
this.add.image(worldWidth / 2, floorTop, 'architecture-skirting')
|
||||
.setDisplaySize(worldWidth, 28)
|
||||
.setDepth(6)
|
||||
|
||||
this.addMaskedLevelImage(
|
||||
skin.floor,
|
||||
[
|
||||
{ x: backLeft, y: horizonY },
|
||||
{ x: backRight, y: horizonY },
|
||||
{ x: right, y: bottomY },
|
||||
{ x: left, y: bottomY }
|
||||
],
|
||||
3
|
||||
)
|
||||
this.add.image(worldWidth / 2, floorBottom, 'architecture-floorEdge')
|
||||
.setDisplaySize(worldWidth, 30)
|
||||
.setDepth(6)
|
||||
|
||||
graphics.fillStyle(0x6d4c61, 0.12)
|
||||
graphics.fillRect(backLeft, horizonY - 3, backRight - backLeft, 7)
|
||||
this.add.image(worldWidth / 2, wallTop, 'architecture-ceilingTrim')
|
||||
.setDisplaySize(worldWidth, 32)
|
||||
.setDepth(6)
|
||||
|
||||
this.add.image(WIDTH / 2, backTop, 'architecture-ceilingTrim')
|
||||
.setDisplaySize(backRight - backLeft + 24, 30)
|
||||
.setDepth(8)
|
||||
// Break the long room into visible bays without blocking movement.
|
||||
graphics.lineStyle(5, 0xd7b4c6, 0.42)
|
||||
for (let x = 760; x < worldWidth; x += 760) {
|
||||
graphics.lineBetween(x, wallTop + 20, x, floorTop - 10)
|
||||
}
|
||||
|
||||
this.add.image(WIDTH / 2, horizonY, 'architecture-skirting')
|
||||
.setDisplaySize(backRight - backLeft + 24, 24)
|
||||
.setDepth(8)
|
||||
// Left/right end caps.
|
||||
graphics.lineStyle(8, 0xd5b8c6, 1)
|
||||
graphics.lineBetween(22, wallTop, 22, floorBottom)
|
||||
graphics.lineBetween(worldWidth - 22, wallTop, worldWidth - 22, floorBottom)
|
||||
|
||||
this.add.image(WIDTH / 2, bottomY, 'architecture-floorEdge')
|
||||
.setDisplaySize(right - left, 28)
|
||||
.setDepth(8)
|
||||
// Elevator/House exit lives at the beginning of every room level.
|
||||
this.addHouseExit(170, groundY)
|
||||
|
||||
graphics.lineStyle(5, 0xd2aabf, 1)
|
||||
graphics.lineBetween(left, top, backLeft, backTop)
|
||||
graphics.lineBetween(right, top, backRight, backTop)
|
||||
graphics.lineBetween(left, bottomY, backLeft, horizonY)
|
||||
graphics.lineBetween(right, bottomY, backRight, horizonY)
|
||||
|
||||
graphics.lineStyle(6, 0xd5b8c6, 1)
|
||||
graphics.strokeRoundedRect(42, 126, WIDTH - 84, 1372, 30)
|
||||
|
||||
this.add.text(72, 62, room.name, {
|
||||
fontFamily: 'Arial Rounded MT Bold, ui-rounded, sans-serif',
|
||||
fontSize: '42px',
|
||||
color: '#684b5d',
|
||||
fontStyle: 'bold'
|
||||
}).setDepth(100)
|
||||
|
||||
this.add.text(72, 112, 'Drag to explore', {
|
||||
fontFamily: 'Arial Rounded MT Bold, ui-rounded, sans-serif',
|
||||
fontSize: '20px',
|
||||
color: '#8a6c7d'
|
||||
}).setDepth(100)
|
||||
|
||||
// Elevator doorway is the return path to the dollhouse hub.
|
||||
this.addHouseExit(916, 785, 126, 250)
|
||||
// A couple of simple depth markers so scrolling reads immediately.
|
||||
graphics.fillStyle(0xffffff, 0.30)
|
||||
graphics.fillRoundedRect(1040, 760, 360, 190, 24)
|
||||
graphics.fillRoundedRect(2150, 690, 420, 250, 24)
|
||||
|
||||
this.createLevelPlayer(
|
||||
780,
|
||||
1260,
|
||||
{ left: 150, right: 980, top: 1060, bottom: 1430 },
|
||||
0.66
|
||||
470,
|
||||
groundY,
|
||||
{ left: 110, right: worldWidth - 110 },
|
||||
0.68
|
||||
)
|
||||
|
||||
this.configureSideScroller(worldWidth)
|
||||
this.addLevelHud(room.name)
|
||||
|
||||
// Start framed near the elevator rather than centred on the whole stage.
|
||||
this.cameras.main.scrollX = 0
|
||||
}
|
||||
}
|
||||
|
||||
@ -1613,65 +1566,53 @@ class RoofScene extends PlayableLevelScene {
|
||||
}
|
||||
|
||||
create() {
|
||||
const graphics = this.add.graphics().setDepth(0)
|
||||
const worldWidth = 2900
|
||||
const groundY = 1370
|
||||
const roofTop = 830
|
||||
const roofBottom = 1515
|
||||
|
||||
this.cameras.main.setBackgroundColor('#ccecff')
|
||||
this.cameras.main.fadeIn(220, 255, 247, 251)
|
||||
|
||||
this.add.text(72, 62, 'Roof', {
|
||||
fontFamily: 'Arial Rounded MT Bold, ui-rounded, sans-serif',
|
||||
fontSize: '42px',
|
||||
color: '#684b5d',
|
||||
fontStyle: 'bold'
|
||||
}).setDepth(100)
|
||||
const graphics = this.add.graphics().setDepth(0)
|
||||
|
||||
this.add.text(72, 112, 'Drag to explore', {
|
||||
fontFamily: 'Arial Rounded MT Bold, ui-rounded, sans-serif',
|
||||
fontSize: '20px',
|
||||
color: '#6d7184'
|
||||
}).setDepth(100)
|
||||
// Long open sky.
|
||||
graphics.fillStyle(0xccecff, 1)
|
||||
graphics.fillRect(0, 0, worldWidth, HEIGHT)
|
||||
|
||||
// Keep the roof level intentionally bare for now: just the approved shell
|
||||
// roof language expanded into a large playable surface.
|
||||
this.add.image(WIDTH / 2, 210, 'shell-roof')
|
||||
// Repeat the approved pink roof language across a side-scrolling stage.
|
||||
for (let x = 0; x < worldWidth; x += 1060) {
|
||||
this.add.image(x + 530, 215, 'shell-roof')
|
||||
.setOrigin(0.5, 0)
|
||||
.setDisplaySize(1060, 228)
|
||||
.setDepth(1)
|
||||
|
||||
const roofBackY = 610
|
||||
const roofFrontY = 1450
|
||||
const roofLeftBack = 240
|
||||
const roofRightBack = WIDTH - 240
|
||||
const roofLeftFront = 92
|
||||
const roofRightFront = WIDTH - 92
|
||||
}
|
||||
|
||||
graphics.fillStyle(0xf5a8c3, 1)
|
||||
graphics.fillPoints([
|
||||
new Phaser.Geom.Point(roofLeftBack, roofBackY),
|
||||
new Phaser.Geom.Point(roofRightBack, roofBackY),
|
||||
new Phaser.Geom.Point(roofRightFront, roofFrontY),
|
||||
new Phaser.Geom.Point(roofLeftFront, roofFrontY)
|
||||
], true)
|
||||
graphics.fillRect(0, roofTop, worldWidth, roofBottom - roofTop)
|
||||
|
||||
graphics.lineStyle(7, 0xb96f8e, 1)
|
||||
graphics.strokePoints([
|
||||
new Phaser.Geom.Point(roofLeftBack, roofBackY),
|
||||
new Phaser.Geom.Point(roofRightBack, roofBackY),
|
||||
new Phaser.Geom.Point(roofRightFront, roofFrontY),
|
||||
new Phaser.Geom.Point(roofLeftFront, roofFrontY)
|
||||
], true, true)
|
||||
graphics.lineStyle(8, 0xb96f8e, 1)
|
||||
graphics.lineBetween(0, roofTop, worldWidth, roofTop)
|
||||
graphics.lineBetween(0, roofBottom, worldWidth, roofBottom)
|
||||
|
||||
graphics.lineStyle(4, 0xffd2df, 0.9)
|
||||
graphics.lineBetween(WIDTH / 2, roofBackY, WIDTH / 2, roofFrontY)
|
||||
// Subtle roof seams to give motion/parallax reference while walking.
|
||||
graphics.lineStyle(4, 0xffd2df, 0.72)
|
||||
for (let x = 320; x < worldWidth; x += 420) {
|
||||
graphics.lineBetween(x, roofTop, x + 150, roofBottom)
|
||||
}
|
||||
|
||||
this.addHouseExit(WIDTH / 2, 690, 124, 236)
|
||||
this.addHouseExit(180, groundY)
|
||||
|
||||
this.createLevelPlayer(
|
||||
WIDTH / 2 + 180,
|
||||
1200,
|
||||
{ left: 170, right: WIDTH - 170, top: 880, bottom: 1400 },
|
||||
0.62
|
||||
500,
|
||||
groundY,
|
||||
{ left: 120, right: worldWidth - 120 },
|
||||
0.64
|
||||
)
|
||||
|
||||
this.configureSideScroller(worldWidth)
|
||||
this.addLevelHud('Roof')
|
||||
this.cameras.main.scrollX = 0
|
||||
}
|
||||
}
|
||||
|
||||
@ -1698,7 +1639,7 @@ const game = new Phaser.Game({
|
||||
})
|
||||
|
||||
window.__DOLLHOUSE__ = {
|
||||
version: '0.11.0',
|
||||
version: '0.12.0',
|
||||
game,
|
||||
async resetAll() {
|
||||
await clearGameState()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user