185 lines
5.5 KiB
JavaScript
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 });
|
|
}
|
|
}
|