feat: Implement 2D tactical view and refine LOS with corner detection

This commit is contained in:
2026-01-06 20:50:46 +01:00
parent c0a9299dc5
commit 61c7cc3313
5 changed files with 255 additions and 8 deletions

View File

@@ -47,6 +47,10 @@ export class GameRenderer {
this.rangedGroup = new THREE.Group();
this.scene.add(this.rangedGroup);
this.tokensGroup = new THREE.Group();
this.scene.add(this.tokensGroup);
this.tokens = new Map();
this.entities = new Map();
}
@@ -333,6 +337,14 @@ export class GameRenderer {
// Prevent snapping if animation is active
if (mesh.userData.isMoving || mesh.userData.pathQueue.length > 0) return;
mesh.position.set(entity.x, 1.56 / 2, -entity.y);
// Sync Token
if (this.tokens) {
const token = this.tokens.get(entity.id);
if (token) {
token.position.set(entity.x, 0.05, -entity.y);
}
}
}
}
@@ -359,6 +371,15 @@ export class GameRenderer {
mesh.position.x = THREE.MathUtils.lerp(data.startPos.x, data.targetPos.x, progress);
mesh.position.z = THREE.MathUtils.lerp(data.startPos.z, data.targetPos.z, progress);
// Sync Token
if (this.tokens) {
const token = this.tokens.get(id);
if (token) {
token.position.x = mesh.position.x;
token.position.z = mesh.position.z;
}
}
// Hop (Botecito)
const jumpHeight = 0.5;
const baseHeight = 1.56 / 2;
@@ -1130,4 +1151,69 @@ export class GameRenderer {
// Walls are implicit (Line just turns red and stops/passes through)
}
}
showTokens(heroes, monsters) {
this.hideTokens(); // Clear existing (makes invisible)
if (this.tokensGroup) this.tokensGroup.visible = true; // Now force visible
const createToken = (entity, type, subType) => {
const geometry = new THREE.CircleGeometry(0.35, 32);
const material = new THREE.MeshBasicMaterial({
color: (type === 'hero') ? 0x00BFFF : 0xDC143C, // Fallback color
side: THREE.DoubleSide,
transparent: true,
opacity: 1.0
});
const token = new THREE.Mesh(geometry, material);
token.rotation.x = -Math.PI / 2;
const mesh3D = this.entities.get(entity.id);
if (mesh3D) {
token.position.set(mesh3D.position.x, 0.05, mesh3D.position.z);
} else {
token.position.set(entity.x, 0.05, -entity.y);
}
this.tokensGroup.add(token);
this.tokens.set(entity.id, token);
// White Border Ring
const borderGeo = new THREE.RingGeometry(0.35, 0.38, 32);
const borderMat = new THREE.MeshBasicMaterial({ color: 0xFFFFFF, side: THREE.DoubleSide });
const border = new THREE.Mesh(borderGeo, borderMat);
border.position.z = 0.001;
token.add(border);
// Load Image
let path = '';
// Ensure filename is safe (though keys usually are)
const filename = subType;
if (type === 'hero') {
path = `/assets/images/dungeon1/tokens/heroes/${filename}.png`;
} else {
path = `/assets/images/dungeon1/tokens/enemies/${filename}.png`;
}
this.getTexture(path, (texture) => {
token.material.map = texture;
token.material.color.setHex(0xFFFFFF); // Reset to white to show texture
token.material.needsUpdate = true;
}, undefined, (err) => {
console.warn(`[GameRenderer] Token texture missing: ${path}`);
});
};
if (heroes) heroes.forEach(h => createToken(h, 'hero', h.key));
if (monsters) monsters.forEach(m => {
if (!m.isDead) createToken(m, 'monster', m.key);
});
}
hideTokens() {
if (this.tokensGroup) {
this.tokensGroup.clear();
this.tokensGroup.visible = false;
}
if (this.tokens) this.tokens.clear();
}
}