obscales
This commit is contained in:
37
src/entities/ObstaclePlatformDisappearing.ts
Normal file
37
src/entities/ObstaclePlatformDisappearing.ts
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
import Phaser from "phaser";
|
||||||
|
|
||||||
|
export class ObstaclePlatformDisappearing extends Phaser.GameObjects.Rectangle {
|
||||||
|
static readonly COLOR = 0x9966cc;
|
||||||
|
static readonly TRIGGER_DISTANCE = 110;
|
||||||
|
static readonly FADE_MS = 200;
|
||||||
|
|
||||||
|
declare body: Phaser.Physics.Arcade.StaticBody;
|
||||||
|
|
||||||
|
private vanished = false;
|
||||||
|
|
||||||
|
constructor(scene: Phaser.Scene, x: number, y: number, width: number, height: number) {
|
||||||
|
super(scene, x, y, width, height, ObstaclePlatformDisappearing.COLOR);
|
||||||
|
scene.add.existing(this);
|
||||||
|
scene.physics.add.existing(this, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
react(playerX: number, playerY: number): void {
|
||||||
|
if (this.vanished) return;
|
||||||
|
const dx = this.x - playerX;
|
||||||
|
const dy = this.y - playerY;
|
||||||
|
if (Math.hypot(dx, dy) < ObstaclePlatformDisappearing.TRIGGER_DISTANCE) {
|
||||||
|
this.vanish();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private vanish(): void {
|
||||||
|
this.vanished = true;
|
||||||
|
this.body.enable = false;
|
||||||
|
this.scene.tweens.add({
|
||||||
|
targets: this,
|
||||||
|
alpha: 0,
|
||||||
|
duration: ObstaclePlatformDisappearing.FADE_MS,
|
||||||
|
onComplete: () => this.setVisible(false),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
15
src/entities/ObstacleSpikeNormal.ts
Normal file
15
src/entities/ObstacleSpikeNormal.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import Phaser from "phaser";
|
||||||
|
|
||||||
|
export class ObstacleSpikeNormal extends Phaser.GameObjects.Rectangle {
|
||||||
|
static readonly WIDTH = 40;
|
||||||
|
static readonly HEIGHT = 20;
|
||||||
|
static readonly COLOR = 0xff3333;
|
||||||
|
|
||||||
|
declare body: Phaser.Physics.Arcade.StaticBody;
|
||||||
|
|
||||||
|
constructor(scene: Phaser.Scene, x: number, y: number) {
|
||||||
|
super(scene, x, y, ObstacleSpikeNormal.WIDTH, ObstacleSpikeNormal.HEIGHT, ObstacleSpikeNormal.COLOR);
|
||||||
|
scene.add.existing(this);
|
||||||
|
scene.physics.add.existing(this, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
30
src/entities/ObstacleSpikeTricky.ts
Normal file
30
src/entities/ObstacleSpikeTricky.ts
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
import Phaser from "phaser";
|
||||||
|
|
||||||
|
export class ObstacleSpikeTricky extends Phaser.GameObjects.Rectangle {
|
||||||
|
static readonly WIDTH = 40;
|
||||||
|
static readonly HEIGHT = 20;
|
||||||
|
static readonly COLOR = 0xff8800;
|
||||||
|
static readonly TRIGGER_DISTANCE = 140;
|
||||||
|
static readonly DISPLACEMENT = 80;
|
||||||
|
|
||||||
|
declare body: Phaser.Physics.Arcade.StaticBody;
|
||||||
|
|
||||||
|
private moved = false;
|
||||||
|
|
||||||
|
constructor(scene: Phaser.Scene, x: number, y: number) {
|
||||||
|
super(scene, x, y, ObstacleSpikeTricky.WIDTH, ObstacleSpikeTricky.HEIGHT, ObstacleSpikeTricky.COLOR);
|
||||||
|
scene.add.existing(this);
|
||||||
|
scene.physics.add.existing(this, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
react(playerX: number): void {
|
||||||
|
if (this.moved) return;
|
||||||
|
const dx = this.x - playerX;
|
||||||
|
if (Math.abs(dx) < ObstacleSpikeTricky.TRIGGER_DISTANCE) {
|
||||||
|
const direction = Math.sign(dx) || 1;
|
||||||
|
this.x += direction * ObstacleSpikeTricky.DISPLACEMENT;
|
||||||
|
this.body.updateFromGameObject();
|
||||||
|
this.moved = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,12 +2,18 @@ import Phaser from "phaser";
|
|||||||
import { Player } from "../entities/Player";
|
import { Player } from "../entities/Player";
|
||||||
import { Exit } from "../entities/Exit";
|
import { Exit } from "../entities/Exit";
|
||||||
import { Platform } from "../entities/Platform";
|
import { Platform } from "../entities/Platform";
|
||||||
|
import { ObstaclePlatformDisappearing } from "../entities/ObstaclePlatformDisappearing";
|
||||||
|
import { ObstacleSpikeNormal } from "../entities/ObstacleSpikeNormal";
|
||||||
|
import { ObstacleSpikeTricky } from "../entities/ObstacleSpikeTricky";
|
||||||
import { LevelMatrix, TILE_SIZE, TileType } from "./levels";
|
import { LevelMatrix, TILE_SIZE, TileType } from "./levels";
|
||||||
|
|
||||||
export interface LoadedLevel {
|
export interface LoadedLevel {
|
||||||
player: Player;
|
player: Player;
|
||||||
exit: Exit;
|
exit: Exit;
|
||||||
platforms: Platform[];
|
platforms: Platform[];
|
||||||
|
disappearing: ObstaclePlatformDisappearing[];
|
||||||
|
spikes: ObstacleSpikeNormal[];
|
||||||
|
trickySpikes: ObstacleSpikeTricky[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface Cell {
|
interface Cell {
|
||||||
@@ -19,30 +25,50 @@ export class LevelLoader {
|
|||||||
constructor(private readonly scene: Phaser.Scene) {}
|
constructor(private readonly scene: Phaser.Scene) {}
|
||||||
|
|
||||||
load(matrix: LevelMatrix): LoadedLevel {
|
load(matrix: LevelMatrix): LoadedLevel {
|
||||||
const platforms = this.buildPlatforms(matrix);
|
return {
|
||||||
const player = this.buildPlayer(matrix);
|
player: this.buildPlayer(matrix),
|
||||||
const exit = this.buildExit(matrix);
|
exit: this.buildExit(matrix),
|
||||||
return { player, exit, platforms };
|
platforms: this.buildHorizontalRuns(
|
||||||
|
matrix,
|
||||||
|
TileType.Platform,
|
||||||
|
(x, y, w, h) => new Platform(this.scene, x, y, w, h),
|
||||||
|
),
|
||||||
|
disappearing: this.buildHorizontalRuns(
|
||||||
|
matrix,
|
||||||
|
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);
|
||||||
|
}),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private buildHorizontalRuns<T>(
|
||||||
* Merge contiguous Platform tiles in each row into a single rectangle to
|
matrix: LevelMatrix,
|
||||||
* avoid corner-catching issues between adjacent 1-tile static bodies.
|
tileType: TileType,
|
||||||
*/
|
factory: (x: number, y: number, width: number, height: number) => T,
|
||||||
private buildPlatforms(matrix: LevelMatrix): Platform[] {
|
): T[] {
|
||||||
const result: Platform[] = [];
|
const result: T[] = [];
|
||||||
for (let row = 0; row < matrix.length; row++) {
|
for (let row = 0; row < matrix.length; row++) {
|
||||||
const cols = matrix[row].length;
|
const cols = matrix[row].length;
|
||||||
let runStart = -1;
|
let runStart = -1;
|
||||||
for (let col = 0; col <= cols; col++) {
|
for (let col = 0; col <= cols; col++) {
|
||||||
const isPlatform = col < cols && matrix[row][col] === TileType.Platform;
|
const match = col < cols && matrix[row][col] === tileType;
|
||||||
if (isPlatform && runStart === -1) {
|
if (match && runStart === -1) {
|
||||||
runStart = col;
|
runStart = col;
|
||||||
} else if (!isPlatform && runStart !== -1) {
|
} else if (!match && runStart !== -1) {
|
||||||
const width = (col - runStart) * TILE_SIZE;
|
const width = (col - runStart) * TILE_SIZE;
|
||||||
const x = runStart * TILE_SIZE + width / 2;
|
const x = runStart * TILE_SIZE + width / 2;
|
||||||
const y = row * TILE_SIZE + TILE_SIZE / 2;
|
const y = row * TILE_SIZE + TILE_SIZE / 2;
|
||||||
result.push(new Platform(this.scene, x, y, width, TILE_SIZE));
|
result.push(factory(x, y, width, TILE_SIZE));
|
||||||
runStart = -1;
|
runStart = -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,6 +76,20 @@ export class LevelLoader {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private buildSingleCells<T>(
|
||||||
|
matrix: LevelMatrix,
|
||||||
|
tileType: TileType,
|
||||||
|
factory: (col: number, row: 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
private buildPlayer(matrix: LevelMatrix): Player {
|
private buildPlayer(matrix: LevelMatrix): Player {
|
||||||
const cell = this.findTile(matrix, TileType.Player);
|
const cell = this.findTile(matrix, TileType.Player);
|
||||||
if (!cell) throw new Error("Level matrix has no Player tile");
|
if (!cell) throw new Error("Level matrix has no Player tile");
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ export enum TileType {
|
|||||||
Platform = 1,
|
Platform = 1,
|
||||||
Player = 2,
|
Player = 2,
|
||||||
Exit = 3,
|
Exit = 3,
|
||||||
|
DisappearingPlatform = 4,
|
||||||
|
Spike = 5,
|
||||||
|
TrickySpike = 6,
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LevelMatrix = TileType[][];
|
export type LevelMatrix = TileType[][];
|
||||||
@@ -21,6 +24,9 @@ const CHAR_TO_TILE: Record<string, TileType> = {
|
|||||||
"#": TileType.Platform,
|
"#": TileType.Platform,
|
||||||
"P": TileType.Player,
|
"P": TileType.Player,
|
||||||
"E": TileType.Exit,
|
"E": TileType.Exit,
|
||||||
|
"~": TileType.DisappearingPlatform,
|
||||||
|
"^": TileType.Spike,
|
||||||
|
"T": TileType.TrickySpike,
|
||||||
};
|
};
|
||||||
|
|
||||||
function parseLevel(rows: string[]): LevelMatrix {
|
function parseLevel(rows: string[]): LevelMatrix {
|
||||||
@@ -40,7 +46,24 @@ function defineLevel(name: string, rows: string[]): LevelDef {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const LEVELS: readonly LevelDef[] = [
|
export const LEVELS: readonly LevelDef[] = [
|
||||||
defineLevel("Sétálj át", [
|
defineLevel("Ugorj a tüskén át", [
|
||||||
|
"....................",
|
||||||
|
"....................",
|
||||||
|
"....................",
|
||||||
|
"....................",
|
||||||
|
"....................",
|
||||||
|
"....................",
|
||||||
|
"....................",
|
||||||
|
"....................",
|
||||||
|
"....................",
|
||||||
|
"....................",
|
||||||
|
"....................",
|
||||||
|
"....................",
|
||||||
|
".E........^........P",
|
||||||
|
"####################",
|
||||||
|
"####################",
|
||||||
|
]),
|
||||||
|
defineLevel("Csapda híd", [
|
||||||
"....................",
|
"....................",
|
||||||
"....................",
|
"....................",
|
||||||
"....................",
|
"....................",
|
||||||
@@ -54,27 +77,10 @@ export const LEVELS: readonly LevelDef[] = [
|
|||||||
"....................",
|
"....................",
|
||||||
"....................",
|
"....................",
|
||||||
".E.................P",
|
".E.................P",
|
||||||
"####################",
|
"######~~~~##########",
|
||||||
"####################",
|
"######^^^^##########",
|
||||||
]),
|
]),
|
||||||
defineLevel("Ugorj át a szakadékokon", [
|
defineLevel("Csaló tüske és lépcső", [
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
"....................",
|
|
||||||
".E.................P",
|
|
||||||
"####....####....####",
|
|
||||||
"####....####....####",
|
|
||||||
]),
|
|
||||||
defineLevel("Mászd meg a lépcsőt", [
|
|
||||||
"....................",
|
"....................",
|
||||||
"....................",
|
"....................",
|
||||||
"....................",
|
"....................",
|
||||||
@@ -88,7 +94,7 @@ export const LEVELS: readonly LevelDef[] = [
|
|||||||
"........#####.......",
|
"........#####.......",
|
||||||
"............#####...",
|
"............#####...",
|
||||||
"...............#####",
|
"...............#####",
|
||||||
"...................P",
|
".........T.........P",
|
||||||
"####################",
|
"####################",
|
||||||
]),
|
]),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -2,6 +2,9 @@ import Phaser from "phaser";
|
|||||||
import { Player } from "../entities/Player";
|
import { Player } from "../entities/Player";
|
||||||
import { Exit } from "../entities/Exit";
|
import { Exit } from "../entities/Exit";
|
||||||
import { Platform } from "../entities/Platform";
|
import { Platform } from "../entities/Platform";
|
||||||
|
import { ObstaclePlatformDisappearing } from "../entities/ObstaclePlatformDisappearing";
|
||||||
|
import { ObstacleSpikeNormal } from "../entities/ObstacleSpikeNormal";
|
||||||
|
import { ObstacleSpikeTricky } from "../entities/ObstacleSpikeTricky";
|
||||||
import { LEVELS } from "../levels/levels";
|
import { LEVELS } from "../levels/levels";
|
||||||
import { LevelLoader } from "../levels/LevelLoader";
|
import { LevelLoader } from "../levels/LevelLoader";
|
||||||
|
|
||||||
@@ -17,7 +20,11 @@ export class GameScene extends Phaser.Scene {
|
|||||||
private player!: Player;
|
private player!: Player;
|
||||||
private exit!: Exit;
|
private exit!: Exit;
|
||||||
private platforms: Platform[] = [];
|
private platforms: Platform[] = [];
|
||||||
|
private disappearing: ObstaclePlatformDisappearing[] = [];
|
||||||
|
private spikes: ObstacleSpikeNormal[] = [];
|
||||||
|
private trickySpikes: ObstacleSpikeTricky[] = [];
|
||||||
private levelCompleted = false;
|
private levelCompleted = false;
|
||||||
|
private dying = false;
|
||||||
private hudText!: Phaser.GameObjects.Text;
|
private hudText!: Phaser.GameObjects.Text;
|
||||||
private centerText!: Phaser.GameObjects.Text;
|
private centerText!: Phaser.GameObjects.Text;
|
||||||
|
|
||||||
@@ -28,7 +35,11 @@ export class GameScene extends Phaser.Scene {
|
|||||||
init(data: SceneData): void {
|
init(data: SceneData): void {
|
||||||
this.levelIndex = data.levelIndex ?? 0;
|
this.levelIndex = data.levelIndex ?? 0;
|
||||||
this.levelCompleted = false;
|
this.levelCompleted = false;
|
||||||
|
this.dying = false;
|
||||||
this.platforms = [];
|
this.platforms = [];
|
||||||
|
this.disappearing = [];
|
||||||
|
this.spikes = [];
|
||||||
|
this.trickySpikes = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
create(): void {
|
create(): void {
|
||||||
@@ -40,9 +51,15 @@ export class GameScene extends Phaser.Scene {
|
|||||||
this.player = loaded.player;
|
this.player = loaded.player;
|
||||||
this.exit = loaded.exit;
|
this.exit = loaded.exit;
|
||||||
this.platforms = loaded.platforms;
|
this.platforms = loaded.platforms;
|
||||||
|
this.disappearing = loaded.disappearing;
|
||||||
|
this.spikes = loaded.spikes;
|
||||||
|
this.trickySpikes = 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.overlap(this.player, this.exit, () => this.completeLevel());
|
this.physics.add.overlap(this.player, this.exit, () => this.completeLevel());
|
||||||
|
this.physics.add.overlap(this.player, this.spikes, () => this.die());
|
||||||
|
this.physics.add.overlap(this.player, this.trickySpikes, () => this.die());
|
||||||
|
|
||||||
this.hudText = this.add.text(
|
this.hudText = this.add.text(
|
||||||
12,
|
12,
|
||||||
@@ -63,16 +80,23 @@ export class GameScene extends Phaser.Scene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override update(): void {
|
override update(): void {
|
||||||
if (this.levelCompleted) return;
|
if (this.levelCompleted || this.dying) return;
|
||||||
this.player.update();
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.player.y > this.scale.height + GameScene.FALL_LIMIT_PADDING) {
|
if (this.player.y > this.scale.height + GameScene.FALL_LIMIT_PADDING) {
|
||||||
this.restartLevel();
|
this.die();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private completeLevel(): void {
|
private completeLevel(): void {
|
||||||
if (this.levelCompleted) return;
|
if (this.levelCompleted || this.dying) return;
|
||||||
this.levelCompleted = true;
|
this.levelCompleted = true;
|
||||||
this.player.freeze();
|
this.player.freeze();
|
||||||
|
|
||||||
@@ -88,7 +112,10 @@ export class GameScene extends Phaser.Scene {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private restartLevel(): void {
|
private die(): void {
|
||||||
|
if (this.levelCompleted || this.dying) return;
|
||||||
|
this.dying = true;
|
||||||
|
this.player.freeze();
|
||||||
this.scene.restart({ levelIndex: this.levelIndex });
|
this.scene.restart({ levelIndex: this.levelIndex });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user