From 7aaadee3c5bd2855aa09f83efe6bf7d14622fb51 Mon Sep 17 00:00:00 2001 From: Peter Stockings Date: Wed, 21 Jan 2026 20:18:02 +1100 Subject: [PATCH] feat: Add scene with track loop and mine cart --- src/core/config/GameConfig.ts | 19 ++++- src/engine/systems/MineCartSystem.ts | 62 ++++++++++++++++ src/engine/systems/TrackSystem.ts | 45 ++++++++++++ src/main.ts | 3 +- src/scenes/MenuScene.ts | 8 +++ src/scenes/TrackExplorationScene.ts | 104 +++++++++++++++++++++++++++ 6 files changed, 239 insertions(+), 2 deletions(-) create mode 100644 src/engine/systems/MineCartSystem.ts create mode 100644 src/engine/systems/TrackSystem.ts create mode 100644 src/scenes/TrackExplorationScene.ts diff --git a/src/core/config/GameConfig.ts b/src/core/config/GameConfig.ts index 4e74bcc..534fa45 100644 --- a/src/core/config/GameConfig.ts +++ b/src/core/config/GameConfig.ts @@ -112,7 +112,24 @@ export const GAME_CONFIG = { fogAlphaWall: 0.35, visibleMinAlpha: 0.35, visibleMaxAlpha: 1.0, - visibleStrengthFactor: 0.65 + visibleStrengthFactor: 0.65, + tracks: { + endTop: 67, + endBottom: 68, + cornerNE: 93, + horizontal: 70, + cornerSE: 69, + endLeft: 79, + endRight: 80, + vertical: 81, + cornerSW: 71, + cornerNW: 95 + }, + mineCarts: { + horizontal: 54, + vertical: 55, + turning: 56 + } }, ui: { diff --git a/src/engine/systems/MineCartSystem.ts b/src/engine/systems/MineCartSystem.ts new file mode 100644 index 0000000..855f22c --- /dev/null +++ b/src/engine/systems/MineCartSystem.ts @@ -0,0 +1,62 @@ +import Phaser from "phaser"; +import { GAME_CONFIG } from "../../core/config/GameConfig"; +import { TrackDirection } from "./TrackSystem"; + +export interface MineCartState { + x: number; + y: number; + facing: { dx: number, dy: number }; +} + +export class MineCartSystem { + static updateOrientation(sprite: Phaser.GameObjects.Sprite, dx: number, dy: number, _connections: TrackDirection) { + const { mineCarts } = GAME_CONFIG.rendering; + + // Horizontal movement + if (dx !== 0 && dy === 0) { + sprite.setFrame(mineCarts.horizontal); + sprite.setFlipX(dx < 0); + sprite.setAngle(0); + } + // Vertical movement + else if (dy !== 0 && dx === 0) { + sprite.setFrame(mineCarts.vertical); + sprite.setFlipY(false); + sprite.setAngle(0); + } + // Turning (Corner case) + else { + sprite.setFrame(mineCarts.turning); + // Logic for 56 (turned from right to down by default) + // We need to rotate/flip to match the actual turn. + // This is a bit complex without seeing the sprite, but we'll approximate: + if (dx > 0 && dy > 0) sprite.setAngle(0); // Right to Down + if (dx < 0 && dy < 0) sprite.setAngle(180); // Left to Up + if (dx > 0 && dy < 0) sprite.setAngle(-90); // Right to Up + if (dx < 0 && dy > 0) sprite.setAngle(90); // Left to Down + } + } + + static getNextPosition(current: { x: number, y: number }, dx: number, dy: number, isTrack: (x: number, y: number) => boolean): { x: number, y: number, dx: number, dy: number } | null { + const nextX = current.x + dx; + const nextY = current.y + dy; + + if (isTrack(nextX, nextY)) { + return { x: nextX, y: nextY, dx, dy }; + } + + // Try turning if blocked + const possibleTurns = [ + { tdx: dy, tdy: -dx }, // Left turn + { tdx: -dy, tdy: dx } // Right turn + ]; + + for (const turn of possibleTurns) { + if (isTrack(current.x + turn.tdx, current.y + turn.tdy)) { + return { x: current.x + turn.tdx, y: current.y + turn.tdy, dx: turn.tdx, dy: turn.tdy }; + } + } + + return null; + } +} diff --git a/src/engine/systems/TrackSystem.ts b/src/engine/systems/TrackSystem.ts new file mode 100644 index 0000000..c0cf1a8 --- /dev/null +++ b/src/engine/systems/TrackSystem.ts @@ -0,0 +1,45 @@ +import { GAME_CONFIG } from "../../core/config/GameConfig"; + +export const TrackDirection = { + NONE: 0, + NORTH: 1 << 0, + SOUTH: 1 << 1, + EAST: 1 << 2, + WEST: 1 << 3 +} as const; + +export type TrackDirection = number; + +export class TrackSystem { + static getTrackFrame(connections: TrackDirection): number { + const { tracks } = GAME_CONFIG.rendering; + + // Dead Ends + if (connections === TrackDirection.SOUTH) return tracks.endTop; + if (connections === TrackDirection.NORTH) return tracks.endBottom; + if (connections === TrackDirection.EAST) return tracks.endLeft; + if (connections === TrackDirection.WEST) return tracks.endRight; + + // Straights + if (connections === (TrackDirection.NORTH | TrackDirection.SOUTH)) return tracks.vertical; + if (connections === (TrackDirection.EAST | TrackDirection.WEST)) return tracks.horizontal; + + // Corners + if (connections === (TrackDirection.NORTH | TrackDirection.EAST)) return tracks.cornerNE; + if (connections === (TrackDirection.SOUTH | TrackDirection.EAST)) return tracks.cornerSE; + if (connections === (TrackDirection.SOUTH | TrackDirection.WEST)) return tracks.cornerSW; + if (connections === (TrackDirection.NORTH | TrackDirection.WEST)) return tracks.cornerNW; + + // Fallback to horizontal + return tracks.horizontal; + } + + static getConnectionsFromNeighbors(x: number, y: number, isTrack: (x: number, y: number) => boolean): TrackDirection { + let connections = TrackDirection.NONE; + if (isTrack(x, y - 1)) connections |= TrackDirection.NORTH; + if (isTrack(x, y + 1)) connections |= TrackDirection.SOUTH; + if (isTrack(x + 1, y)) connections |= TrackDirection.EAST; + if (isTrack(x - 1, y)) connections |= TrackDirection.WEST; + return connections; + } +} diff --git a/src/main.ts b/src/main.ts index 2b72dc3..665d2a1 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,6 +4,7 @@ import { GameScene } from "./scenes/GameScene"; import { MenuScene } from "./scenes/MenuScene"; import { PreloadScene } from "./scenes/PreloadScene"; import { AssetViewerScene } from "./scenes/AssetViewerScene"; +import { TrackExplorationScene } from "./scenes/TrackExplorationScene"; new Phaser.Game({ type: Phaser.AUTO, @@ -19,5 +20,5 @@ new Phaser.Game({ dom: { createContainer: true }, - scene: [PreloadScene, MenuScene, AssetViewerScene, GameScene, GameUI] + scene: [PreloadScene, MenuScene, AssetViewerScene, TrackExplorationScene, GameScene, GameUI] }); diff --git a/src/scenes/MenuScene.ts b/src/scenes/MenuScene.ts index 46fdb68..58f57d7 100644 --- a/src/scenes/MenuScene.ts +++ b/src/scenes/MenuScene.ts @@ -58,6 +58,7 @@ export class MenuScene extends Phaser.Scene { const startBtn = this.createButton(width / 2, buttonYStart, "ENTER DUNGEON", 0x2288ff); const optBtn = this.createButton(width / 2, buttonYStart + 80, "OPTIONS", 0x444444); const assetViewerBtn = this.createButton(width / 2, buttonYStart + 160, "ASSET VIEWER", 0xff9922); + const trackExplorationBtn = this.createButton(width / 2, buttonYStart + 240, "TRACK EXPLORATION", 0x00ff88); startBtn.on("pointerdown", () => { this.cameras.main.fadeOut(1000, 0, 0, 0); @@ -76,6 +77,13 @@ export class MenuScene extends Phaser.Scene { this.scene.start("AssetViewerScene"); }); }); + + trackExplorationBtn.on("pointerdown", () => { + this.cameras.main.fadeOut(500, 0, 0, 0); + this.cameras.main.once(Phaser.Cameras.Scene2D.Events.FADE_OUT_COMPLETE, () => { + this.scene.start("TrackExplorationScene"); + }); + }); } private createWindEffect() { diff --git a/src/scenes/TrackExplorationScene.ts b/src/scenes/TrackExplorationScene.ts new file mode 100644 index 0000000..bf78456 --- /dev/null +++ b/src/scenes/TrackExplorationScene.ts @@ -0,0 +1,104 @@ +import { GAME_CONFIG } from "../core/config/GameConfig"; +import { TrackSystem } from "../engine/systems/TrackSystem"; +import { MineCartSystem } from "../engine/systems/MineCartSystem"; + +export class TrackExplorationScene extends Phaser.Scene { + constructor() { + super("TrackExplorationScene"); + } + + private tracks: Set = new Set(); + private cart?: Phaser.GameObjects.Sprite; + private cartPos = { x: 5, y: 5 }; + private cartDir = { dx: 1, dy: 0 }; + private moveTimer?: Phaser.Time.TimerEvent; + + create() { + const { width, height } = this.scale; + + this.add.text(width / 2, 30, "TRACK EXPLORATION", { + fontSize: "32px", + color: "#ffffff" + }).setOrigin(0.5); + + const backBtn = this.add.text(width / 2, height - 30, "BACK TO MENU", { + fontSize: "24px", + color: "#2288ff" + }).setOrigin(0.5) + .setInteractive({ useHandCursor: true }) + .on("pointerdown", () => { + this.scene.start("MenuScene"); + }); + + // Silence lint warning if needed + backBtn.setAlpha(0.8); + + // Create a smaller track loop to test all corners clearly + const startX = 5; + const startY = 5; + const size = 5; // 5x5 loop + for (let x = startX; x < startX + size; x++) { + this.tracks.add(`${x},${startY}`); + this.tracks.add(`${x},${startY + size - 1}`); + } + for (let y = startY + 1; y < startY + size - 1; y++) { + this.tracks.add(`${startX},${y}`); + this.tracks.add(`${startX + size - 1},${y}`); + } + + // Render tracks + this.renderTracks(); + + // Add Mine Cart + const { mineCarts } = GAME_CONFIG.rendering; + this.cart = this.add.sprite(5 * 32 + 16, 5 * 32 + 16, "kennys_dungeon", mineCarts.horizontal); + this.cart.setScale(2); // Make it visible + + // Movement Loop + this.moveTimer = this.time.addEvent({ + delay: 500, + callback: this.moveCart, + callbackScope: this, + loop: true + }); + + this.events.on('shutdown', () => { + if (this.moveTimer) this.moveTimer.destroy(); + }); + } + + private renderTracks() { + this.tracks.forEach(posKey => { + const [x, y] = posKey.split(',').map(Number); + const connections = TrackSystem.getConnectionsFromNeighbors(x, y, (nx: number, ny: number) => this.tracks.has(`${nx},${ny}`)); + const frame = TrackSystem.getTrackFrame(connections); + this.add.sprite(x * 32 + 16, y * 32 + 16, "kennys_dungeon", frame).setScale(2); + }); + } + + private moveCart() { + if (!this.cart) return; + + const next = MineCartSystem.getNextPosition(this.cartPos, this.cartDir.dx, this.cartDir.dy, (nx: number, ny: number) => this.tracks.has(`${nx},${ny}`)); + + if (next) { + this.cartPos = { x: next.x, y: next.y }; + this.cartDir = { dx: next.dx, dy: next.dy }; + + const tx = next.x * 32 + 16; + const ty = next.y * 32 + 16; + + this.tweens.add({ + targets: this.cart, + x: tx, + y: ty, + duration: 400, + ease: 'Linear', + onStart: () => { + const connections = TrackSystem.getConnectionsFromNeighbors(next.x, next.y, (nx: number, ny: number) => this.tracks.has(`${nx},${ny}`)); + MineCartSystem.updateOrientation(this.cart!, next.dx, next.dy, connections); + } + }); + } + } +}