SOULGATE/client/js/renderer.js
2026-05-04 03:42:47 +02:00

185 lines
5.5 KiB
JavaScript

// 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 });
}
}