Hide sprites of corpses when in fog of war

This commit is contained in:
Peter Stockings
2026-01-27 15:56:32 +11:00
parent a15bb3675b
commit 7260781f38
4 changed files with 120 additions and 10 deletions

View File

@@ -360,6 +360,7 @@ export class DungeonRenderer {
}
this.minimapRenderer.render(this.world, seen, visible, this.entityAccessor);
this.fxRenderer.updateVisibility(seen, visible, this.world.width);
}
// FX Delegations

View File

@@ -5,7 +5,7 @@ import { GAME_CONFIG } from "../core/config/GameConfig";
export class FxRenderer {
private scene: Phaser.Scene;
private corpseSprites: Phaser.GameObjects.Sprite[] = [];
private corpseSprites: { sprite: Phaser.GameObjects.Sprite; x: number; y: number }[] = [];
constructor(scene: Phaser.Scene) {
this.scene = scene;
@@ -34,8 +34,8 @@ export class FxRenderer {
}
clearCorpses() {
for (const sprite of this.corpseSprites) {
sprite.destroy();
for (const entry of this.corpseSprites) {
entry.sprite.destroy();
}
this.corpseSprites = [];
}
@@ -142,7 +142,26 @@ export class FxRenderer {
);
corpse.setDepth(50);
corpse.play(`${textureKey}-die`);
this.corpseSprites.push(corpse);
this.corpseSprites.push({ sprite: corpse, x, y });
}
updateVisibility(seen: Uint8Array, visible: Uint8Array, worldWidth: number) {
for (const entry of this.corpseSprites) {
const idx = entry.y * worldWidth + entry.x;
const isSeen = seen[idx] === 1;
const isVisible = visible[idx] === 1;
entry.sprite.setVisible(isSeen);
if (isSeen) {
if (isVisible) {
entry.sprite.setAlpha(1);
entry.sprite.clearTint();
} else {
entry.sprite.setAlpha(0.4);
entry.sprite.setTint(0x888888);
}
}
}
}
showWait(x: number, y: number) {

View File

@@ -1,11 +1,7 @@
import '../../__tests__/test-setup';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { DungeonRenderer } from '../DungeonRenderer';
import type { World, EntityId } from '../../core/types';
import { ECSWorld } from '../../engine/ecs/World';
import { EntityAccessor } from '../../engine/EntityAccessor';
// Mock Phaser
// Mock Phaser - must be before imports that use it
vi.mock('phaser', () => {
const mockSprite = {
setDepth: vi.fn().mockReturnThis(),
@@ -65,6 +61,11 @@ vi.mock('phaser', () => {
};
});
import { DungeonRenderer } from '../DungeonRenderer';
import type { World, EntityId } from '../../core/types';
import { ECSWorld } from '../../engine/ecs/World';
import { EntityAccessor } from '../../engine/EntityAccessor';
describe('DungeonRenderer', () => {
let mockScene: any;
let renderer: DungeonRenderer;

View File

@@ -0,0 +1,89 @@
import '../../__tests__/test-setup';
import { describe, it, expect, vi, beforeEach } from 'vitest';
// Mock Phaser - must be before imports that use it
vi.mock('phaser', () => {
const mockSprite = {
setDepth: vi.fn().mockReturnThis(),
play: vi.fn().mockReturnThis(),
setVisible: vi.fn().mockReturnThis(),
setAlpha: vi.fn().mockReturnThis(),
setTint: vi.fn().mockReturnThis(),
clearTint: vi.fn().mockReturnThis(),
destroy: vi.fn(),
};
return {
default: {
GameObjects: {
Sprite: vi.fn(() => mockSprite),
},
Scene: vi.fn(),
},
};
});
import { FxRenderer } from '../FxRenderer';
describe('FxRenderer', () => {
let mockScene: any;
let fxRenderer: FxRenderer;
beforeEach(() => {
vi.clearAllMocks();
mockScene = {
add: {
sprite: vi.fn(() => ({
setDepth: vi.fn().mockReturnThis(),
play: vi.fn().mockReturnThis(),
setVisible: vi.fn().mockReturnThis(),
setAlpha: vi.fn().mockReturnThis(),
setTint: vi.fn().mockReturnThis(),
clearTint: vi.fn().mockReturnThis(),
destroy: vi.fn(),
})),
text: vi.fn(() => ({
setOrigin: vi.fn().mockReturnThis(),
setDepth: vi.fn().mockReturnThis(),
destroy: vi.fn(),
})),
},
tweens: {
add: vi.fn(),
},
};
fxRenderer = new FxRenderer(mockScene);
});
it('should update corpse visibility and appearance based on FOV', () => {
// Spawn a corpse at (5, 5)
fxRenderer.spawnCorpse(5, 5, 'rat');
const corpseSprite = mockScene.add.sprite.mock.results[0].value;
const seen = new Uint8Array(100).fill(0);
const visible = new Uint8Array(100).fill(0);
const worldWidth = 10;
const idx = 5 * worldWidth + 5;
// Case 1: Unseen tile
fxRenderer.updateVisibility(seen, visible, worldWidth);
expect(corpseSprite.setVisible).toHaveBeenCalledWith(false);
// Case 2: Seen but not currently visible (dimmed)
seen[idx] = 1;
fxRenderer.updateVisibility(seen, visible, worldWidth);
expect(corpseSprite.setVisible).toHaveBeenCalledWith(true);
expect(corpseSprite.setAlpha).toHaveBeenCalledWith(0.4);
expect(corpseSprite.setTint).toHaveBeenCalledWith(0x888888);
// Case 3: Currently visible (full brightness)
visible[idx] = 1;
fxRenderer.updateVisibility(seen, visible, worldWidth);
expect(corpseSprite.setVisible).toHaveBeenCalledWith(true);
expect(corpseSprite.setAlpha).toHaveBeenCalledWith(1);
expect(corpseSprite.clearTint).toHaveBeenCalled();
});
});