Added rail tracks, cart and camera movement with arrow keys, removed enemies... 4 now
This commit is contained in:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user