feat: player entity with WASD movement, tile collision, camera follow (Phase 4.1)
Player spawns at walkable tile near map center. WASD controls movement (150px/s, normalized diagonal). Tile collision with wall-sliding prevents walking through acid pools, crystals, geysers. Camera follows player with smooth lerp. 39 new tests (134 total). Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import Phaser from 'phaser';
|
||||
import { createGameWorld, updateTime, type GameWorld } from '../ecs/world';
|
||||
import { Position } from '../ecs/components';
|
||||
import { movementSystem } from '../ecs/systems/movement';
|
||||
import { healthSystem } from '../ecs/systems/health';
|
||||
import { removeGameEntity } from '../ecs/factory';
|
||||
@@ -8,23 +9,40 @@ import biomeDataArray from '../data/biomes.json';
|
||||
import type { BiomeData } from '../world/types';
|
||||
import { generateWorld } from '../world/generator';
|
||||
import { createWorldTilemap } from '../world/tilemap';
|
||||
import { setupCamera, updateCamera, type CameraKeys } from '../world/camera';
|
||||
import { setupPlayerCamera } from '../world/camera';
|
||||
import { Minimap } from '../world/minimap';
|
||||
import { playerInputSystem } from '../player/input';
|
||||
import { tileCollisionSystem, buildWalkableSet } from '../player/collision';
|
||||
import { findSpawnPosition } from '../player/spawn';
|
||||
import { createPlayerEntity } from '../player/factory';
|
||||
import type { InputState } from '../player/types';
|
||||
|
||||
export class GameScene extends Phaser.Scene {
|
||||
private gameWorld!: GameWorld;
|
||||
private bridge!: PhaserBridge;
|
||||
private cameraKeys!: CameraKeys;
|
||||
private minimap!: Minimap;
|
||||
private statsText!: Phaser.GameObjects.Text;
|
||||
private worldSeed!: number;
|
||||
|
||||
// Player state
|
||||
private playerEid!: number;
|
||||
private walkableSet!: Set<number>;
|
||||
private worldGrid!: number[][];
|
||||
private tileSize!: number;
|
||||
private keys!: {
|
||||
W: Phaser.Input.Keyboard.Key;
|
||||
A: Phaser.Input.Keyboard.Key;
|
||||
S: Phaser.Input.Keyboard.Key;
|
||||
D: Phaser.Input.Keyboard.Key;
|
||||
E: Phaser.Input.Keyboard.Key;
|
||||
};
|
||||
|
||||
constructor() {
|
||||
super({ key: 'GameScene' });
|
||||
}
|
||||
|
||||
create(): void {
|
||||
// 1. Initialize ECS (needed for future entity systems)
|
||||
// 1. Initialize ECS
|
||||
this.gameWorld = createGameWorld();
|
||||
this.bridge = new PhaserBridge(this);
|
||||
|
||||
@@ -36,15 +54,45 @@ export class GameScene extends Phaser.Scene {
|
||||
// 3. Create tilemap
|
||||
createWorldTilemap(this, worldData);
|
||||
|
||||
// 4. Camera with bounds and WASD controls
|
||||
// 4. Build walkable set + store world data for collision
|
||||
this.walkableSet = buildWalkableSet(biome.tiles);
|
||||
this.worldGrid = worldData.grid;
|
||||
this.tileSize = biome.tileSize;
|
||||
|
||||
// 5. Create player at spawn position
|
||||
const spawn = findSpawnPosition(worldData.grid, biome.tileSize, this.walkableSet);
|
||||
const spawnX = spawn?.x ?? (biome.mapWidth * biome.tileSize) / 2;
|
||||
const spawnY = spawn?.y ?? (biome.mapHeight * biome.tileSize) / 2;
|
||||
this.playerEid = createPlayerEntity(this.gameWorld.world, spawnX, spawnY);
|
||||
|
||||
// 6. Camera — follow player, zoom via scroll wheel
|
||||
const worldPixelW = biome.mapWidth * biome.tileSize;
|
||||
const worldPixelH = biome.mapHeight * biome.tileSize;
|
||||
this.cameraKeys = setupCamera(this, worldPixelW, worldPixelH);
|
||||
setupPlayerCamera(this, worldPixelW, worldPixelH);
|
||||
|
||||
// 5. Minimap
|
||||
// Sync bridge to create player sprite, then attach camera follow
|
||||
this.bridge.sync(this.gameWorld.world);
|
||||
const playerSprite = this.bridge.getSprite(this.playerEid);
|
||||
if (playerSprite) {
|
||||
playerSprite.setDepth(10);
|
||||
this.cameras.main.startFollow(playerSprite, true, 0.1, 0.1);
|
||||
}
|
||||
|
||||
// 7. Keyboard input
|
||||
const keyboard = this.input.keyboard;
|
||||
if (!keyboard) throw new Error('Keyboard plugin not available');
|
||||
this.keys = {
|
||||
W: keyboard.addKey('W'),
|
||||
A: keyboard.addKey('A'),
|
||||
S: keyboard.addKey('S'),
|
||||
D: keyboard.addKey('D'),
|
||||
E: keyboard.addKey('E'),
|
||||
};
|
||||
|
||||
// 8. Minimap
|
||||
this.minimap = new Minimap(this, worldData);
|
||||
|
||||
// 6. UI overlay
|
||||
// 9. UI stats overlay
|
||||
this.statsText = this.add.text(10, 10, '', {
|
||||
fontSize: '12px',
|
||||
color: '#00ff88',
|
||||
@@ -60,24 +108,46 @@ export class GameScene extends Phaser.Scene {
|
||||
// 1. Update world time
|
||||
updateTime(this.gameWorld, delta);
|
||||
|
||||
// 2. Camera movement
|
||||
updateCamera(this, this.cameraKeys, delta);
|
||||
// 2. Read keyboard → InputState
|
||||
const input: InputState = {
|
||||
moveX: (this.keys.D.isDown ? 1 : 0) - (this.keys.A.isDown ? 1 : 0),
|
||||
moveY: (this.keys.S.isDown ? 1 : 0) - (this.keys.W.isDown ? 1 : 0),
|
||||
interact: this.keys.E.isDown,
|
||||
};
|
||||
|
||||
// 3. ECS systems (no entities yet — future phases will add player, creatures)
|
||||
// 3. Player input → velocity
|
||||
playerInputSystem(this.gameWorld.world, input);
|
||||
|
||||
// 4. Movement (all entities)
|
||||
movementSystem(this.gameWorld.world, delta);
|
||||
|
||||
// 5. Tile collision (player only)
|
||||
tileCollisionSystem(
|
||||
this.gameWorld.world,
|
||||
delta,
|
||||
this.worldGrid,
|
||||
this.tileSize,
|
||||
this.walkableSet,
|
||||
);
|
||||
|
||||
// 6. Health / death
|
||||
const dead = healthSystem(this.gameWorld.world);
|
||||
for (const eid of dead) {
|
||||
removeGameEntity(this.gameWorld.world, eid);
|
||||
}
|
||||
|
||||
// 7. Render sync
|
||||
this.bridge.sync(this.gameWorld.world);
|
||||
|
||||
// 4. Minimap viewport
|
||||
// 8. Minimap viewport
|
||||
this.minimap.update(this.cameras.main);
|
||||
|
||||
// 5. Stats
|
||||
// 9. Stats
|
||||
const fps = delta > 0 ? Math.round(1000 / delta) : 0;
|
||||
const px = Math.round(Position.x[this.playerEid]);
|
||||
const py = Math.round(Position.y[this.playerEid]);
|
||||
this.statsText.setText(
|
||||
`seed: ${this.worldSeed} | ${fps} fps | WASD move, scroll zoom`,
|
||||
`seed: ${this.worldSeed} | ${fps} fps | pos: ${px},${py} | WASD move, E interact, scroll zoom`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user