Use wall + floor assets from Pixel dungeon
This commit is contained in:
@@ -7,7 +7,9 @@ import { GAME_CONFIG } from "../core/config/GameConfig";
|
||||
|
||||
export class DungeonRenderer {
|
||||
private scene: Phaser.Scene;
|
||||
private gfx: Phaser.GameObjects.Graphics;
|
||||
private map?: Phaser.Tilemaps.Tilemap;
|
||||
private layer?: Phaser.Tilemaps.TilemapLayer;
|
||||
|
||||
private playerSprite?: Phaser.GameObjects.Sprite;
|
||||
private enemySprites: Map<EntityId, Phaser.GameObjects.Sprite> = new Map();
|
||||
private corpseSprites: Phaser.GameObjects.Sprite[] = [];
|
||||
@@ -25,36 +27,28 @@ export class DungeonRenderer {
|
||||
private minimapGfx!: Phaser.GameObjects.Graphics;
|
||||
private minimapContainer!: Phaser.GameObjects.Container;
|
||||
private minimapBg!: Phaser.GameObjects.Rectangle;
|
||||
private minimapVisible = false; // Off by default
|
||||
private minimapVisible = false;
|
||||
|
||||
constructor(scene: Phaser.Scene) {
|
||||
this.scene = scene;
|
||||
this.gfx = this.scene.add.graphics();
|
||||
|
||||
// Initialize minimap
|
||||
this.initMinimap();
|
||||
}
|
||||
|
||||
private initMinimap() {
|
||||
this.minimapContainer = this.scene.add.container(0, 0);
|
||||
this.minimapContainer.setScrollFactor(0); // Fixed to camera
|
||||
this.minimapContainer.setDepth(1001); // Same as menu
|
||||
this.minimapContainer.setScrollFactor(0);
|
||||
this.minimapContainer.setDepth(1001);
|
||||
|
||||
// Background panel (like menu)
|
||||
this.minimapBg = this.scene.add
|
||||
.rectangle(0, 0, GAME_CONFIG.ui.minimapPanelWidth, GAME_CONFIG.ui.minimapPanelHeight, 0x000000, 0.8)
|
||||
.setStrokeStyle(1, 0xffffff, 0.9)
|
||||
.setInteractive(); // Capture clicks
|
||||
.setInteractive();
|
||||
|
||||
this.minimapGfx = this.scene.add.graphics();
|
||||
|
||||
this.minimapContainer.add(this.minimapBg);
|
||||
this.minimapContainer.add(this.minimapGfx);
|
||||
|
||||
// Position in center
|
||||
this.positionMinimap();
|
||||
|
||||
// Start hidden
|
||||
this.minimapContainer.setVisible(false);
|
||||
}
|
||||
|
||||
@@ -64,22 +58,40 @@ export class DungeonRenderer {
|
||||
this.visible = new Uint8Array(this.world.width * this.world.height);
|
||||
this.visibleStrength = new Float32Array(this.world.width * this.world.height);
|
||||
|
||||
// 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
|
||||
});
|
||||
|
||||
const tileset = this.map.addTilesetImage("tiles0", "tiles0", 16, 16, 0, 0)!;
|
||||
this.layer = this.map.createLayer(0, tileset, 0, 0)!;
|
||||
this.layer.setDepth(0);
|
||||
|
||||
// Initial tile states (hidden)
|
||||
this.layer.forEachTile(tile => {
|
||||
tile.setVisible(false);
|
||||
});
|
||||
|
||||
// Clear old corpses
|
||||
for (const sprite of this.corpseSprites) {
|
||||
sprite.destroy();
|
||||
}
|
||||
this.corpseSprites = [];
|
||||
|
||||
// Setup player sprite
|
||||
// Setup player sprite
|
||||
if (!this.playerSprite) {
|
||||
this.playerSprite = this.scene.add.sprite(0, 0, "warrior", 0);
|
||||
this.playerSprite.setDepth(100);
|
||||
|
||||
// Calculate display size to fit within tile while maintaining 12:15 aspect ratio
|
||||
const scale = TILE_SIZE / 15; // Fit height to tile size
|
||||
// Calculate scale to fit 15px high sprite into 16px tile
|
||||
const scale = 1.0;
|
||||
this.playerSprite.setScale(scale);
|
||||
|
||||
// Simple animations from PD source
|
||||
this.scene.anims.create({
|
||||
key: 'warrior-idle',
|
||||
frames: this.scene.anims.generateFrameNumbers('warrior', { frames: [0, 0, 0, 1, 0, 0, 1, 1] }),
|
||||
@@ -104,7 +116,7 @@ export class DungeonRenderer {
|
||||
this.playerSprite.play('warrior-idle');
|
||||
}
|
||||
|
||||
// Rat animations
|
||||
// Enemy animations
|
||||
if (!this.scene.anims.exists('rat-idle')) {
|
||||
this.scene.anims.create({
|
||||
key: 'rat-idle',
|
||||
@@ -126,7 +138,6 @@ export class DungeonRenderer {
|
||||
});
|
||||
}
|
||||
|
||||
// Bat animations
|
||||
if (!this.scene.anims.exists('bat-idle')) {
|
||||
this.scene.anims.create({
|
||||
key: 'bat-idle',
|
||||
@@ -153,13 +164,11 @@ export class DungeonRenderer {
|
||||
return !isWall(this.world, x, y);
|
||||
});
|
||||
|
||||
// Position minimap
|
||||
this.positionMinimap();
|
||||
}
|
||||
|
||||
private positionMinimap() {
|
||||
const cam = this.scene.cameras.main;
|
||||
// Center on screen like menu
|
||||
this.minimapContainer.setPosition(cam.width / 2, cam.height / 2);
|
||||
}
|
||||
|
||||
@@ -187,7 +196,6 @@ export class DungeonRenderer {
|
||||
this.visible[i] = 1;
|
||||
this.seen[i] = 1;
|
||||
|
||||
// falloff: 1 at center, ~0.4 at radius edge
|
||||
const radiusT = Phaser.Math.Clamp(r / GAME_CONFIG.player.viewRadius, 0, 1);
|
||||
const falloff = 1 - radiusT * 0.6;
|
||||
const strength = Phaser.Math.Clamp(v * falloff, 0, 1);
|
||||
@@ -205,67 +213,31 @@ export class DungeonRenderer {
|
||||
return this.seen;
|
||||
}
|
||||
|
||||
render(playerPath: Vec2[]) {
|
||||
this.gfx.clear();
|
||||
render(_playerPath: Vec2[]) {
|
||||
if (!this.world || !this.layer) return;
|
||||
|
||||
if (!this.world) return;
|
||||
// Update Tiles
|
||||
this.layer.forEachTile(tile => {
|
||||
const i = idx(this.world, tile.x, tile.y);
|
||||
const isSeen = this.seen[i] === 1;
|
||||
const isVis = this.visible[i] === 1;
|
||||
|
||||
// Tiles w/ fog + falloff + silhouettes
|
||||
for (let y = 0; y < this.world.height; y++) {
|
||||
for (let x = 0; x < this.world.width; x++) {
|
||||
const i = idx(this.world, x, y);
|
||||
|
||||
const isSeen = this.seen[i] === 1;
|
||||
const isVis = this.visible[i] === 1;
|
||||
|
||||
if (!isSeen) {
|
||||
this.gfx.fillStyle(0x000000, 1);
|
||||
this.gfx.fillRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
|
||||
continue;
|
||||
}
|
||||
|
||||
const wall = isWall(this.world, x, y);
|
||||
const base = wall ? GAME_CONFIG.rendering.wallColor : GAME_CONFIG.rendering.floorColor;
|
||||
|
||||
let alpha: number;
|
||||
if (!isSeen) {
|
||||
tile.setVisible(false);
|
||||
} else {
|
||||
tile.setVisible(true);
|
||||
if (isVis) {
|
||||
const s = this.visibleStrength[i];
|
||||
alpha = Phaser.Math.Clamp(GAME_CONFIG.rendering.visibleMinAlpha + s * GAME_CONFIG.rendering.visibleStrengthFactor, GAME_CONFIG.rendering.visibleMinAlpha, GAME_CONFIG.rendering.visibleMaxAlpha);
|
||||
tile.alpha = 1.0;
|
||||
tile.tint = 0xffffff;
|
||||
} else {
|
||||
alpha = wall ? GAME_CONFIG.rendering.fogAlphaWall : GAME_CONFIG.rendering.fogAlphaFloor;
|
||||
tile.alpha = isWall(this.world, tile.x, tile.y) ? 0.4 : 0.2;
|
||||
tile.tint = 0x888888;
|
||||
}
|
||||
|
||||
this.gfx.fillStyle(base, alpha);
|
||||
this.gfx.fillRect(x * TILE_SIZE, y * TILE_SIZE, TILE_SIZE, TILE_SIZE);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Exit (stairs) if seen
|
||||
{
|
||||
const ex = this.world.exit.x;
|
||||
const ey = this.world.exit.y;
|
||||
const i = idx(this.world, ex, ey);
|
||||
if (this.seen[i] === 1) {
|
||||
const alpha = this.visible[i] === 1 ? 1.0 : GAME_CONFIG.rendering.visibleMinAlpha;
|
||||
this.gfx.fillStyle(GAME_CONFIG.rendering.exitColor, alpha);
|
||||
this.gfx.fillRect(ex * TILE_SIZE + 7, ey * TILE_SIZE + 7, TILE_SIZE - 14, TILE_SIZE - 14);
|
||||
}
|
||||
}
|
||||
|
||||
// Path preview (seen only)
|
||||
if (playerPath.length >= 2) {
|
||||
this.gfx.fillStyle(GAME_CONFIG.rendering.pathPreviewColor, GAME_CONFIG.rendering.visibleMinAlpha);
|
||||
for (const p of playerPath) {
|
||||
// We can check isSeen via internal helper or just local array since we're inside
|
||||
const i = idx(this.world, p.x, p.y);
|
||||
if (this.seen[i] !== 1) continue;
|
||||
this.gfx.fillRect(p.x * TILE_SIZE + 6, p.y * TILE_SIZE + 6, TILE_SIZE - 12, TILE_SIZE - 12);
|
||||
}
|
||||
}
|
||||
|
||||
// Actors (enemies only if visible)
|
||||
// Actors
|
||||
const activeEnemyIds = new Set<EntityId>();
|
||||
|
||||
for (const a of this.world.actors.values()) {
|
||||
const i = idx(this.world, a.pos.x, a.pos.y);
|
||||
const isVis = this.visible[i] === 1;
|
||||
@@ -282,14 +254,11 @@ export class DungeonRenderer {
|
||||
|
||||
activeEnemyIds.add(a.id);
|
||||
let sprite = this.enemySprites.get(a.id);
|
||||
|
||||
const textureKey = a.type === "bat" ? "bat" : "rat";
|
||||
|
||||
if (!sprite) {
|
||||
sprite = this.scene.add.sprite(0, 0, textureKey, 0);
|
||||
sprite.setDepth(99);
|
||||
const scale = TILE_SIZE / 15;
|
||||
sprite.setScale(scale);
|
||||
sprite.play(`${textureKey}-idle`);
|
||||
this.enemySprites.set(a.id, sprite);
|
||||
}
|
||||
@@ -298,11 +267,9 @@ export class DungeonRenderer {
|
||||
sprite.setVisible(true);
|
||||
}
|
||||
|
||||
// Hide/Cleanup inactive/non-visible enemy sprites
|
||||
for (const [id, sprite] of this.enemySprites.entries()) {
|
||||
if (!activeEnemyIds.has(id)) {
|
||||
sprite.setVisible(false);
|
||||
// We could also destroy if they are dead, but hide is safer for now
|
||||
if (!this.world.actors.has(id)) {
|
||||
sprite.destroy();
|
||||
this.enemySprites.delete(id);
|
||||
@@ -310,16 +277,13 @@ export class DungeonRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
// Render minimap
|
||||
this.renderMinimap();
|
||||
}
|
||||
|
||||
private renderMinimap() {
|
||||
this.minimapGfx.clear();
|
||||
|
||||
if (!this.world) return;
|
||||
|
||||
// Calculate scale to fit map within panel
|
||||
const padding = GAME_CONFIG.ui.minimapPadding;
|
||||
const availableWidth = GAME_CONFIG.ui.minimapPanelWidth - padding * 2;
|
||||
const availableHeight = GAME_CONFIG.ui.minimapPanelHeight - padding * 2;
|
||||
@@ -328,73 +292,44 @@ export class DungeonRenderer {
|
||||
const scaleY = availableHeight / this.world.height;
|
||||
const tileSize = Math.floor(Math.min(scaleX, scaleY));
|
||||
|
||||
// Center the map within the panel
|
||||
const mapPixelWidth = this.world.width * tileSize;
|
||||
const mapPixelHeight = this.world.height * tileSize;
|
||||
const offsetX = -mapPixelWidth / 2;
|
||||
const offsetY = -mapPixelHeight / 2;
|
||||
|
||||
// Draw only seen tiles
|
||||
for (let y = 0; y < this.world.height; y++) {
|
||||
for (let x = 0; x < this.world.width; x++) {
|
||||
const i = idx(this.world, x, y);
|
||||
const isSeen = this.seen[i] === 1;
|
||||
|
||||
if (!isSeen) continue;
|
||||
if (this.seen[i] !== 1) continue;
|
||||
|
||||
const wall = isWall(this.world, x, y);
|
||||
const color = wall ? 0x666666 : 0x333333;
|
||||
|
||||
this.minimapGfx.fillStyle(color, 1);
|
||||
this.minimapGfx.fillRect(
|
||||
offsetX + x * tileSize,
|
||||
offsetY + y * tileSize,
|
||||
tileSize,
|
||||
tileSize
|
||||
);
|
||||
this.minimapGfx.fillRect(offsetX + x * tileSize, offsetY + y * tileSize, tileSize, tileSize);
|
||||
}
|
||||
}
|
||||
|
||||
// Draw exit if seen
|
||||
const ex = this.world.exit.x;
|
||||
const ey = this.world.exit.y;
|
||||
const exitIdx = idx(this.world, ex, ey);
|
||||
if (this.seen[exitIdx] === 1) {
|
||||
if (this.seen[idx(this.world, ex, ey)] === 1) {
|
||||
this.minimapGfx.fillStyle(0xffd166, 1);
|
||||
this.minimapGfx.fillRect(
|
||||
offsetX + ex * tileSize,
|
||||
offsetY + ey * tileSize,
|
||||
tileSize,
|
||||
tileSize
|
||||
);
|
||||
this.minimapGfx.fillRect(offsetX + ex * tileSize, offsetY + ey * tileSize, tileSize, tileSize);
|
||||
}
|
||||
|
||||
// Draw player
|
||||
const player = [...this.world.actors.values()].find(a => a.isPlayer);
|
||||
if (player) {
|
||||
this.minimapGfx.fillStyle(0x66ff66, 1);
|
||||
this.minimapGfx.fillRect(
|
||||
offsetX + player.pos.x * tileSize,
|
||||
offsetY + player.pos.y * tileSize,
|
||||
tileSize,
|
||||
tileSize
|
||||
);
|
||||
this.minimapGfx.fillRect(offsetX + player.pos.x * tileSize, offsetY + player.pos.y * tileSize, tileSize, tileSize);
|
||||
}
|
||||
|
||||
// Draw visible enemies
|
||||
for (const a of this.world.actors.values()) {
|
||||
if (a.isPlayer) continue;
|
||||
const i = idx(this.world, a.pos.x, a.pos.y);
|
||||
const isVis = this.visible[i] === 1;
|
||||
if (!isVis) continue;
|
||||
|
||||
this.minimapGfx.fillStyle(0xff6666, 1);
|
||||
this.minimapGfx.fillRect(
|
||||
offsetX + a.pos.x * tileSize,
|
||||
offsetY + a.pos.y * tileSize,
|
||||
tileSize,
|
||||
tileSize
|
||||
);
|
||||
if (this.visible[i] === 1) {
|
||||
this.minimapGfx.fillStyle(0xff6666, 1);
|
||||
this.minimapGfx.fillRect(offsetX + a.pos.x * tileSize, offsetY + a.pos.y * tileSize, tileSize, tileSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -429,7 +364,6 @@ export class DungeonRenderer {
|
||||
0
|
||||
);
|
||||
corpse.setDepth(50);
|
||||
corpse.setScale(TILE_SIZE / 15);
|
||||
corpse.play(`${textureKey}-die`);
|
||||
this.corpseSprites.push(corpse);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user