S2: Unit spawn & control milestone
- Port skin system into Unit base class (animations via _createAnimation/playAnimation) - Wire SystemOrchestrator + all systems into Map_Player create/update - Connect SelectionSystem to scene input (disable old Interface handlers) - Switch pathfinding dispatch to new PathfindingSystem - Auto-spawn player units on game start
This commit is contained in:
@@ -87,12 +87,16 @@ export default class Unit extends Phaser.Physics.Arcade.Sprite {
|
||||
_createAnimation(config) {
|
||||
const key = config.key;
|
||||
if (!this.scene.anims.exists(key)) {
|
||||
this.scene.anims.create({
|
||||
const animConfig = {
|
||||
key,
|
||||
frames: config.frames || [],
|
||||
frameRate: config.frameRate || 10,
|
||||
repeat: config.repeat ?? -1,
|
||||
});
|
||||
};
|
||||
if (config.repeatDelay !== undefined) {
|
||||
animConfig.repeatDelay = config.repeatDelay;
|
||||
}
|
||||
this.scene.anims.create(animConfig);
|
||||
}
|
||||
this._anims.set(key, true);
|
||||
if (!this._currentAnim) {
|
||||
|
||||
@@ -64,7 +64,7 @@ export default class Infantry extends Unit {
|
||||
setAnimations() {
|
||||
for (const key in this.STATES) {
|
||||
const stateConfig = this.STATES[key];
|
||||
this.scene.anims.create({
|
||||
this._createAnimation({
|
||||
key: stateConfig.key,
|
||||
frames: this.scene.anims.generateFrameNumbers(this.skin, {
|
||||
start: stateConfig.animationConfig.start || 0,
|
||||
|
||||
@@ -8,7 +8,7 @@ export default {
|
||||
repeat: -1,
|
||||
},
|
||||
onEnter: (ctx) => {
|
||||
ctx.anims.play("IDLING");
|
||||
ctx._playAnimation("IDLING");
|
||||
},
|
||||
onExit: (ctx) => {},
|
||||
updateFunction: (ctx, time, delta) => {
|
||||
@@ -26,7 +26,7 @@ export default {
|
||||
repeat: -1,
|
||||
},
|
||||
onEnter: (ctx) => {
|
||||
ctx.anims.play("MOVING");
|
||||
ctx._playAnimation("MOVING");
|
||||
ctx.clearTarget();
|
||||
},
|
||||
onExit: (ctx) => {},
|
||||
@@ -45,7 +45,7 @@ export default {
|
||||
repeat: 0,
|
||||
},
|
||||
onEnter: (ctx) => {
|
||||
ctx.anims.play("DYING");
|
||||
ctx._playAnimation("DYING");
|
||||
ctx.dead = true;
|
||||
setTimeout(() => {
|
||||
ctx.destroy();
|
||||
@@ -63,7 +63,7 @@ export default {
|
||||
repeat: -1,
|
||||
},
|
||||
onEnter: (ctx) => {
|
||||
ctx.anims.play("SHOOTING");
|
||||
ctx._playAnimation("SHOOTING");
|
||||
ctx.orientToTarget();
|
||||
},
|
||||
onExit: (ctx) => {
|
||||
|
||||
@@ -8,7 +8,7 @@ export default {
|
||||
repeat: -1,
|
||||
},
|
||||
onEnter: (ctx) => {
|
||||
ctx.anims.play("IDLING");
|
||||
ctx._playAnimation("IDLING");
|
||||
},
|
||||
onExit: (ctx) => {},
|
||||
updateFunction: (ctx, time, delta) => {
|
||||
@@ -26,7 +26,7 @@ export default {
|
||||
repeat: -1,
|
||||
},
|
||||
onEnter: (ctx) => {
|
||||
ctx.anims.play("MOVING");
|
||||
ctx._playAnimation("MOVING");
|
||||
ctx.clearTarget();
|
||||
},
|
||||
onExit: (ctx) => {},
|
||||
@@ -45,7 +45,7 @@ export default {
|
||||
repeat: 0,
|
||||
},
|
||||
onEnter: (ctx) => {
|
||||
ctx.anims.play("DYING");
|
||||
ctx._playAnimation("DYING");
|
||||
ctx.dead = true;
|
||||
setTimeout(() => {
|
||||
ctx.destroy();
|
||||
@@ -64,7 +64,7 @@ export default {
|
||||
repeat: -1,
|
||||
},
|
||||
onEnter: (ctx) => {
|
||||
ctx.anims.play("SHOOTING");
|
||||
ctx._playAnimation("SHOOTING");
|
||||
ctx.orientToTarget();
|
||||
},
|
||||
onExit: (ctx) => {
|
||||
|
||||
@@ -67,7 +67,7 @@ export default class Tank extends Unit {
|
||||
setAnimations() {
|
||||
for (const key in this.STATES) {
|
||||
const stateConfig = this.STATES[key];
|
||||
this.scene.anims.create({
|
||||
this._createAnimation({
|
||||
key: stateConfig.key,
|
||||
frames: this.scene.anims.generateFrameNumbers(this.skin, {
|
||||
start: stateConfig.animationConfig.start || 0,
|
||||
@@ -75,7 +75,7 @@ export default class Tank extends Unit {
|
||||
}),
|
||||
frameRate: stateConfig.animationConfig.frameRate || 10,
|
||||
repeat: stateConfig.animationConfig.repeat || 0,
|
||||
repeatDelay: stateConfig.animationConfig.repeatDelay || 0,
|
||||
repeatDelay: stateConfig.animationConfig.repeatDelay,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,21 +170,10 @@ export default class Interface {
|
||||
Phaser.Input.Keyboard.KeyCodes.SHIFT
|
||||
);
|
||||
|
||||
this.scene.input.on(
|
||||
Phaser.Input.Events.POINTER_DOWN,
|
||||
this.handlePointerDown,
|
||||
this
|
||||
);
|
||||
this.scene.input.on(
|
||||
Phaser.Input.Events.POINTER_MOVE,
|
||||
this.handlePointerMove,
|
||||
this
|
||||
);
|
||||
this.scene.input.on(
|
||||
Phaser.Input.Events.POINTER_UP,
|
||||
this.handlePointerUp,
|
||||
this
|
||||
);
|
||||
// NOTE: Pointer DOWN / MOVE / UP are now handled exclusively by
|
||||
// SelectionSystem. The old Interface handlers (handlePointerDown,
|
||||
// handlePointerMove, handlePointerUp) are kept on the class so
|
||||
// existing call-sites compile but are no longer wired.
|
||||
this.scene.input.on(
|
||||
Phaser.Input.Events.POINTER_WHEEL,
|
||||
this.handlePointerWheel,
|
||||
|
||||
@@ -5,6 +5,7 @@ import Ukrainian_Rifle from "Entities/skins/ukrainian-infantry";
|
||||
import CONSTANTS from "PhaserClasses/CustomConstants";
|
||||
import Interface from "PhaserClasses/interface";
|
||||
import { NetworkSystemClient } from "Systems/NetworkSystem.js";
|
||||
import SystemOrchestrator from "Systems/SystemOrchestrator.js";
|
||||
|
||||
export default class Map_Player extends Phaser.Scene {
|
||||
constructor() {
|
||||
@@ -125,8 +126,43 @@ export default class Map_Player extends Phaser.Scene {
|
||||
this.createMap();
|
||||
this.interface = new Interface(this).init();
|
||||
|
||||
// ── System Orchestrator: initialize all 9+ systems ────────────────────
|
||||
const colyseus = this.game?.colyseus;
|
||||
this.orchestrator = new SystemOrchestrator(this, {
|
||||
room: colyseus?.room ?? null,
|
||||
mapKey: 'test1',
|
||||
tilesetKey: 'floorsPrimary',
|
||||
tilesetName: 'floorsPrimary',
|
||||
debug: false,
|
||||
});
|
||||
this.orchestrator.init();
|
||||
|
||||
// Initialize pathfinding after MapSystem has the tilemap
|
||||
this.orchestrator.initPathfinding();
|
||||
|
||||
// Wire up Colyseus networking if the client was created by Server_Connector
|
||||
this._initNetworking();
|
||||
|
||||
// Wire the orchestrator's NetworkSystem to the scene for update() access
|
||||
this._syncNetworkSystem();
|
||||
|
||||
// ── Auto-spawn player units ──────────────────────────────────────────
|
||||
this.createInfantry();
|
||||
|
||||
// ── Clean up on shutdown ─────────────────────────────────────────────
|
||||
this.events.on('shutdown', () => {
|
||||
this.orchestrator?.shutdown();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync the orchestrator-managed NetworkSystemClient reference to
|
||||
* `this.networkSystem` so that both old and new code can find it.
|
||||
*/
|
||||
_syncNetworkSystem() {
|
||||
if (this.orchestrator?.systems?.network) {
|
||||
this.networkSystem = this.orchestrator.systems.network;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -171,6 +207,11 @@ export default class Map_Player extends Phaser.Scene {
|
||||
|
||||
this.networkSystem = new NetworkSystemClient(this, room);
|
||||
|
||||
// Also register with the orchestrator so its update loop ticks it
|
||||
if (this.orchestrator) {
|
||||
this.orchestrator.systems.network = this.networkSystem;
|
||||
}
|
||||
|
||||
console.log(
|
||||
"[Map_Player] NetworkSystemClient wired to room:",
|
||||
room.id
|
||||
@@ -180,9 +221,10 @@ export default class Map_Player extends Phaser.Scene {
|
||||
update(time, delta) {
|
||||
this.interface.controls.update(delta);
|
||||
|
||||
// Tick the network system (snapshot interpolation + scene application)
|
||||
if (this.networkSystem) {
|
||||
this.networkSystem.update(time, delta);
|
||||
// Canonical orchestrator tick: selection → economy → CPs → buildings →
|
||||
// entities → pathfinding → combat → network
|
||||
if (this.orchestrator) {
|
||||
this.orchestrator.update(time, delta);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,6 +227,12 @@ export default class SelectionSystem {
|
||||
const { tile } = target;
|
||||
if (!tile) return;
|
||||
|
||||
const pathfinding = this.scene.orchestrator?.systems?.pathfinding;
|
||||
if (!pathfinding) {
|
||||
console.warn('[SelectionSystem] PathfindingSystem not available');
|
||||
return;
|
||||
}
|
||||
|
||||
const positions = this.getFormationPositions(leaderPos, entities.length);
|
||||
|
||||
entities.forEach((entity, i) => {
|
||||
@@ -235,17 +241,22 @@ export default class SelectionSystem {
|
||||
y: tile.y + (positions[i]?.y ?? 0),
|
||||
};
|
||||
|
||||
if (this.scene.interface?.pathfinder) {
|
||||
const startTile = this.scene.interface.generateTileXY(
|
||||
new Phaser.Math.Vector2(entity.x, entity.y),
|
||||
);
|
||||
this.scene.interface.pathfinder.findPath(
|
||||
entity,
|
||||
startTile,
|
||||
offsetTile,
|
||||
false,
|
||||
);
|
||||
}
|
||||
const startTile = pathfinding.worldToTileCoords(entity.x, entity.y);
|
||||
|
||||
pathfinding.findPath(startTile, offsetTile, {}, (path) => {
|
||||
if (path === null) {
|
||||
console.warn(`[SelectionSystem] No path found for entity`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Entities expect the path format from EasyStar: [{x, y}, ...]
|
||||
// Tank.moveToPath(path) and Infantry.moveToPath(path, shiftDown)
|
||||
if (typeof entity.moveToPath === 'function') {
|
||||
// Determine if shift is held (waypoint append for infantry)
|
||||
const shiftDown = this.shiftKey?.isDown ?? false;
|
||||
entity.moveToPath(path, shiftDown);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -663,6 +674,28 @@ export default class SelectionSystem {
|
||||
this.handlePointerUp,
|
||||
this,
|
||||
);
|
||||
|
||||
// --- Right-click: issue commands ---
|
||||
// Phaser fires POINTER_DOWN for both buttons. We multiplex inside the
|
||||
// handler: left/middle goes through handlePointerDown → handlePointerUp,
|
||||
// right goes through handleRightClick.
|
||||
this.scene.input.on(
|
||||
Phaser.Input.Events.POINTER_DOWN,
|
||||
this.#onPointerDownMultiplex,
|
||||
this,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Multiplex POINTER_DOWN: right-clicks → handleRightClick,
|
||||
* everything else → already handled by handlePointerDown.
|
||||
* This is a separate listener registered after handlePointerDown so both
|
||||
* fire, but handlePointerDown bails early when rightButtonDown() is true.
|
||||
*/
|
||||
#onPointerDownMultiplex(pointer, currentlyOver) {
|
||||
if (pointer.rightButtonDown()) {
|
||||
this.handleRightClick(pointer, currentlyOver);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -685,6 +718,11 @@ export default class SelectionSystem {
|
||||
this.handlePointerUp,
|
||||
this,
|
||||
);
|
||||
this.scene.input.off(
|
||||
Phaser.Input.Events.POINTER_DOWN,
|
||||
this.#onPointerDownMultiplex,
|
||||
this,
|
||||
);
|
||||
|
||||
if (this.selectionBox) {
|
||||
this.selectionBox.destroy();
|
||||
|
||||
Reference in New Issue
Block a user