// renderer.js : projection iso (world -> screen et inverse), camera, projectiles + AOE // formule : sx = (wx - wy) * tw/2 / sy = (wx + wy) * th/2 import { Graphics } from 'pixi.js'; import { ARENA_WIDTH, ARENA_HEIGHT, TILE_WIDTH, TILE_HEIGHT, PROJECTILE_HIT_RADIUS, SOULGATE_HITBOX_RADIUS, SOULGATE_X, SOULGATE_Y, } from './constants.js'; let _debugHitboxes = false; export function setDebugHitboxes(v) { _debugHitboxes = v; } // Re-exports : ces fonctions viennent d'autres fichiers mais sont exposées ici // → main.js peut tout importer depuis renderer.js en un seul endroit export { drawStaticArena } from './renderArena.js'; // dessine l'arène statique (sol, Soulgate, etc.) export { updateEnemies } from './renderEnemies.js'; // met à jour les sprites ennemis export { updatePlayers } from './renderPlayers.js'; // met à jour les sprites joueurs // Zoom / Layout // Nombre d'unités monde visibles en diagonale iso — détermine le niveau de zoom // Plus c'est grand → plus on voit loin → plus les objets sont petits const CAMERA_VIEWPORT_DIAG = 22; // tw / th = pixels par "unité monde" dans les axes X et Y isométriques // recalcules au resize let tw, th; export function getTh() { return th; } export function getTw() { return tw; } export function updateIsoLayout(screenW, screenH) { const bW = CAMERA_VIEWPORT_DIAG * TILE_WIDTH / 2; const bH = CAMERA_VIEWPORT_DIAG * TILE_HEIGHT / 2; tw = TILE_WIDTH * (screenW / bW); th = TILE_HEIGHT * (screenH / bH); } // projection iso : (wx - wy) * tw/2 / (wx + wy) * th/2 export function iso(wx, wy) { return { x: (wx - wy) * tw / 2, y: (wx + wy) * th / 2, }; } export function screenToWorld(screenX, screenY, containerX, containerY) { // inverse de iso, sert au clic souris const sx = screenX - containerX; const sy = screenY - containerY; return { wx: sx / tw + sy / th, wy: sy / th - sx / tw, }; } export function updateCamera(worldContainer, screenW, screenH, wx, wy) { const HW = ARENA_WIDTH / 2; const HH = ARENA_HEIGHT / 2; // clamp pour pas voir du vide au bord const camX = Math.max(-HW, Math.min(HW, wx)); const camY = Math.max(-HH, Math.min(HH, wy)); const p = iso(camX, camY); worldContainer.x = screenW / 2 - p.x; worldContainer.y = screenH / 2 - p.y; } export function updateProjectiles(layer, pool, projectiles) { const ids = new Set(projectiles.map(p => p.id)); for (const [id, g] of Object.entries(pool)) { if (!ids.has(id)) { layer.removeChild(g); delete pool[id]; } } for (const p of projectiles) { if (!pool[p.id]) { pool[p.id] = _createProjectileGraphic(); layer.addChild(pool[p.id]); } const pos = iso(p.x, p.y); pool[p.id].x = pos.x; pool[p.id].y = pos.y; // Debug : hitbox du projectile if (_debugHitboxes && pool[p.id]._hboxG) { const hg = pool[p.id]._hboxG; hg.clear(); const rx = PROJECTILE_HIT_RADIUS * tw / 2; const ry = PROJECTILE_HIT_RADIUS * th / 2; hg.ellipse(0, 0, rx, ry).stroke({ color: 0xffff00, width: 1, alpha: 0.8 }); } else if (!_debugHitboxes && pool[p.id]._hboxG) { pool[p.id]._hboxG.clear(); } } } function _createProjectileGraphic() { const g = new Graphics(); const r = Math.max(3, th * 0.08); g.circle(0, 0, r).fill({ color: 0xffee44 }); g.circle(0, 0, r).stroke({ color: 0xffffff, width: 1, alpha: 0.3 }); // sous-graphics pour la hitbox debug const hboxG = new Graphics(); g.addChild(hboxG); g._hboxG = hboxG; return g; } // AOE const _AOE_STYLE = { slam: { fill: 0xff6600, stroke: 0xffaa44 }, storm: { fill: 0x8800ff, stroke: 0xcc88ff }, void: { fill: 0x00ccff, stroke: 0x88eeff }, pulse: { fill: 0xffd700, stroke: 0xfff0a0 }, }; export function updateAoeZones(layer, pool, zones) { // Supprimer les zones disparues const ids = new Set(zones.map(z => z.id)); for (const [id, g] of Object.entries(pool)) { if (!ids.has(id)) { layer.removeChild(g); delete pool[id]; } } for (const z of zones) { if (!pool[z.id]) { pool[z.id] = new Graphics(); layer.addChild(pool[z.id]); } const g = pool[z.id]; const pos = iso(z.x, z.y); g.x = pos.x; g.y = pos.y; const progress = z.ticks / z.max_ticks; // 1.0 → 0.0 au fil du temps const pal = _AOE_STYLE[z.type] ?? _AOE_STYLE.slam; const rx = z.radius * tw / 2; const ry = z.radius * th / 2; g.clear(); g.ellipse(0, 0, rx, ry).fill({ color: pal.fill, alpha: 0.18 * progress }); g.ellipse(0, 0, rx, ry).stroke({ color: pal.stroke, width: 2, alpha: 0.85 * progress }); } } // hitbox debug Soulgate (plus utilise mais on garde le graphics au cas ou) let _sgHboxG = null; export function initSoulgateHitbox(layer) { if (_sgHboxG) { layer.removeChild(_sgHboxG); } _sgHboxG = new Graphics(); layer.addChild(_sgHboxG); } export function updateSoulgateHitbox() { if (!_sgHboxG) return; _sgHboxG.clear(); if (_debugHitboxes) { const pos = iso(SOULGATE_X, SOULGATE_Y); _sgHboxG.x = pos.x; _sgHboxG.y = pos.y; const rx = SOULGATE_HITBOX_RADIUS * tw / 2; const ry = SOULGATE_HITBOX_RADIUS * th / 2; _sgHboxG.ellipse(0, 0, rx, ry).stroke({ color: 0x00ff88, width: 2, alpha: 0.9 }); } }