refact round
This commit is contained in:
@@ -1,5 +1,20 @@
|
|||||||
import Phaser from "phaser";
|
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 {
|
export class Platform extends Phaser.GameObjects.TileSprite {
|
||||||
static readonly TEXTURE_KEY = "platform";
|
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.
|
// Phaser collider processCallback: only collide when the mover is descending onto the top.
|
||||||
static canLand(
|
static canLand: Phaser.Types.Physics.Arcade.ArcadePhysicsCallback = (mover, platform) => {
|
||||||
mover: Phaser.Types.Physics.Arcade.GameObjectWithBody,
|
const moverBody = getBody(mover) as Phaser.Physics.Arcade.Body;
|
||||||
platform: Phaser.Types.Physics.Arcade.GameObjectWithBody,
|
const platBody = getBody(platform) as Phaser.Physics.Arcade.StaticBody;
|
||||||
): boolean {
|
|
||||||
const moverBody = mover.body as Phaser.Physics.Arcade.Body;
|
|
||||||
const platBody = platform.body as Phaser.Physics.Arcade.StaticBody;
|
|
||||||
return (
|
return (
|
||||||
moverBody.velocity.y >= 0 &&
|
moverBody.velocity.y >= 0 &&
|
||||||
moverBody.bottom - moverBody.deltaY() <= platBody.top
|
moverBody.bottom - moverBody.deltaY() <= platBody.top
|
||||||
);
|
);
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
11
src/entities/obstacles/ObstacleSpikeBase.ts
Normal file
11
src/entities/obstacles/ObstacleSpikeBase.ts
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,15 +1,12 @@
|
|||||||
import Phaser from "phaser";
|
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 TEXTURE_KEY = "spike-normal";
|
||||||
static readonly WIDTH = 40;
|
static readonly WIDTH = 40;
|
||||||
static readonly HEIGHT = 20;
|
static readonly HEIGHT = 20;
|
||||||
|
|
||||||
declare body: Phaser.Physics.Arcade.StaticBody;
|
|
||||||
|
|
||||||
constructor(scene: Phaser.Scene, x: number, y: number) {
|
constructor(scene: Phaser.Scene, x: number, y: number) {
|
||||||
super(scene, x, y, ObstacleSpikeNormal.TEXTURE_KEY);
|
super(scene, x, y, ObstacleSpikeNormal.TEXTURE_KEY);
|
||||||
scene.add.existing(this);
|
|
||||||
scene.physics.add.existing(this, true);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,23 +1,20 @@
|
|||||||
import Phaser from "phaser";
|
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 TEXTURE_KEY = "spike-tricky";
|
||||||
static readonly WIDTH = 40;
|
static readonly WIDTH = 40;
|
||||||
static readonly HEIGHT = 20;
|
static readonly HEIGHT = 20;
|
||||||
static readonly TRIGGER_DISTANCE = 55;
|
static readonly TRIGGER_DISTANCE = 55;
|
||||||
static readonly DISPLACEMENT = 100;
|
static readonly DISPLACEMENT = 100;
|
||||||
|
|
||||||
declare body: Phaser.Physics.Arcade.StaticBody;
|
|
||||||
|
|
||||||
private moved = false;
|
private moved = false;
|
||||||
|
|
||||||
constructor(scene: Phaser.Scene, x: number, y: number) {
|
constructor(scene: Phaser.Scene, x: number, y: number) {
|
||||||
super(scene, x, y, ObstacleSpikeTricky.TEXTURE_KEY);
|
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;
|
if (this.moved) return;
|
||||||
const dx = this.x - playerX;
|
const dx = this.x - playerX;
|
||||||
if (Math.abs(dx) < ObstacleSpikeTricky.TRIGGER_DISTANCE) {
|
if (Math.abs(dx) < ObstacleSpikeTricky.TRIGGER_DISTANCE) {
|
||||||
|
|||||||
@@ -1,18 +1,6 @@
|
|||||||
import { defineLevel } from "../lib/levelTypes";
|
import { defineLevel } from "../lib/levelTypes";
|
||||||
|
|
||||||
export const level01 = defineLevel("Spike Hop", [
|
export const level01 = defineLevel("Spike Hop", [
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
".E........^........P",
|
".E........^........P",
|
||||||
"####################",
|
"####################",
|
||||||
"####################",
|
"####################",
|
||||||
|
|||||||
@@ -1,18 +1,6 @@
|
|||||||
import { defineLevel } from "../lib/levelTypes";
|
import { defineLevel } from "../lib/levelTypes";
|
||||||
|
|
||||||
export const level02 = defineLevel("Trap Bridge", [
|
export const level02 = defineLevel("Trap Bridge", [
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
".E.................P",
|
".E.................P",
|
||||||
"#######~~~##########",
|
"#######~~~##########",
|
||||||
"#######~~~##########",
|
"#######~~~##########",
|
||||||
|
|||||||
@@ -1,13 +1,6 @@
|
|||||||
import { defineLevel } from "../lib/levelTypes";
|
import { defineLevel } from "../lib/levelTypes";
|
||||||
|
|
||||||
export const level03 = defineLevel("Trickster Stairs", [
|
export const level03 = defineLevel("Trickster Stairs", [
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
".E..................",
|
".E..................",
|
||||||
"#####...............",
|
"#####...............",
|
||||||
"....#####...........",
|
"....#####...........",
|
||||||
|
|||||||
@@ -1,18 +1,6 @@
|
|||||||
import { defineLevel } from "../lib/levelTypes";
|
import { defineLevel } from "../lib/levelTypes";
|
||||||
|
|
||||||
export const level04 = defineLevel("Spike Field", [
|
export const level04 = defineLevel("Spike Field", [
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
".E....^...^...^....P",
|
".E....^...^...^....P",
|
||||||
"####################",
|
"####################",
|
||||||
"####################",
|
"####################",
|
||||||
|
|||||||
@@ -1,18 +1,6 @@
|
|||||||
import { defineLevel } from "../lib/levelTypes";
|
import { defineLevel } from "../lib/levelTypes";
|
||||||
|
|
||||||
export const level05 = defineLevel("Pit Crossing", [
|
export const level05 = defineLevel("Pit Crossing", [
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
".E.................P",
|
".E.................P",
|
||||||
"####...######...####",
|
"####...######...####",
|
||||||
"####^^^######^^^####",
|
"####^^^######^^^####",
|
||||||
|
|||||||
@@ -1,18 +1,6 @@
|
|||||||
import { defineLevel } from "../lib/levelTypes";
|
import { defineLevel } from "../lib/levelTypes";
|
||||||
|
|
||||||
export const level06 = defineLevel("Trickster", [
|
export const level06 = defineLevel("Trickster", [
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
".E..........T......P",
|
".E..........T......P",
|
||||||
"####################",
|
"####################",
|
||||||
"####################",
|
"####################",
|
||||||
|
|||||||
@@ -1,18 +1,6 @@
|
|||||||
import { defineLevel } from "../lib/levelTypes";
|
import { defineLevel } from "../lib/levelTypes";
|
||||||
|
|
||||||
export const level07 = defineLevel("Bridge Hopping", [
|
export const level07 = defineLevel("Bridge Hopping", [
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
".E.................P",
|
".E.................P",
|
||||||
"####...#####...#####",
|
"####...#####...#####",
|
||||||
"####...#####...#####",
|
"####...#####...#####",
|
||||||
|
|||||||
@@ -1,18 +1,6 @@
|
|||||||
import { defineLevel } from "../lib/levelTypes";
|
import { defineLevel } from "../lib/levelTypes";
|
||||||
|
|
||||||
export const level08 = defineLevel("Mixed Hazards", [
|
export const level08 = defineLevel("Mixed Hazards", [
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
".E..^......T....^..P",
|
".E..^......T....^..P",
|
||||||
"####################",
|
"####################",
|
||||||
"####################",
|
"####################",
|
||||||
|
|||||||
@@ -1,18 +1,6 @@
|
|||||||
import { defineLevel } from "../lib/levelTypes";
|
import { defineLevel } from "../lib/levelTypes";
|
||||||
|
|
||||||
export const level09 = defineLevel("Vanishing Bridges", [
|
export const level09 = defineLevel("Vanishing Bridges", [
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
".E.................P",
|
".E.................P",
|
||||||
"####~~~####~~~######",
|
"####~~~####~~~######",
|
||||||
"####~~~####~~~######",
|
"####~~~####~~~######",
|
||||||
|
|||||||
@@ -1,18 +1,6 @@
|
|||||||
import { defineLevel } from "../lib/levelTypes";
|
import { defineLevel } from "../lib/levelTypes";
|
||||||
|
|
||||||
export const level10 = defineLevel("Final Gauntlet", [
|
export const level10 = defineLevel("Final Gauntlet", [
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
".E......T..........P",
|
".E......T..........P",
|
||||||
"####...####~~~######",
|
"####...####~~~######",
|
||||||
"####^^^####...######",
|
"####^^^####...######",
|
||||||
|
|||||||
@@ -18,18 +18,29 @@ export interface LoadedLevel {
|
|||||||
trickySpikes: ObstacleSpikeTricky[];
|
trickySpikes: ObstacleSpikeTricky[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Cell {
|
const cellCenterX = (col: number): number => col * TILE_SIZE + TILE_SIZE / 2;
|
||||||
row: number;
|
const cellBottomY = (row: number, entityHeight: number): number =>
|
||||||
col: number;
|
(row + 1) * TILE_SIZE - entityHeight / 2;
|
||||||
}
|
|
||||||
|
|
||||||
export class LevelLoader {
|
export class LevelLoader {
|
||||||
constructor(private readonly scene: Phaser.Scene) {}
|
constructor(private readonly scene: Phaser.Scene) {}
|
||||||
|
|
||||||
load(matrix: LevelMatrix): LoadedLevel {
|
load(matrix: LevelMatrix): LoadedLevel {
|
||||||
return {
|
return {
|
||||||
player: this.buildPlayer(matrix),
|
player: this.buildSingleton(
|
||||||
exit: this.buildExit(matrix),
|
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(
|
platforms: this.buildHorizontalRuns(
|
||||||
matrix,
|
matrix,
|
||||||
TileType.Platform,
|
TileType.Platform,
|
||||||
@@ -40,16 +51,18 @@ export class LevelLoader {
|
|||||||
TileType.DisappearingPlatform,
|
TileType.DisappearingPlatform,
|
||||||
(x, y, w, h) => new ObstaclePlatformDisappearing(this.scene, x, y, w, h),
|
(x, y, w, h) => new ObstaclePlatformDisappearing(this.scene, x, y, w, h),
|
||||||
),
|
),
|
||||||
spikes: this.buildSingleCells(matrix, TileType.Spike, (col, row) => {
|
spikes: this.buildSingleCells(
|
||||||
const x = col * TILE_SIZE + TILE_SIZE / 2;
|
matrix,
|
||||||
const y = (row + 1) * TILE_SIZE - ObstacleSpikeNormal.HEIGHT / 2;
|
TileType.Spike,
|
||||||
return new ObstacleSpikeNormal(this.scene, x, y);
|
ObstacleSpikeNormal.HEIGHT,
|
||||||
}),
|
(x, y) => new ObstacleSpikeNormal(this.scene, x, y),
|
||||||
trickySpikes: this.buildSingleCells(matrix, TileType.TrickySpike, (col, row) => {
|
),
|
||||||
const x = col * TILE_SIZE + TILE_SIZE / 2;
|
trickySpikes: this.buildSingleCells(
|
||||||
const y = (row + 1) * TILE_SIZE - ObstacleSpikeTricky.HEIGHT / 2;
|
matrix,
|
||||||
return new ObstacleSpikeTricky(this.scene, x, y);
|
TileType.TrickySpike,
|
||||||
}),
|
ObstacleSpikeTricky.HEIGHT,
|
||||||
|
(x, y) => new ObstacleSpikeTricky(this.scene, x, y),
|
||||||
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,39 +94,34 @@ export class LevelLoader {
|
|||||||
private buildSingleCells<T>(
|
private buildSingleCells<T>(
|
||||||
matrix: LevelMatrix,
|
matrix: LevelMatrix,
|
||||||
tileType: TileType,
|
tileType: TileType,
|
||||||
factory: (col: number, row: number) => T,
|
entityHeight: number,
|
||||||
|
factory: (x: number, y: number) => T,
|
||||||
): T[] {
|
): T[] {
|
||||||
const result: T[] = [];
|
const result: T[] = [];
|
||||||
for (let row = 0; row < matrix.length; row++) {
|
for (let row = 0; row < matrix.length; row++) {
|
||||||
for (let col = 0; col < matrix[row].length; col++) {
|
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;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private buildPlayer(matrix: LevelMatrix): Player {
|
private buildSingleton<T>(
|
||||||
const cell = this.findTile(matrix, TileType.Player);
|
matrix: LevelMatrix,
|
||||||
if (!cell) throw new Error("Level matrix has no Player tile");
|
tileType: TileType,
|
||||||
const x = cell.col * TILE_SIZE + TILE_SIZE / 2;
|
entityHeight: number,
|
||||||
const y = (cell.row + 1) * TILE_SIZE - Player.SIZE / 2;
|
factory: (x: number, y: number) => T,
|
||||||
return new Player(this.scene, x, y);
|
label: string,
|
||||||
}
|
): T {
|
||||||
|
|
||||||
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 {
|
|
||||||
for (let row = 0; row < matrix.length; row++) {
|
for (let row = 0; row < matrix.length; row++) {
|
||||||
for (let col = 0; col < matrix[row].length; col++) {
|
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`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,8 @@ const CHAR_TO_TILE: Record<string, TileType> = {
|
|||||||
"T": TileType.TrickySpike,
|
"T": TileType.TrickySpike,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const EMPTY_ROW = ".".repeat(LEVEL_COLS);
|
||||||
|
|
||||||
function parseLevel(rows: string[]): LevelMatrix {
|
function parseLevel(rows: string[]): LevelMatrix {
|
||||||
return rows.map((row, rowIdx) =>
|
return rows.map((row, rowIdx) =>
|
||||||
[...row].map((c, colIdx) => {
|
[...row].map((c, colIdx) => {
|
||||||
@@ -42,5 +44,12 @@ function parseLevel(rows: string[]): LevelMatrix {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function defineLevel(name: string, rows: string[]): LevelDef {
|
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) };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,26 +7,69 @@ import {
|
|||||||
ObstacleSpikeTricky,
|
ObstacleSpikeTricky,
|
||||||
} from "../entities/obstacles";
|
} 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 {
|
export class BootScene extends Phaser.Scene {
|
||||||
constructor() {
|
constructor() {
|
||||||
super({ key: "BootScene" });
|
super({ key: "BootScene" });
|
||||||
}
|
}
|
||||||
|
|
||||||
preload(): void {
|
preload(): void {
|
||||||
this.load.svg(Player.TEXTURE_KEY, "/sprites/player.svg", { width: 32, height: 32 });
|
for (const asset of SPRITE_ASSETS) {
|
||||||
this.load.svg(Exit.TEXTURE_KEY, "/sprites/exit.svg", {
|
this.load.svg(asset.key, asset.path, { width: asset.width, height: asset.height });
|
||||||
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,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
create(): void {
|
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.tileSprite(cx, h - 20, w, 40, Platform.TEXTURE_KEY);
|
||||||
|
|
||||||
this.add
|
this.add.text(cx, 130, "TRICKSTER", TITLE_STYLE).setOrigin(0.5);
|
||||||
.text(cx, 130, "TRICKSTER", {
|
this.add.text(cx, 200, "TILES", TITLE_STYLE).setOrigin(0.5);
|
||||||
fontFamily: "Arial Black, Arial",
|
this.add.text(cx, 270, "a pixel-art platformer", SUBTITLE_STYLE).setOrigin(0.5);
|
||||||
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.image(cx - 80, 360, Player.TEXTURE_KEY);
|
this.add.image(cx - 80, 360, Player.TEXTURE_KEY);
|
||||||
this.add.image(cx, 374, ObstacleSpikeNormal.TEXTURE_KEY);
|
this.add.image(cx, 374, ObstacleSpikeNormal.TEXTURE_KEY);
|
||||||
this.add.image(cx + 80, 374, ObstacleSpikeTricky.TEXTURE_KEY);
|
this.add.image(cx + 80, 374, ObstacleSpikeTricky.TEXTURE_KEY);
|
||||||
|
|
||||||
const startText = this.add
|
const startText = this.add
|
||||||
.text(cx, 450, "PRESS SPACE TO START", {
|
.text(cx, 450, "PRESS SPACE TO START", PROMPT_STYLE)
|
||||||
fontFamily: "Arial",
|
|
||||||
fontSize: "26px",
|
|
||||||
color: "#ffffff",
|
|
||||||
fontStyle: "bold",
|
|
||||||
})
|
|
||||||
.setOrigin(0.5);
|
.setOrigin(0.5);
|
||||||
|
|
||||||
this.tweens.add({
|
this.tweens.add({
|
||||||
@@ -91,11 +102,7 @@ export class BootScene extends Phaser.Scene {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.add
|
this.add
|
||||||
.text(cx, 510, "← → / A D to move SPACE / ↑ / W to jump", {
|
.text(cx, 510, "← → / A D to move SPACE / ↑ / W to jump", HINT_STYLE)
|
||||||
fontFamily: "Arial",
|
|
||||||
fontSize: "14px",
|
|
||||||
color: "#888888",
|
|
||||||
})
|
|
||||||
.setOrigin(0.5);
|
.setOrigin(0.5);
|
||||||
|
|
||||||
const start = () => this.scene.start("GameScene");
|
const start = () => this.scene.start("GameScene");
|
||||||
|
|||||||
@@ -14,9 +14,14 @@ interface SceneData {
|
|||||||
levelIndex?: number;
|
levelIndex?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface Reactive {
|
||||||
|
react(playerX: number, playerY: number): void;
|
||||||
|
}
|
||||||
|
|
||||||
export class GameScene extends Phaser.Scene {
|
export class GameScene extends Phaser.Scene {
|
||||||
private static readonly TRANSITION_DELAY_MS = 1200;
|
private static readonly TRANSITION_DELAY_MS = 1200;
|
||||||
private static readonly FALL_LIMIT_PADDING = 80;
|
private static readonly FALL_LIMIT_PADDING = 80;
|
||||||
|
private static readonly WORLD_BOUNDS_BOTTOM_PADDING = 200;
|
||||||
|
|
||||||
private levelIndex = 0;
|
private levelIndex = 0;
|
||||||
private player!: Player;
|
private player!: Player;
|
||||||
@@ -25,6 +30,7 @@ export class GameScene extends Phaser.Scene {
|
|||||||
private disappearing: ObstaclePlatformDisappearing[] = [];
|
private disappearing: ObstaclePlatformDisappearing[] = [];
|
||||||
private spikes: ObstacleSpikeNormal[] = [];
|
private spikes: ObstacleSpikeNormal[] = [];
|
||||||
private trickySpikes: ObstacleSpikeTricky[] = [];
|
private trickySpikes: ObstacleSpikeTricky[] = [];
|
||||||
|
private reactives: Reactive[] = [];
|
||||||
private levelCompleted = false;
|
private levelCompleted = false;
|
||||||
private dying = false;
|
private dying = false;
|
||||||
private hudText!: Phaser.GameObjects.Text;
|
private hudText!: Phaser.GameObjects.Text;
|
||||||
@@ -42,10 +48,16 @@ export class GameScene extends Phaser.Scene {
|
|||||||
this.disappearing = [];
|
this.disappearing = [];
|
||||||
this.spikes = [];
|
this.spikes = [];
|
||||||
this.trickySpikes = [];
|
this.trickySpikes = [];
|
||||||
|
this.reactives = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
create(): void {
|
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 def = LEVELS[this.levelIndex];
|
||||||
const loader = new LevelLoader(this);
|
const loader = new LevelLoader(this);
|
||||||
@@ -56,6 +68,7 @@ export class GameScene extends Phaser.Scene {
|
|||||||
this.disappearing = loaded.disappearing;
|
this.disappearing = loaded.disappearing;
|
||||||
this.spikes = loaded.spikes;
|
this.spikes = loaded.spikes;
|
||||||
this.trickySpikes = loaded.trickySpikes;
|
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.platforms, undefined, Platform.canLand);
|
||||||
this.physics.add.collider(this.player, this.disappearing, 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;
|
if (this.levelCompleted || this.dying) return;
|
||||||
this.player.update();
|
this.player.update();
|
||||||
|
|
||||||
for (const platform of this.disappearing) {
|
for (const entity of this.reactives) {
|
||||||
platform.react(this.player.x, this.player.y);
|
entity.react(this.player.x, this.player.y);
|
||||||
}
|
|
||||||
for (const spike of this.trickySpikes) {
|
|
||||||
spike.react(this.player.x);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.player.y > this.scale.height + GameScene.FALL_LIMIT_PADDING) {
|
if (this.player.y > this.scale.height + GameScene.FALL_LIMIT_PADDING) {
|
||||||
|
|||||||
Reference in New Issue
Block a user