From 42cd77998dffd3778e19ce4bb44a01432680085d Mon Sep 17 00:00:00 2001 From: Peter Stockings Date: Sun, 4 Jan 2026 16:46:49 +1100 Subject: [PATCH] Use wall + floor assets from Pixel dungeon --- public/assets/tiles0.png | Bin 0 -> 16574 bytes src/core/config/GameConfig.ts | 11 +- src/core/types.ts | 2 +- src/engine/world/generator.ts | 87 +++++++++++++-- src/engine/world/world-logic.ts | 4 +- src/rendering/DungeonRenderer.ts | 178 ++++++++++--------------------- src/scenes/GameScene.ts | 1 + 7 files changed, 152 insertions(+), 131 deletions(-) create mode 100644 public/assets/tiles0.png diff --git a/public/assets/tiles0.png b/public/assets/tiles0.png new file mode 100644 index 0000000000000000000000000000000000000000..cc42fdfddc4d8386917f7a468a76c1a12f10b880 GIT binary patch literal 16574 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 literal 0 HcmV?d00001 diff --git a/src/core/config/GameConfig.ts b/src/core/config/GameConfig.ts index 700f41f..23621e7 100644 --- a/src/core/config/GameConfig.ts +++ b/src/core/config/GameConfig.ts @@ -30,7 +30,7 @@ export const GAME_CONFIG = { }, rendering: { - tileSize: 24, + tileSize: 16, cameraZoom: 2, wallColor: 0x2b2b2b, floorColor: 0x161616, @@ -44,6 +44,15 @@ export const GAME_CONFIG = { visibleMaxAlpha: 1.0, visibleStrengthFactor: 0.65 }, + + terrain: { + empty: 1, + wall: 4, + water: 63, + emptyDeco: 24, + wallDeco: 12, + exit: 8 + }, ui: { minimapPanelWidth: 340, diff --git a/src/core/types.ts b/src/core/types.ts index c3bf6ac..6739e2f 100644 --- a/src/core/types.ts +++ b/src/core/types.ts @@ -2,7 +2,7 @@ export type EntityId = number; export type Vec2 = { x: number; y: number }; -export type Tile = 0 | 1; // 0 = floor, 1 = wall +export type Tile = number; export type Action = | { type: "move"; dx: number; dy: number } diff --git a/src/engine/world/generator.ts b/src/engine/world/generator.ts index 6e5b92b..d3ecf54 100644 --- a/src/engine/world/generator.ts +++ b/src/engine/world/generator.ts @@ -19,7 +19,7 @@ interface Room { export function generateWorld(level: number, runState: RunState): { world: World; playerId: EntityId } { const width = GAME_CONFIG.map.width; const height = GAME_CONFIG.map.height; - const tiles: Tile[] = new Array(width * height).fill(1); // Start with all walls + const tiles: Tile[] = new Array(width * height).fill(GAME_CONFIG.terrain.wall); // Start with all walls const random = seededRandom(level * 12345); @@ -52,6 +52,8 @@ export function generateWorld(level: number, runState: RunState): { world: World placeEnemies(level, rooms, actors, random); + decorate(width, height, tiles, random, exit); + return { world: { width, height, tiles, actors, exit }, playerId }; } @@ -99,7 +101,7 @@ function doesOverlap(newRoom: Room, rooms: Room[]): boolean { function carveRoom(room: Room, tiles: Tile[], world: any): void { for (let x = room.x; x < room.x + room.width; x++) { for (let y = room.y; y < room.y + room.height; y++) { - tiles[idx(world, x, y)] = 0; + tiles[idx(world, x, y)] = GAME_CONFIG.terrain.empty; } } } @@ -113,22 +115,95 @@ function carveCorridor(room1: Room, room2: Room, tiles: Tile[], world: any, rand if (random() < 0.5) { // Horizontal then vertical for (let x = Math.min(x1, x2); x <= Math.max(x1, x2); x++) { - tiles[idx(world, x, y1)] = 0; + tiles[idx(world, x, y1)] = GAME_CONFIG.terrain.empty; } for (let y = Math.min(y1, y2); y <= Math.max(y1, y2); y++) { - tiles[idx(world, x2, y)] = 0; + tiles[idx(world, x2, y)] = GAME_CONFIG.terrain.empty; } } else { // Vertical then horizontal for (let y = Math.min(y1, y2); y <= Math.max(y1, y2); y++) { - tiles[idx(world, x1, y)] = 0; + tiles[idx(world, x1, y)] = GAME_CONFIG.terrain.empty; } for (let x = Math.min(x1, x2); x <= Math.max(x1, x2); x++) { - tiles[idx(world, x, y2)] = 0; + tiles[idx(world, x, y2)] = GAME_CONFIG.terrain.empty; } } } +function decorate(width: number, height: number, tiles: Tile[], random: () => number, exit: Vec2): void { + const world = { width, height }; + + // Set exit tile + tiles[idx(world as any, exit.x, exit.y)] = GAME_CONFIG.terrain.exit; + + // Add water patches (similar to PD Sewers) + const waterMask = generatePatch(width, height, 0.45, 5, random); + for (let i = 0; i < tiles.length; i++) { + if (tiles[i] === GAME_CONFIG.terrain.empty && waterMask[i]) { + tiles[i] = GAME_CONFIG.terrain.water; + } + } + + // Wall decorations + for (let y = 0; y < height - 1; y++) { + for (let x = 0; x < width; x++) { + const i = idx(world as any, x, y); + const nextY = idx(world as any, x, y + 1); + + if (tiles[i] === GAME_CONFIG.terrain.wall && + tiles[nextY] === GAME_CONFIG.terrain.water && + random() < 0.25) { + tiles[i] = GAME_CONFIG.terrain.wallDeco; + } + } + } + + // Floor decorations (moss) + for (let y = 1; y < height - 1; y++) { + for (let x = 1; x < width - 1; x++) { + const i = idx(world as any, x, y); + if (tiles[i] === GAME_CONFIG.terrain.empty) { + let wallCount = 0; + if (tiles[idx(world as any, x + 1, y)] === GAME_CONFIG.terrain.wall) wallCount++; + if (tiles[idx(world as any, x - 1, y)] === GAME_CONFIG.terrain.wall) wallCount++; + if (tiles[idx(world as any, x, y + 1)] === GAME_CONFIG.terrain.wall) wallCount++; + if (tiles[idx(world as any, x, y - 1)] === GAME_CONFIG.terrain.wall) wallCount++; + + if (random() * 16 < wallCount * wallCount) { + tiles[i] = GAME_CONFIG.terrain.emptyDeco; + } + } + } + } +} + +/** + * Simple cellular automata for generating patches of terrain + */ +function generatePatch(width: number, height: number, fillChance: number, iterations: number, random: () => number): boolean[] { + let map = new Array(width * height).fill(false).map(() => random() < fillChance); + + for (let step = 0; step < iterations; step++) { + const nextMap = new Array(width * height).fill(false); + for (let y = 1; y < height - 1; y++) { + for (let x = 1; x < width - 1; x++) { + let neighbors = 0; + for (let dy = -1; dy <= 1; dy++) { + for (let dx = -1; dx <= 1; dx++) { + if (map[(y + dy) * width + (x + dx)]) neighbors++; + } + } + if (neighbors > 4) nextMap[y * width + x] = true; + else if (neighbors < 4) nextMap[y * width + x] = false; + else nextMap[y * width + x] = map[y * width + x]; + } + } + map = nextMap; + } + return map; +} + function placeEnemies(level: number, rooms: Room[], actors: Map, random: () => number): void { let enemyId = 2; const numEnemies = GAME_CONFIG.enemy.baseCount + level * GAME_CONFIG.enemy.baseCountPerLevel + Math.floor(random() * GAME_CONFIG.enemy.randomBonus); diff --git a/src/engine/world/world-logic.ts b/src/engine/world/world-logic.ts index 47fcd21..b1139c2 100644 --- a/src/engine/world/world-logic.ts +++ b/src/engine/world/world-logic.ts @@ -1,4 +1,5 @@ import type { World, EntityId } from "../../core/types"; +import { GAME_CONFIG } from "../../core/config/GameConfig"; export function inBounds(w: World, x: number, y: number): boolean { return x >= 0 && y >= 0 && x < w.width && y < w.height; @@ -9,7 +10,8 @@ export function idx(w: World, x: number, y: number): number { } export function isWall(w: World, x: number, y: number): boolean { - return w.tiles[idx(w, x, y)] === 1; + const tile = w.tiles[idx(w, x, y)]; + return tile === GAME_CONFIG.terrain.wall || tile === GAME_CONFIG.terrain.wallDeco; } export function isBlocked(w: World, x: number, y: number): boolean { diff --git a/src/rendering/DungeonRenderer.ts b/src/rendering/DungeonRenderer.ts index 7b1f91b..f71d369 100644 --- a/src/rendering/DungeonRenderer.ts +++ b/src/rendering/DungeonRenderer.ts @@ -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 = 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(); - 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); } diff --git a/src/scenes/GameScene.ts b/src/scenes/GameScene.ts index b2dd863..fccbf81 100644 --- a/src/scenes/GameScene.ts +++ b/src/scenes/GameScene.ts @@ -42,6 +42,7 @@ export class GameScene extends Phaser.Scene { this.load.spritesheet("warrior", "warrior.png", { frameWidth: 12, frameHeight: 15 }); this.load.spritesheet("rat", "rat.png", { frameWidth: 16, frameHeight: 15 }); this.load.spritesheet("bat", "bat.png", { frameWidth: 15, frameHeight: 15 }); + this.load.spritesheet("tiles0", "assets/tiles0.png", { frameWidth: 16, frameHeight: 16 }); } create() {