Add levelling up mechanics through experience gained via killing enemies
This commit is contained in:
@@ -12,7 +12,9 @@ export class DungeonRenderer {
|
||||
|
||||
private playerSprite?: Phaser.GameObjects.Sprite;
|
||||
private enemySprites: Map<EntityId, Phaser.GameObjects.Sprite> = new Map();
|
||||
private orbSprites: Map<EntityId, Phaser.GameObjects.Arc> = new Map();
|
||||
private corpseSprites: Phaser.GameObjects.Sprite[] = [];
|
||||
|
||||
|
||||
// FOV
|
||||
private fov!: any;
|
||||
@@ -52,7 +54,8 @@ export class DungeonRenderer {
|
||||
this.minimapContainer.setVisible(false);
|
||||
}
|
||||
|
||||
initializeLevel(world: World) {
|
||||
initializeFloor(world: World) {
|
||||
|
||||
this.world = world;
|
||||
this.seen = new Uint8Array(this.world.width * this.world.height);
|
||||
this.visible = new Uint8Array(this.world.width * this.world.height);
|
||||
@@ -277,6 +280,41 @@ export class DungeonRenderer {
|
||||
}
|
||||
}
|
||||
|
||||
// Orbs
|
||||
const activeOrbIds = new Set<EntityId>();
|
||||
for (const a of this.world.actors.values()) {
|
||||
if (a.type !== "exp_orb") continue;
|
||||
|
||||
const i = idx(this.world, a.pos.x, a.pos.y);
|
||||
// PD usually shows items only when visible or seen. Let's do visible.
|
||||
const isVis = this.visible[i] === 1;
|
||||
|
||||
|
||||
if (!isVis) continue;
|
||||
|
||||
activeOrbIds.add(a.id);
|
||||
let orb = this.orbSprites.get(a.id);
|
||||
if (!orb) {
|
||||
orb = this.scene.add.circle(0, 0, 4, GAME_CONFIG.rendering.expOrbColor);
|
||||
orb.setStrokeStyle(1, 0xffffff, 0.5);
|
||||
orb.setDepth(45);
|
||||
this.orbSprites.set(a.id, orb);
|
||||
}
|
||||
orb.setPosition(a.pos.x * TILE_SIZE + TILE_SIZE / 2, a.pos.y * TILE_SIZE + TILE_SIZE / 2);
|
||||
orb.setVisible(true);
|
||||
}
|
||||
|
||||
for (const [id, orb] of this.orbSprites.entries()) {
|
||||
if (!activeOrbIds.has(id)) {
|
||||
orb.setVisible(false);
|
||||
if (!this.world.actors.has(id)) {
|
||||
orb.destroy();
|
||||
this.orbSprites.delete(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
this.renderMinimap();
|
||||
}
|
||||
|
||||
@@ -355,8 +393,10 @@ export class DungeonRenderer {
|
||||
});
|
||||
}
|
||||
|
||||
spawnCorpse(x: number, y: number, type: "player" | "rat" | "bat") {
|
||||
spawnCorpse(x: number, y: number, type: "player" | "rat" | "bat" | "exp_orb") {
|
||||
if (type === "exp_orb") return;
|
||||
const textureKey = type === "player" ? "warrior" : type;
|
||||
|
||||
const corpse = this.scene.add.sprite(
|
||||
x * TILE_SIZE + TILE_SIZE / 2,
|
||||
y * TILE_SIZE + TILE_SIZE / 2,
|
||||
@@ -389,4 +429,54 @@ export class DungeonRenderer {
|
||||
onComplete: () => text.destroy()
|
||||
});
|
||||
}
|
||||
|
||||
spawnOrb(_orbId: EntityId, _x: number, _y: number) {
|
||||
// Just to trigger a render update if needed, but render() handles it
|
||||
}
|
||||
|
||||
collectOrb(_actorId: EntityId, amount: number, x: number, y: number) {
|
||||
|
||||
const screenX = x * TILE_SIZE + TILE_SIZE / 2;
|
||||
const screenY = y * TILE_SIZE;
|
||||
|
||||
const text = this.scene.add.text(screenX, screenY, `+${amount} EXP`, {
|
||||
fontSize: "14px",
|
||||
color: "#" + GAME_CONFIG.rendering.expTextColor.toString(16),
|
||||
stroke: "#000",
|
||||
strokeThickness: 2,
|
||||
fontStyle: "bold"
|
||||
}).setOrigin(0.5, 1).setDepth(200);
|
||||
|
||||
this.scene.tweens.add({
|
||||
targets: text,
|
||||
y: screenY - 32,
|
||||
alpha: 0,
|
||||
duration: 1000,
|
||||
ease: "Power1",
|
||||
onComplete: () => text.destroy()
|
||||
});
|
||||
}
|
||||
|
||||
showLevelUp(x: number, y: number) {
|
||||
const screenX = x * TILE_SIZE + TILE_SIZE / 2;
|
||||
const screenY = y * TILE_SIZE;
|
||||
|
||||
const text = this.scene.add.text(screenX, screenY - 16, "+1 LVL", {
|
||||
fontSize: "20px",
|
||||
color: "#" + GAME_CONFIG.rendering.levelUpColor.toString(16),
|
||||
stroke: "#000",
|
||||
strokeThickness: 3,
|
||||
fontStyle: "bold"
|
||||
}).setOrigin(0.5, 1).setDepth(210);
|
||||
|
||||
this.scene.tweens.add({
|
||||
targets: text,
|
||||
y: screenY - 60,
|
||||
alpha: 0,
|
||||
duration: 1500,
|
||||
ease: "Cubic.out",
|
||||
onComplete: () => text.destroy()
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -94,6 +94,17 @@ describe('DungeonRenderer', () => {
|
||||
exists: vi.fn().mockReturnValue(true),
|
||||
generateFrameNumbers: vi.fn(),
|
||||
},
|
||||
make: {
|
||||
tilemap: vi.fn().mockReturnValue({
|
||||
addTilesetImage: vi.fn().mockReturnValue({}),
|
||||
createLayer: vi.fn().mockReturnValue({
|
||||
setDepth: vi.fn(),
|
||||
forEachTile: vi.fn(),
|
||||
}),
|
||||
destroy: vi.fn(),
|
||||
}),
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
mockWorld = {
|
||||
@@ -107,8 +118,9 @@ describe('DungeonRenderer', () => {
|
||||
renderer = new DungeonRenderer(mockScene);
|
||||
});
|
||||
|
||||
it('should track and clear corpse sprites on level initialization', () => {
|
||||
renderer.initializeLevel(mockWorld);
|
||||
it('should track and clear corpse sprites on floor initialization', () => {
|
||||
renderer.initializeFloor(mockWorld);
|
||||
|
||||
|
||||
// Spawn a couple of corpses
|
||||
renderer.spawnCorpse(1, 1, 'rat');
|
||||
@@ -120,8 +132,9 @@ describe('DungeonRenderer', () => {
|
||||
|
||||
expect(mockScene.add.sprite).toHaveBeenCalledTimes(3);
|
||||
|
||||
// Initialize level again (changing level)
|
||||
renderer.initializeLevel(mockWorld);
|
||||
// Initialize floor again (changing level)
|
||||
renderer.initializeFloor(mockWorld);
|
||||
|
||||
|
||||
// Verify destroy was called on both corpse sprites
|
||||
expect(corpse1.destroy).toHaveBeenCalledTimes(1);
|
||||
|
||||
Reference in New Issue
Block a user