Added rail tracks, cart and camera movement with arrow keys, removed enemies... 4 now

This commit is contained in:
2026-01-31 13:47:34 +11:00
parent b18e2d08ba
commit 43b33733e9
22 changed files with 712 additions and 395 deletions

View File

@@ -30,6 +30,7 @@ export class DungeonRenderer {
private entityAccessor!: EntityAccessor;
private ecsWorld!: ECSWorld;
private trapSprites: Map<number, Phaser.GameObjects.Sprite> = new Map();
private trackSprites: Phaser.GameObjects.Sprite[] = [];
constructor(scene: Phaser.Scene) {
this.scene = scene;
@@ -50,6 +51,12 @@ export class DungeonRenderer {
}
this.trapSprites.clear();
for (const sprite of this.trackSprites) {
sprite.destroy();
}
this.trackSprites = [];
this.trapSprites.clear();
for (const [, sprite] of this.enemySprites) {
sprite.destroy();
}
@@ -68,22 +75,39 @@ export class DungeonRenderer {
// Setup Tilemap
if (this.map) this.map.destroy();
this.map = this.scene.make.tilemap({
data: Array.from({ length: world.height }, (_, y) =>
Array.from({ length: world.width }, (_, x) => this.world.tiles[idx(this.world, x, y)])
),
tileWidth: 16,
tileHeight: 16
tileWidth: TILE_SIZE,
tileHeight: TILE_SIZE,
width: world.width,
height: world.height
});
const tileset = this.map.addTilesetImage("dungeon", "dungeon", 16, 16, 0, 0)!;
this.layer = this.map.createLayer(0, tileset, 0, 0)!;
this.layer.setDepth(0);
const tileset = this.map.addTilesetImage("dungeon", "dungeon");
if (!tileset) {
console.error("[DungeonRenderer] FAILED to load tileset 'dungeon'!");
// Fallback or throw?
}
// Initial tile states (hidden)
this.layer.forEachTile(tile => {
tile.setVisible(false);
});
this.layer = this.map.createBlankLayer("floor", tileset || "dungeon")!;
if (this.layer) {
this.layer.setDepth(0);
this.layer.setVisible(true);
console.log(`[DungeonRenderer] Layer created. Size: ${world.width}x${world.height}`);
} else {
console.error("[DungeonRenderer] FAILED to create tilemap layer!");
}
let tilesPlaced = 0;
for (let y = 0; y < world.height; y++) {
for (let x = 0; x < world.width; x++) {
const i = y * world.width + x;
const tile = world.tiles[i];
if (tile !== undefined && this.layer) {
this.layer.putTileAt(tile, x, y);
tilesPlaced++;
}
}
}
console.log(`[DungeonRenderer] Placed ${tilesPlaced} tiles.`);
this.fxRenderer.clearCorpses();
// Ensure player sprite exists
@@ -111,25 +135,41 @@ export class DungeonRenderer {
}
}
// Create sprites for ECS trap entities
// Create sprites for ECS entities with sprites (traps, mine carts, etc.)
if (this.ecsWorld) {
const traps = this.ecsWorld.getEntitiesWith("trigger", "position", "sprite");
for (const trapId of traps) {
const pos = this.ecsWorld.getComponent(trapId, "position");
const spriteData = this.ecsWorld.getComponent(trapId, "sprite");
console.log(`[DungeonRenderer] Creating ECS sprites...`);
const spriteEntities = this.ecsWorld.getEntitiesWith("position", "sprite");
for (const entId of spriteEntities) {
// Skip player as it's handled separately
const player = this.ecsWorld.getComponent(entId, "player");
if (player) continue;
const pos = this.ecsWorld.getComponent(entId, "position");
const spriteData = this.ecsWorld.getComponent(entId, "sprite");
if (pos && spriteData) {
const sprite = this.scene.add.sprite(
pos.x * TILE_SIZE + TILE_SIZE / 2,
pos.y * TILE_SIZE + TILE_SIZE / 2,
spriteData.texture,
spriteData.index
);
sprite.setDepth(5); // Below actors, above floor
sprite.setVisible(false); // Hidden until FOV reveals
this.trapSprites.set(trapId, sprite);
try {
const isStandalone = spriteData.texture === "mine_cart" || spriteData.texture === "ceramic_dragon_head";
const sprite = this.scene.add.sprite(
pos.x * TILE_SIZE + TILE_SIZE / 2,
pos.y * TILE_SIZE + TILE_SIZE / 2,
spriteData.texture,
isStandalone ? undefined : (spriteData.index ?? 0)
);
sprite.setDepth(5);
sprite.setVisible(true); // Force visible for diagnostics
sprite.setAlpha(1.0); // Force opaque for diagnostics
sprite.setDisplaySize(TILE_SIZE, TILE_SIZE);
console.log(`[DungeonRenderer] Created sprite for ${spriteData.texture} at ${pos.x},${pos.y}`);
this.trapSprites.set(entId, sprite);
} catch (e) {
console.error(`[DungeonRenderer] Failed to create sprite for entity ${entId}:`, e);
}
}
}
}
// Render static tracks
this.renderTracks();
}
@@ -163,9 +203,16 @@ export class DungeonRenderer {
return this.fovManager.seenArray;
}
private firstRender = true;
render(_playerPath: Vec2[]) {
if (!this.world || !this.layer) return;
if (this.firstRender) {
console.log(`[DungeonRenderer] First render call... World: ${this.world.width}x${this.world.height}`);
this.firstRender = false;
}
const seen = this.fovManager.seenArray;
const visible = this.fovManager.visibleArray;
@@ -217,6 +264,18 @@ export class DungeonRenderer {
}
});
// Update track sprites visibility
for (const sprite of this.trackSprites) {
const tx = Math.floor(sprite.x / TILE_SIZE);
const ty = Math.floor(sprite.y / TILE_SIZE);
const i = idx(this.world, tx, ty);
const isSeen = seen[i] === 1;
const isVis = visible[i] === 1;
sprite.setVisible(isSeen);
sprite.alpha = isVis ? 1.0 : 0.3;
}
// Update trap sprites visibility and appearance
if (this.ecsWorld) {
for (const [trapId, sprite] of this.trapSprites) {
@@ -224,14 +283,43 @@ export class DungeonRenderer {
const spriteData = this.ecsWorld.getComponent(trapId, "sprite");
if (pos && spriteData) {
// Bounds check
if (pos.x < 0 || pos.x >= this.world.width || pos.y < 0 || pos.y >= this.world.height) {
sprite.setVisible(false);
continue;
}
const i = idx(this.world, pos.x, pos.y);
const isSeen = seen[i] === 1;
const isVis = visible[i] === 1;
sprite.setVisible(isSeen);
// Update position (with simple smoothing)
const targetX = pos.x * TILE_SIZE + TILE_SIZE / 2;
const targetY = pos.y * TILE_SIZE + TILE_SIZE / 2;
if (sprite.x !== targetX || sprite.y !== targetY) {
// Check if it's far away (teleport) or nearby (tween)
const dist = Phaser.Math.Distance.Between(sprite.x, sprite.y, targetX, targetY);
if (dist > TILE_SIZE * 2) {
this.scene.tweens.killTweensOf(sprite);
sprite.setPosition(targetX, targetY);
} else if (!this.scene.tweens.isTweening(sprite)) {
this.scene.tweens.add({
targets: sprite,
x: targetX,
y: targetY,
duration: GAME_CONFIG.rendering.moveDuration,
ease: 'Power1'
});
}
}
// Update sprite frame in case trap was triggered
if (sprite.frame.name !== String(spriteData.index)) {
const isStandalone = spriteData.texture === "mine_cart" || spriteData.texture === "ceramic_dragon_head";
if (!isStandalone && sprite.frame.name !== String(spriteData.index)) {
sprite.setFrame(spriteData.index);
}
@@ -491,13 +579,8 @@ export class DungeonRenderer {
? this.scene.add.sprite(startX, startY, texture)
: this.scene.add.sprite(startX, startY, texture, frame);
// Scale for standalone 24x24 image should be 1.0 (or matching world scale)
// Other sprites are 16x16.
if (isStandalone) {
sprite.setDisplaySize(16, 16);
} else {
sprite.setScale(1.0);
}
// Ensure all sprites fit in a single 16x16 tile.
sprite.setDisplaySize(TILE_SIZE, TILE_SIZE);
sprite.setDepth(2000);
@@ -526,4 +609,59 @@ export class DungeonRenderer {
shakeCamera() {
this.scene.cameras.main.shake(100, 0.01);
}
private renderTracks() {
if (!this.world.trackPath || this.world.trackPath.length === 0) return;
const path = this.world.trackPath;
for (let i = 0; i < path.length; i++) {
const curr = path[i];
const prev = i > 0 ? path[i - 1] : null;
const next = i < path.length - 1 ? path[i + 1] : null;
let spriteKey = "track_straight";
let angle = 0;
if (prev && next) {
const dx1 = curr.x - prev.x;
const dy1 = curr.y - prev.y;
const dx2 = next.x - curr.x;
const dy2 = next.y - curr.y;
if (dx1 === dx2 && dy1 === dy2) {
// Straight
spriteKey = "track_straight";
angle = dx1 === 0 ? 0 : 90; // Asset is vertical (0 deg), rotate to 90 for horizontal
} else {
// Corner
spriteKey = "track_corner";
const p = { dx: prev.x - curr.x, dy: prev.y - curr.y };
const n = { dx: next.x - curr.x, dy: next.y - curr.y };
// Top-Right: 180, Right-Bottom: 270, Bottom-Left: 0, Left-Top: 90
if ((p.dy === -1 && n.dx === 1) || (n.dy === -1 && p.dx === 1)) angle = 180;
else if ((p.dx === 1 && n.dy === 1) || (n.dx === 1 && p.dy === 1)) angle = 270;
else if ((p.dy === 1 && n.dx === -1) || (n.dy === 1 && p.dx === -1)) angle = 0;
else if ((p.dx === -1 && n.dy === -1) || (n.dx === -1 && p.dy === -1)) angle = 90;
}
} else if (next) {
spriteKey = "track_straight";
angle = (next.x === curr.x) ? 0 : 90;
} else if (prev) {
spriteKey = "track_straight";
angle = (prev.x === curr.x) ? 0 : 90;
}
const sprite = this.scene.add.sprite(
curr.x * TILE_SIZE + TILE_SIZE / 2,
curr.y * TILE_SIZE + TILE_SIZE / 2,
spriteKey
);
sprite.setAngle(angle);
sprite.setDisplaySize(TILE_SIZE, TILE_SIZE);
sprite.setDepth(2);
sprite.setVisible(false);
this.trackSprites.push(sprite);
}
}
}

View File

@@ -141,12 +141,7 @@ export class FxRenderer {
0
);
corpse.setDepth(50);
// Use display size for Priestess sprites to match 1 tile
if (textureKey.startsWith("Priestess")) {
corpse.setDisplaySize(TILE_SIZE, TILE_SIZE);
} else {
corpse.setScale(1.0); // Reset to standard scale for spritesheet assets
}
corpse.setDisplaySize(TILE_SIZE, TILE_SIZE); // All corpses should be tile-sized