From 737f16f38bd22a4c193d21462fa97e87fbbe51dc Mon Sep 17 00:00:00 2001 From: Zsolt Tasnadi Date: Fri, 15 May 2026 21:45:18 +0200 Subject: [PATCH] refact round --- src/entities/Platform.ts | 26 ++-- src/entities/obstacles/ObstacleSpikeBase.ts | 11 ++ src/entities/obstacles/ObstacleSpikeNormal.ts | 7 +- src/entities/obstacles/ObstacleSpikeTricky.ts | 9 +- src/levels/01_level.ts | 12 -- src/levels/02_level.ts | 12 -- src/levels/03_level.ts | 7 -- src/levels/04_level.ts | 12 -- src/levels/05_level.ts | 12 -- src/levels/06_level.ts | 12 -- src/levels/07_level.ts | 12 -- src/levels/08_level.ts | 12 -- src/levels/09_level.ts | 12 -- src/levels/10_level.ts | 12 -- src/lib/LevelLoader.ts | 82 ++++++------ src/lib/levelTypes.ts | 11 +- src/scenes/BootScene.ts | 117 ++++++++++-------- src/scenes/GameScene.ts | 22 +++- 18 files changed, 168 insertions(+), 232 deletions(-) create mode 100644 src/entities/obstacles/ObstacleSpikeBase.ts diff --git a/src/entities/Platform.ts b/src/entities/Platform.ts index eeaf93d..f3adb39 100644 --- a/src/entities/Platform.ts +++ b/src/entities/Platform.ts @@ -1,5 +1,20 @@ import Phaser from "phaser"; +type PhysicsBodyHolder = + | Phaser.Types.Physics.Arcade.GameObjectWithBody + | Phaser.Physics.Arcade.Body + | Phaser.Physics.Arcade.StaticBody + | Phaser.Tilemaps.Tile; + +function getBody(target: PhysicsBodyHolder): Phaser.Physics.Arcade.Body | Phaser.Physics.Arcade.StaticBody { + if (target instanceof Phaser.Physics.Arcade.Body || target instanceof Phaser.Physics.Arcade.StaticBody) { + return target; + } + return (target as Phaser.Types.Physics.Arcade.GameObjectWithBody).body as + | Phaser.Physics.Arcade.Body + | Phaser.Physics.Arcade.StaticBody; +} + export class Platform extends Phaser.GameObjects.TileSprite { static readonly TEXTURE_KEY = "platform"; @@ -12,15 +27,12 @@ export class Platform extends Phaser.GameObjects.TileSprite { } // Phaser collider processCallback: only collide when the mover is descending onto the top. - static canLand( - mover: Phaser.Types.Physics.Arcade.GameObjectWithBody, - platform: Phaser.Types.Physics.Arcade.GameObjectWithBody, - ): boolean { - const moverBody = mover.body as Phaser.Physics.Arcade.Body; - const platBody = platform.body as Phaser.Physics.Arcade.StaticBody; + static canLand: Phaser.Types.Physics.Arcade.ArcadePhysicsCallback = (mover, platform) => { + const moverBody = getBody(mover) as Phaser.Physics.Arcade.Body; + const platBody = getBody(platform) as Phaser.Physics.Arcade.StaticBody; return ( moverBody.velocity.y >= 0 && moverBody.bottom - moverBody.deltaY() <= platBody.top ); - } + }; } diff --git a/src/entities/obstacles/ObstacleSpikeBase.ts b/src/entities/obstacles/ObstacleSpikeBase.ts new file mode 100644 index 0000000..403ca11 --- /dev/null +++ b/src/entities/obstacles/ObstacleSpikeBase.ts @@ -0,0 +1,11 @@ +import Phaser from "phaser"; + +export abstract class ObstacleSpikeBase extends Phaser.GameObjects.Image { + declare body: Phaser.Physics.Arcade.StaticBody; + + constructor(scene: Phaser.Scene, x: number, y: number, textureKey: string) { + super(scene, x, y, textureKey); + scene.add.existing(this); + scene.physics.add.existing(this, true); + } +} diff --git a/src/entities/obstacles/ObstacleSpikeNormal.ts b/src/entities/obstacles/ObstacleSpikeNormal.ts index 90147c5..df8ea9b 100644 --- a/src/entities/obstacles/ObstacleSpikeNormal.ts +++ b/src/entities/obstacles/ObstacleSpikeNormal.ts @@ -1,15 +1,12 @@ import Phaser from "phaser"; +import { ObstacleSpikeBase } from "./ObstacleSpikeBase"; -export class ObstacleSpikeNormal extends Phaser.GameObjects.Image { +export class ObstacleSpikeNormal extends ObstacleSpikeBase { static readonly TEXTURE_KEY = "spike-normal"; static readonly WIDTH = 40; static readonly HEIGHT = 20; - declare body: Phaser.Physics.Arcade.StaticBody; - constructor(scene: Phaser.Scene, x: number, y: number) { super(scene, x, y, ObstacleSpikeNormal.TEXTURE_KEY); - scene.add.existing(this); - scene.physics.add.existing(this, true); } } diff --git a/src/entities/obstacles/ObstacleSpikeTricky.ts b/src/entities/obstacles/ObstacleSpikeTricky.ts index 16d3c7d..029da4e 100644 --- a/src/entities/obstacles/ObstacleSpikeTricky.ts +++ b/src/entities/obstacles/ObstacleSpikeTricky.ts @@ -1,23 +1,20 @@ import Phaser from "phaser"; +import { ObstacleSpikeBase } from "./ObstacleSpikeBase"; -export class ObstacleSpikeTricky extends Phaser.GameObjects.Image { +export class ObstacleSpikeTricky extends ObstacleSpikeBase { static readonly TEXTURE_KEY = "spike-tricky"; static readonly WIDTH = 40; static readonly HEIGHT = 20; static readonly TRIGGER_DISTANCE = 55; static readonly DISPLACEMENT = 100; - declare body: Phaser.Physics.Arcade.StaticBody; - private moved = false; constructor(scene: Phaser.Scene, x: number, y: number) { super(scene, x, y, ObstacleSpikeTricky.TEXTURE_KEY); - scene.add.existing(this); - scene.physics.add.existing(this, true); } - react(playerX: number): void { + react(playerX: number, _playerY: number): void { if (this.moved) return; const dx = this.x - playerX; if (Math.abs(dx) < ObstacleSpikeTricky.TRIGGER_DISTANCE) { diff --git a/src/levels/01_level.ts b/src/levels/01_level.ts index bfa738e..da30928 100644 --- a/src/levels/01_level.ts +++ b/src/levels/01_level.ts @@ -1,18 +1,6 @@ import { defineLevel } from "../lib/levelTypes"; export const level01 = defineLevel("Spike Hop", [ - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", ".E........^........P", "####################", "####################", diff --git a/src/levels/02_level.ts b/src/levels/02_level.ts index 21a2298..9f98a69 100644 --- a/src/levels/02_level.ts +++ b/src/levels/02_level.ts @@ -1,18 +1,6 @@ import { defineLevel } from "../lib/levelTypes"; export const level02 = defineLevel("Trap Bridge", [ - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", ".E.................P", "#######~~~##########", "#######~~~##########", diff --git a/src/levels/03_level.ts b/src/levels/03_level.ts index 949899d..1f95c55 100644 --- a/src/levels/03_level.ts +++ b/src/levels/03_level.ts @@ -1,13 +1,6 @@ import { defineLevel } from "../lib/levelTypes"; export const level03 = defineLevel("Trickster Stairs", [ - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", ".E..................", "#####...............", "....#####...........", diff --git a/src/levels/04_level.ts b/src/levels/04_level.ts index d5e049d..dd42bd0 100644 --- a/src/levels/04_level.ts +++ b/src/levels/04_level.ts @@ -1,18 +1,6 @@ import { defineLevel } from "../lib/levelTypes"; export const level04 = defineLevel("Spike Field", [ - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", ".E....^...^...^....P", "####################", "####################", diff --git a/src/levels/05_level.ts b/src/levels/05_level.ts index 272b38e..271b016 100644 --- a/src/levels/05_level.ts +++ b/src/levels/05_level.ts @@ -1,18 +1,6 @@ import { defineLevel } from "../lib/levelTypes"; export const level05 = defineLevel("Pit Crossing", [ - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", ".E.................P", "####...######...####", "####^^^######^^^####", diff --git a/src/levels/06_level.ts b/src/levels/06_level.ts index 95191c7..38a55b2 100644 --- a/src/levels/06_level.ts +++ b/src/levels/06_level.ts @@ -1,18 +1,6 @@ import { defineLevel } from "../lib/levelTypes"; export const level06 = defineLevel("Trickster", [ - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", ".E..........T......P", "####################", "####################", diff --git a/src/levels/07_level.ts b/src/levels/07_level.ts index b8c1381..c508b05 100644 --- a/src/levels/07_level.ts +++ b/src/levels/07_level.ts @@ -1,18 +1,6 @@ import { defineLevel } from "../lib/levelTypes"; export const level07 = defineLevel("Bridge Hopping", [ - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", ".E.................P", "####...#####...#####", "####...#####...#####", diff --git a/src/levels/08_level.ts b/src/levels/08_level.ts index 3fef2e4..14a7200 100644 --- a/src/levels/08_level.ts +++ b/src/levels/08_level.ts @@ -1,18 +1,6 @@ import { defineLevel } from "../lib/levelTypes"; export const level08 = defineLevel("Mixed Hazards", [ - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", ".E..^......T....^..P", "####################", "####################", diff --git a/src/levels/09_level.ts b/src/levels/09_level.ts index bb0dd8c..b4959e1 100644 --- a/src/levels/09_level.ts +++ b/src/levels/09_level.ts @@ -1,18 +1,6 @@ import { defineLevel } from "../lib/levelTypes"; export const level09 = defineLevel("Vanishing Bridges", [ - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", ".E.................P", "####~~~####~~~######", "####~~~####~~~######", diff --git a/src/levels/10_level.ts b/src/levels/10_level.ts index ca36e75..7dfd006 100644 --- a/src/levels/10_level.ts +++ b/src/levels/10_level.ts @@ -1,18 +1,6 @@ import { defineLevel } from "../lib/levelTypes"; export const level10 = defineLevel("Final Gauntlet", [ - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", - "....................", ".E......T..........P", "####...####~~~######", "####^^^####...######", diff --git a/src/lib/LevelLoader.ts b/src/lib/LevelLoader.ts index add9f29..481a3d3 100644 --- a/src/lib/LevelLoader.ts +++ b/src/lib/LevelLoader.ts @@ -18,18 +18,29 @@ export interface LoadedLevel { trickySpikes: ObstacleSpikeTricky[]; } -interface Cell { - row: number; - col: number; -} +const cellCenterX = (col: number): number => col * TILE_SIZE + TILE_SIZE / 2; +const cellBottomY = (row: number, entityHeight: number): number => + (row + 1) * TILE_SIZE - entityHeight / 2; export class LevelLoader { constructor(private readonly scene: Phaser.Scene) {} load(matrix: LevelMatrix): LoadedLevel { return { - player: this.buildPlayer(matrix), - exit: this.buildExit(matrix), + player: this.buildSingleton( + matrix, + TileType.Player, + Player.SIZE, + (x, y) => new Player(this.scene, x, y), + "Player", + ), + exit: this.buildSingleton( + matrix, + TileType.Exit, + Exit.HEIGHT, + (x, y) => new Exit(this.scene, x, y), + "Exit", + ), platforms: this.buildHorizontalRuns( matrix, TileType.Platform, @@ -40,16 +51,18 @@ export class LevelLoader { TileType.DisappearingPlatform, (x, y, w, h) => new ObstaclePlatformDisappearing(this.scene, x, y, w, h), ), - spikes: this.buildSingleCells(matrix, TileType.Spike, (col, row) => { - const x = col * TILE_SIZE + TILE_SIZE / 2; - const y = (row + 1) * TILE_SIZE - ObstacleSpikeNormal.HEIGHT / 2; - return new ObstacleSpikeNormal(this.scene, x, y); - }), - trickySpikes: this.buildSingleCells(matrix, TileType.TrickySpike, (col, row) => { - const x = col * TILE_SIZE + TILE_SIZE / 2; - const y = (row + 1) * TILE_SIZE - ObstacleSpikeTricky.HEIGHT / 2; - return new ObstacleSpikeTricky(this.scene, x, y); - }), + spikes: this.buildSingleCells( + matrix, + TileType.Spike, + ObstacleSpikeNormal.HEIGHT, + (x, y) => new ObstacleSpikeNormal(this.scene, x, y), + ), + trickySpikes: this.buildSingleCells( + matrix, + TileType.TrickySpike, + ObstacleSpikeTricky.HEIGHT, + (x, y) => new ObstacleSpikeTricky(this.scene, x, y), + ), }; } @@ -81,39 +94,34 @@ export class LevelLoader { private buildSingleCells( matrix: LevelMatrix, tileType: TileType, - factory: (col: number, row: number) => T, + entityHeight: number, + factory: (x: number, y: number) => T, ): T[] { const result: T[] = []; for (let row = 0; row < matrix.length; row++) { for (let col = 0; col < matrix[row].length; col++) { - if (matrix[row][col] === tileType) result.push(factory(col, row)); + if (matrix[row][col] === tileType) { + result.push(factory(cellCenterX(col), cellBottomY(row, entityHeight))); + } } } return result; } - private buildPlayer(matrix: LevelMatrix): Player { - const cell = this.findTile(matrix, TileType.Player); - if (!cell) throw new Error("Level matrix has no Player tile"); - const x = cell.col * TILE_SIZE + TILE_SIZE / 2; - const y = (cell.row + 1) * TILE_SIZE - Player.SIZE / 2; - return new Player(this.scene, x, y); - } - - private buildExit(matrix: LevelMatrix): Exit { - const cell = this.findTile(matrix, TileType.Exit); - if (!cell) throw new Error("Level matrix has no Exit tile"); - const x = cell.col * TILE_SIZE + TILE_SIZE / 2; - const y = (cell.row + 1) * TILE_SIZE - Exit.HEIGHT / 2; - return new Exit(this.scene, x, y); - } - - private findTile(matrix: LevelMatrix, tile: TileType): Cell | null { + private buildSingleton( + matrix: LevelMatrix, + tileType: TileType, + entityHeight: number, + factory: (x: number, y: number) => T, + label: string, + ): T { for (let row = 0; row < matrix.length; row++) { for (let col = 0; col < matrix[row].length; col++) { - if (matrix[row][col] === tile) return { row, col }; + if (matrix[row][col] === tileType) { + return factory(cellCenterX(col), cellBottomY(row, entityHeight)); + } } } - return null; + throw new Error(`Level matrix has no ${label} tile`); } } diff --git a/src/lib/levelTypes.ts b/src/lib/levelTypes.ts index ee4c707..5dd72bd 100644 --- a/src/lib/levelTypes.ts +++ b/src/lib/levelTypes.ts @@ -29,6 +29,8 @@ const CHAR_TO_TILE: Record = { "T": TileType.TrickySpike, }; +const EMPTY_ROW = ".".repeat(LEVEL_COLS); + function parseLevel(rows: string[]): LevelMatrix { return rows.map((row, rowIdx) => [...row].map((c, colIdx) => { @@ -42,5 +44,12 @@ function parseLevel(rows: string[]): LevelMatrix { } export function defineLevel(name: string, rows: string[]): LevelDef { - return { name, matrix: parseLevel(rows) }; + if (rows.length > LEVEL_ROWS) { + throw new Error( + `Level "${name}" has ${rows.length} rows, max is ${LEVEL_ROWS}`, + ); + } + const padCount = LEVEL_ROWS - rows.length; + const padded = padCount > 0 ? [...Array(padCount).fill(EMPTY_ROW), ...rows] : rows; + return { name, matrix: parseLevel(padded) }; } diff --git a/src/scenes/BootScene.ts b/src/scenes/BootScene.ts index e5f2dd8..b35beac 100644 --- a/src/scenes/BootScene.ts +++ b/src/scenes/BootScene.ts @@ -7,26 +7,69 @@ import { ObstacleSpikeTricky, } from "../entities/obstacles"; +interface SpriteAsset { + key: string; + path: string; + width: number; + height: number; +} + +const SPRITE_ASSETS: SpriteAsset[] = [ + { key: Player.TEXTURE_KEY, path: "/sprites/player.svg", width: Player.SIZE, height: Player.SIZE }, + { key: Exit.TEXTURE_KEY, path: "/sprites/exit.svg", width: Exit.WIDTH, height: Exit.HEIGHT }, + { key: Platform.TEXTURE_KEY, path: "/sprites/platform.svg", width: 40, height: 40 }, + { + key: ObstacleSpikeNormal.TEXTURE_KEY, + path: "/sprites/spike-normal.svg", + width: ObstacleSpikeNormal.WIDTH, + height: ObstacleSpikeNormal.HEIGHT, + }, + { + key: ObstacleSpikeTricky.TEXTURE_KEY, + path: "/sprites/spike-tricky.svg", + width: ObstacleSpikeTricky.WIDTH, + height: ObstacleSpikeTricky.HEIGHT, + }, +]; + +const TITLE_STYLE: Phaser.Types.GameObjects.Text.TextStyle = { + fontFamily: "Arial Black, Arial", + fontSize: "78px", + color: "#ffd700", + fontStyle: "bold", + stroke: "#7a5500", + strokeThickness: 6, +}; + +const SUBTITLE_STYLE: Phaser.Types.GameObjects.Text.TextStyle = { + fontFamily: "Arial", + fontSize: "18px", + color: "#aaaaaa", + fontStyle: "italic", +}; + +const PROMPT_STYLE: Phaser.Types.GameObjects.Text.TextStyle = { + fontFamily: "Arial", + fontSize: "26px", + color: "#ffffff", + fontStyle: "bold", +}; + +const HINT_STYLE: Phaser.Types.GameObjects.Text.TextStyle = { + fontFamily: "Arial", + fontSize: "14px", + color: "#888888", +}; + export class BootScene extends Phaser.Scene { constructor() { super({ key: "BootScene" }); } preload(): void { - this.load.svg(Player.TEXTURE_KEY, "/sprites/player.svg", { width: 32, height: 32 }); - this.load.svg(Exit.TEXTURE_KEY, "/sprites/exit.svg", { - width: Exit.WIDTH, - height: Exit.HEIGHT, - }); - this.load.svg(Platform.TEXTURE_KEY, "/sprites/platform.svg", { width: 40, height: 40 }); - this.load.svg(ObstacleSpikeNormal.TEXTURE_KEY, "/sprites/spike-normal.svg", { - width: ObstacleSpikeNormal.WIDTH, - height: ObstacleSpikeNormal.HEIGHT, - }); - this.load.svg(ObstacleSpikeTricky.TEXTURE_KEY, "/sprites/spike-tricky.svg", { - width: ObstacleSpikeTricky.WIDTH, - height: ObstacleSpikeTricky.HEIGHT, - }); + for (const asset of SPRITE_ASSETS) { + this.load.svg(asset.key, asset.path, { width: asset.width, height: asset.height }); + } } create(): void { @@ -38,48 +81,16 @@ export class BootScene extends Phaser.Scene { this.add.tileSprite(cx, h - 20, w, 40, Platform.TEXTURE_KEY); - this.add - .text(cx, 130, "TRICKSTER", { - fontFamily: "Arial Black, Arial", - fontSize: "78px", - color: "#ffd700", - fontStyle: "bold", - stroke: "#7a5500", - strokeThickness: 6, - }) - .setOrigin(0.5); - - this.add - .text(cx, 200, "TILES", { - fontFamily: "Arial Black, Arial", - fontSize: "78px", - color: "#ffd700", - fontStyle: "bold", - stroke: "#7a5500", - strokeThickness: 6, - }) - .setOrigin(0.5); - - this.add - .text(cx, 270, "a pixel-art platformer", { - fontFamily: "Arial", - fontSize: "18px", - color: "#aaaaaa", - fontStyle: "italic", - }) - .setOrigin(0.5); + this.add.text(cx, 130, "TRICKSTER", TITLE_STYLE).setOrigin(0.5); + this.add.text(cx, 200, "TILES", TITLE_STYLE).setOrigin(0.5); + this.add.text(cx, 270, "a pixel-art platformer", SUBTITLE_STYLE).setOrigin(0.5); this.add.image(cx - 80, 360, Player.TEXTURE_KEY); this.add.image(cx, 374, ObstacleSpikeNormal.TEXTURE_KEY); this.add.image(cx + 80, 374, ObstacleSpikeTricky.TEXTURE_KEY); const startText = this.add - .text(cx, 450, "PRESS SPACE TO START", { - fontFamily: "Arial", - fontSize: "26px", - color: "#ffffff", - fontStyle: "bold", - }) + .text(cx, 450, "PRESS SPACE TO START", PROMPT_STYLE) .setOrigin(0.5); this.tweens.add({ @@ -91,11 +102,7 @@ export class BootScene extends Phaser.Scene { }); this.add - .text(cx, 510, "← → / A D to move SPACE / ↑ / W to jump", { - fontFamily: "Arial", - fontSize: "14px", - color: "#888888", - }) + .text(cx, 510, "← → / A D to move SPACE / ↑ / W to jump", HINT_STYLE) .setOrigin(0.5); const start = () => this.scene.start("GameScene"); diff --git a/src/scenes/GameScene.ts b/src/scenes/GameScene.ts index 17f844f..e5445fd 100644 --- a/src/scenes/GameScene.ts +++ b/src/scenes/GameScene.ts @@ -14,9 +14,14 @@ interface SceneData { levelIndex?: number; } +interface Reactive { + react(playerX: number, playerY: number): void; +} + export class GameScene extends Phaser.Scene { private static readonly TRANSITION_DELAY_MS = 1200; private static readonly FALL_LIMIT_PADDING = 80; + private static readonly WORLD_BOUNDS_BOTTOM_PADDING = 200; private levelIndex = 0; private player!: Player; @@ -25,6 +30,7 @@ export class GameScene extends Phaser.Scene { private disappearing: ObstaclePlatformDisappearing[] = []; private spikes: ObstacleSpikeNormal[] = []; private trickySpikes: ObstacleSpikeTricky[] = []; + private reactives: Reactive[] = []; private levelCompleted = false; private dying = false; private hudText!: Phaser.GameObjects.Text; @@ -42,10 +48,16 @@ export class GameScene extends Phaser.Scene { this.disappearing = []; this.spikes = []; this.trickySpikes = []; + this.reactives = []; } create(): void { - this.physics.world.setBounds(0, 0, this.scale.width, this.scale.height + 200); + this.physics.world.setBounds( + 0, + 0, + this.scale.width, + this.scale.height + GameScene.WORLD_BOUNDS_BOTTOM_PADDING, + ); const def = LEVELS[this.levelIndex]; const loader = new LevelLoader(this); @@ -56,6 +68,7 @@ export class GameScene extends Phaser.Scene { this.disappearing = loaded.disappearing; this.spikes = loaded.spikes; this.trickySpikes = loaded.trickySpikes; + this.reactives = [...loaded.disappearing, ...loaded.trickySpikes]; this.physics.add.collider(this.player, this.platforms, undefined, Platform.canLand); this.physics.add.collider(this.player, this.disappearing, undefined, Platform.canLand); @@ -85,11 +98,8 @@ export class GameScene extends Phaser.Scene { if (this.levelCompleted || this.dying) return; this.player.update(); - for (const platform of this.disappearing) { - platform.react(this.player.x, this.player.y); - } - for (const spike of this.trickySpikes) { - spike.react(this.player.x); + for (const entity of this.reactives) { + entity.react(this.player.x, this.player.y); } if (this.player.y > this.scale.height + GameScene.FALL_LIMIT_PADDING) {