Versión inicial: Motor funcional con visualización de texturas corregida
This commit is contained in:
@@ -39,12 +39,14 @@ En esta sesión se ha establecido la base completa del motor de juego para **War
|
|||||||
* Zoom y Panoramización.
|
* Zoom y Panoramización.
|
||||||
* **Assets**: Integración de texturas (`.png`) para baldosas, movidas a la carpeta `public/assets` para su correcta carga en el navegador.
|
* **Assets**: Integración de texturas (`.png`) para baldosas, movidas a la carpeta `public/assets` para su correcta carga en el navegador.
|
||||||
|
|
||||||
|
### Estado Actual
|
||||||
|
### Estado Actual
|
||||||
### Estado Actual
|
### Estado Actual
|
||||||
* El generador crea mazmorras lógicas válidas siguiendo las reglas.
|
* El generador crea mazmorras lógicas válidas siguiendo las reglas.
|
||||||
* El visualizador pinta la estructura en 3D.
|
* El visualizador pinta la estructura en 3D.
|
||||||
* Se han añadido las texturas, aunque persisten problemas de caché/visualización en el navegador del usuario que requieren un reinicio limpio.
|
* **Texturas operativas**: Se ha corregido un bug crítico en la rotación (NaN) que impedía la visualización. Ahora las texturas se cargan y posicionan, aunque persisten problemas de alineación visual en las juntas.
|
||||||
|
|
||||||
### Próximos Pasos
|
### Próximos Pasos
|
||||||
* Validar la alineación visual fina de las texturas (especialmente en uniones T y L).
|
* Corregir la alineación fina de las baldosas (especialmente T y L) para eliminar huecos visuales.
|
||||||
* Implementar la interfaz de usuario (UI) para mostrar cartas y estado del juego.
|
* Implementar la interfaz de usuario (UI) para mostrar cartas y estado del juego.
|
||||||
* Añadir modelos 3D para héroes y monstruos.
|
* Añadir modelos 3D para héroes y monstruos.
|
||||||
|
|||||||
@@ -56,20 +56,31 @@ export class GameRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getTexture(path) {
|
getTexture(path, onLoad) {
|
||||||
if (!this.textureCache.has(path)) {
|
if (!this.textureCache.has(path)) {
|
||||||
// NOTE: Using absolute paths from public dir requires leading slash if served from root
|
console.log(`[TextureLoader] Starting load for: ${path}`);
|
||||||
// But verify if we need to prepend anything else.
|
const tex = this.textureLoader.load(
|
||||||
// Assuming served at root /.
|
path,
|
||||||
const tex = this.textureLoader.load(path,
|
(texture) => {
|
||||||
(t) => console.log(`Texture loaded: ${path}`),
|
console.log(`[TextureLoader] ✓ Successfully loaded: ${path}`);
|
||||||
|
texture.needsUpdate = true;
|
||||||
|
if (onLoad) onLoad(texture);
|
||||||
|
},
|
||||||
undefined,
|
undefined,
|
||||||
(err) => console.error(`Texture failed: ${path}`, err)
|
(err) => {
|
||||||
|
console.error(`[TextureLoader] ✗ Failed to load: ${path}`, err);
|
||||||
|
}
|
||||||
);
|
);
|
||||||
tex.magFilter = THREE.NearestFilter;
|
tex.magFilter = THREE.NearestFilter;
|
||||||
tex.minFilter = THREE.NearestFilter;
|
tex.minFilter = THREE.NearestFilter;
|
||||||
tex.colorSpace = THREE.SRGBColorSpace;
|
tex.colorSpace = THREE.SRGBColorSpace;
|
||||||
this.textureCache.set(path, tex);
|
this.textureCache.set(path, tex);
|
||||||
|
} else {
|
||||||
|
// Already cached, call onLoad immediately if texture is ready
|
||||||
|
const cachedTex = this.textureCache.get(path);
|
||||||
|
if (onLoad && cachedTex.image) {
|
||||||
|
onLoad(cachedTex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return this.textureCache.get(path);
|
return this.textureCache.get(path);
|
||||||
}
|
}
|
||||||
@@ -79,78 +90,63 @@ export class GameRenderer {
|
|||||||
// tileDef: The definition object (has textures, dimensions)
|
// tileDef: The definition object (has textures, dimensions)
|
||||||
// tileInstance: The instance object (has x, y, rotation, id)
|
// tileInstance: The instance object (has x, y, rotation, id)
|
||||||
|
|
||||||
console.log(`Rendering Tile [${type}] with ${cells.length} cells.`);
|
console.log(`[GameRenderer] Rendering Tile [${type}] ID: ${tileDef?.id} at (${tileInstance?.x}, ${tileInstance?.y}) Rot: ${tileInstance?.rotation}`);
|
||||||
|
|
||||||
const isRoom = type === 'room' || type === 'room_objective' || type === 'room_dungeon';
|
// Draw Texture Plane (The Image) - WAIT FOR TEXTURE TO LOAD
|
||||||
|
|
||||||
// 1. Draw individual Cells (The Grill)
|
|
||||||
cells.forEach(cell => {
|
|
||||||
const geometry = new THREE.BoxGeometry(1, 0.5, 1);
|
|
||||||
const material = new THREE.MeshStandardMaterial({
|
|
||||||
color: isRoom ? 0x4444ff : 0xaaaaaa,
|
|
||||||
roughness: 0.8,
|
|
||||||
metalness: 0.1,
|
|
||||||
transparent: true,
|
|
||||||
opacity: 0.5
|
|
||||||
});
|
|
||||||
const mesh = new THREE.Mesh(geometry, material);
|
|
||||||
mesh.position.set(cell.x, 0, -cell.y);
|
|
||||||
|
|
||||||
const edges = new THREE.EdgesGeometry(geometry);
|
|
||||||
const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial({ color: 0x000000 }));
|
|
||||||
mesh.add(line);
|
|
||||||
|
|
||||||
this.scene.add(mesh);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 2. Draw Texture Plane (The Image)
|
|
||||||
if (tileDef && tileInstance && tileDef.textures && tileDef.textures.length > 0) {
|
if (tileDef && tileInstance && tileDef.textures && tileDef.textures.length > 0) {
|
||||||
|
|
||||||
const texturePath = tileDef.textures[0];
|
const texturePath = tileDef.textures[0];
|
||||||
console.log(`[GameRenderer] Loading texture ${texturePath} for tile`, tileDef.id);
|
|
||||||
const texture = this.getTexture(texturePath);
|
|
||||||
|
|
||||||
const w = tileDef.width;
|
// Load texture with callback
|
||||||
const l = tileDef.length;
|
this.getTexture(texturePath, (texture) => {
|
||||||
|
const w = tileDef.width;
|
||||||
|
const l = tileDef.length;
|
||||||
|
|
||||||
// Create Plane
|
// Create Plane
|
||||||
const geometry = new THREE.PlaneGeometry(w, l);
|
const geometry = new THREE.PlaneGeometry(w, l);
|
||||||
const material = new THREE.MeshBasicMaterial({
|
// Use MeshStandardMaterial for reaction to light if needed
|
||||||
map: texture,
|
const material = new THREE.MeshStandardMaterial({
|
||||||
transparent: true,
|
map: texture,
|
||||||
side: THREE.DoubleSide,
|
transparent: true,
|
||||||
alphaTest: 0.1
|
side: THREE.FrontSide, // Only visible from top
|
||||||
|
alphaTest: 0.1,
|
||||||
|
roughness: 0.8,
|
||||||
|
metalness: 0.2
|
||||||
|
});
|
||||||
|
const plane = new THREE.Mesh(geometry, material);
|
||||||
|
|
||||||
|
// Initial Rotation: Plane X-Y to X-Z
|
||||||
|
plane.rotation.x = -Math.PI / 2;
|
||||||
|
|
||||||
|
// Handle Rotation safely (Support both 0-3 and N-W)
|
||||||
|
const rotMap = { 'N': 0, '0': 0, 0: 0, 'E': 1, '1': 1, 1: 1, 'S': 2, '2': 2, 2: 2, 'W': 3, '3': 3, 3: 3 };
|
||||||
|
const r = rotMap[tileInstance.rotation] !== undefined ? rotMap[tileInstance.rotation] : 0;
|
||||||
|
|
||||||
|
// Apply Tile Rotation
|
||||||
|
plane.rotation.z = -r * (Math.PI / 2);
|
||||||
|
|
||||||
|
// Calculate Center Offset for Positioning
|
||||||
|
const midX = (tileDef.width - 1) / 2;
|
||||||
|
const midY = (tileDef.length - 1) / 2;
|
||||||
|
|
||||||
|
// Rotate the offset vector based on tile rotation
|
||||||
|
let dx, dy;
|
||||||
|
|
||||||
|
if (r === 0) { dx = midX; dy = midY; }
|
||||||
|
else if (r === 1) { dx = midY; dy = -midX; }
|
||||||
|
else if (r === 2) { dx = -midX; dy = -midY; }
|
||||||
|
else if (r === 3) { dx = -midY; dy = midX; }
|
||||||
|
|
||||||
|
const centerX = tileInstance.x + dx;
|
||||||
|
const centerY = tileInstance.y + dy;
|
||||||
|
|
||||||
|
// Set at almost 0 height to avoid z-fighting with grid helper, but effectively on floor
|
||||||
|
plane.position.set(centerX, 0.01, -centerY);
|
||||||
|
plane.receiveShadow = true;
|
||||||
|
|
||||||
|
this.scene.add(plane);
|
||||||
|
console.log(`[GameRenderer] ✓ Tile plane added at (${centerX}, 0.01, ${-centerY}) for ${tileDef.id}`);
|
||||||
});
|
});
|
||||||
const plane = new THREE.Mesh(geometry, material);
|
|
||||||
|
|
||||||
// Initial Rotation: Plane X-Y to X-Z
|
|
||||||
plane.rotation.x = -Math.PI / 2;
|
|
||||||
|
|
||||||
// Apply Tile Rotation (N=0, E=1, S=2, W=3 in Y axis)
|
|
||||||
// We rotate around 0,0 of the plane geometry
|
|
||||||
// Note: rotation.z is local Z, which after rotX(-90) is Global Y (Vertical)
|
|
||||||
plane.rotation.z = -tileInstance.rotation * (Math.PI / 2);
|
|
||||||
|
|
||||||
// Calculate Center Offset for Positioning
|
|
||||||
// Visual Center needs to be offset from Tile Origin (x,y)
|
|
||||||
const midX = (tileDef.width - 1) / 2;
|
|
||||||
const midY = (tileDef.length - 1) / 2;
|
|
||||||
|
|
||||||
// Rotate the offset vector based on tile rotation
|
|
||||||
let dx, dy;
|
|
||||||
const r = tileInstance.rotation;
|
|
||||||
|
|
||||||
if (r === 0) { dx = midX; dy = midY; }
|
|
||||||
else if (r === 1) { dx = midY; dy = -midX; }
|
|
||||||
else if (r === 2) { dx = -midX; dy = -midY; }
|
|
||||||
else if (r === 3) { dx = -midY; dy = midX; }
|
|
||||||
|
|
||||||
const centerX = tileInstance.x + dx;
|
|
||||||
const centerY = tileInstance.y + dy;
|
|
||||||
|
|
||||||
plane.position.set(centerX, 0.55, -centerY);
|
|
||||||
|
|
||||||
this.scene.add(plane);
|
|
||||||
} else {
|
} else {
|
||||||
console.warn(`[GameRenderer] details missing for texture render. def: ${!!tileDef}, inst: ${!!tileInstance}, tex: ${tileDef?.textures?.length}`);
|
console.warn(`[GameRenderer] details missing for texture render. def: ${!!tileDef}, inst: ${!!tileInstance}, tex: ${tileDef?.textures?.length}`);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user