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

617 lines
24 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// renderEnemies.js : rendu des ennemis (sprites + barre de vie + ombre)
// pool de sprites reutilises (pas de recreate par frame)
import { Container, Graphics, Assets, Sprite } from 'pixi.js';
import { iso, getTh, getTw } from './renderer.js';
import { ENEMY_HITBOX_RADIUS } from './constants.js';
let _debugHitboxes = false;
export function setDebugHitboxes(v) { _debugHitboxes = v; }
// Assets Fracture
const _ft = {};
let _fractureReady = false;
export async function loadFractureAssets() {
if (_fractureReady) return;
const b = '../assets/sprites/fracture/';
function anim(prefix, dirs, n, folder) {
const out = [];
for (const d of dirs) {
for (let i = 0; i < n; i++) {
const pad = String(i).padStart(3, '0');
out.push([`${prefix}_${d}_${i}`, `${b}animations/${folder}/${d}/frame_${pad}.png`]);
}
}
return out;
}
const D4 = ['south', 'north', 'east', 'west'];
const entries = [
['f_south', b + 'rotations/south.png'],
['f_north', b + 'rotations/north.png'],
['f_east', b + 'rotations/east.png'],
['f_west', b + 'rotations/west.png'],
...anim('frun', D4, 4, 'running'),
];
for (const [key, path] of entries) {
const tex = await Assets.load(path);
tex.source.scaleMode = 'nearest';
_ft[key] = tex;
}
_fractureReady = true;
}
// Assets Rampant
const _rt = {};
let _rampantReady = false;
export async function loadRampantAssets() {
if (_rampantReady) return;
const b = '../assets/sprites/rampant/';
function anim(prefix, dirs, n, folder) {
const out = [];
for (const d of dirs) {
for (let i = 0; i < n; i++) {
const pad = String(i).padStart(3, '0');
out.push([`${prefix}_${d}_${i}`, `${b}animations/${folder}/${d}/frame_${pad}.png`]);
}
}
return out;
}
const D4 = ['south', 'north', 'east', 'west'];
const entries = [
['r_south', b + 'rotations/south.png'],
['r_north', b + 'rotations/north.png'],
['r_east', b + 'rotations/east.png'],
['r_west', b + 'rotations/west.png'],
...anim('rrun', D4, 6, 'running'),
];
for (const [key, path] of entries) {
const tex = await Assets.load(path);
tex.source.scaleMode = 'nearest';
_rt[key] = tex;
}
_rampantReady = true;
}
// Assets Colosse
const _ct = {};
let _colosseReady = false;
export async function loadColosseAssets() {
if (_colosseReady) return;
const b = '../assets/sprites/colosse/';
function anim(prefix, dirs, n, folder) {
const out = [];
for (const d of dirs) {
for (let i = 0; i < n; i++) {
const pad = String(i).padStart(3, '0');
out.push([`${prefix}_${d}_${i}`, `${b}animations/${folder}/${d}/frame_${pad}.png`]);
}
}
return out;
}
const D4 = ['south', 'north', 'east', 'west'];
const entries = [
['c_south', b + 'rotations/south.png'],
['c_north', b + 'rotations/north.png'],
['c_east', b + 'rotations/east.png'],
['c_west', b + 'rotations/west.png'],
...anim('crun', D4, 6, 'running'),
];
for (const [key, path] of entries) {
const tex = await Assets.load(path);
tex.source.scaleMode = 'nearest';
_ct[key] = tex;
}
_colosseReady = true;
}
// Assets Éclat
const _et = {};
let _eclatReady = false;
export async function loadEclatAssets() {
if (_eclatReady) return;
const b = '../assets/sprites/eclat/';
function anim(prefix, dirs, n, folder) {
const out = [];
for (const d of dirs) {
for (let i = 0; i < n; i++) {
const pad = String(i).padStart(3, '0');
out.push([`${prefix}_${d}_${i}`, `${b}animations/${folder}/${d}/frame_${pad}.png`]);
}
}
return out;
}
const D4 = ['south', 'north', 'east', 'west'];
const entries = [
['e_south', b + 'rotations/south.png'],
['e_north', b + 'rotations/north.png'],
['e_east', b + 'rotations/east.png'],
['e_west', b + 'rotations/west.png'],
...anim('erun', D4, 8, 'running'),
];
for (const [key, path] of entries) {
const tex = await Assets.load(path);
tex.source.scaleMode = 'nearest';
_et[key] = tex;
}
_eclatReady = true;
}
// Assets Vexaris
const _vt = {};
let _vexarisReady = false;
export async function loadVexarisAssets() {
if (_vexarisReady) return;
const b = '../assets/sprites/vexaris/';
function anim(prefix, dirs, n, folder) {
const out = [];
for (const d of dirs) {
for (let i = 0; i < n; i++) {
const pad = String(i).padStart(3, '0');
out.push([`${prefix}_${d}_${i}`, `${b}animations/${folder}/${d}/frame_${pad}.png`]);
}
}
return out;
}
const D4 = ['south', 'north', 'east', 'west'];
const entries = [
['v_south', b + 'rotations/south.png'],
['v_north', b + 'rotations/north.png'],
['v_east', b + 'rotations/east.png'],
['v_west', b + 'rotations/west.png'],
...anim('vrun', D4, 4, 'running'),
...anim('vatk', D4, 7, 'attack'),
...anim('vchg', D4, 7, 'charge'),
...anim('vbst', ['south'], 9, 'burst'),
];
for (const [key, path] of entries) {
const tex = await Assets.load(path);
tex.source.scaleMode = 'nearest';
_vt[key] = tex;
}
_vexarisReady = true;
}
// Animation state par ennemi (position précédente + état animation)
const _ePrevPos = {}; // id → { x, y }
const _eAnimState = {}; // id → { facing, frame, timer, action }
const F_RUN_FPS = 8; const F_RUN_TICK = 1 / F_RUN_FPS;
const R_RUN_FPS = 12; const R_RUN_TICK = 1 / R_RUN_FPS;
const C_RUN_FPS = 5; const C_RUN_TICK = 1 / C_RUN_FPS; // colosse très lent
const E_RUN_FPS = 16; const E_RUN_TICK = 1 / E_RUN_FPS; // éclat ultrarapide
const V_RUN_FPS = 8; const V_RUN_TICK = 1 / V_RUN_FPS; // running 4 frames
const V_ATK_FPS = 10; const V_ATK_TICK = 1 / V_ATK_FPS; // attack 7 frames
const V_CHG_FPS = 14; const V_CHG_TICK = 1 / V_CHG_FPS; // charge 7 frames
const V_BST_FPS = 8; const V_BST_TICK = 1 / V_BST_FPS; // burst 9 frames south
const TICK_DT = 0.05;
// iso() = projection monde → pixels isométriques
// getTh() = pixels par unité monde (pour les tailles proportionnelles)
// Couleur de chaque type d'ennemi (valeur hexadécimale RGB)
const ENEMY_COLORS = {
fracture: 0xcc3333, // rouge foncé — ennemi de base qui fonce sur le Soulgate
rampant: 0xff6600, // orange — attaque les joueurs
colosse: 0x882200, // brun-rouge — lent et résistant
eclat: 0xffcc00, // jaune — petit et rapide
vexaris: 0xaa22ff, // violet — boss vague 2
general: 0xcc6600, // orange foncé — sous-boss invoqué par Morveth
morveth: 0x550088, // violet très foncé — boss final vague 3
};
// Tailles des ennemis selon le type
// min/max = limites en pixels (pour les petits et grands écrans)
// scale = facteur multiplicateur de th (pixels par unité monde)
const ENEMY_SIZES = {
morveth: { min: 14, max: 55, scale: 0.65 }, // boss final = très grand
vexaris: { min: 12, max: 45, scale: 0.50 }, // boss vague 2 = grand
general: { min: 8, max: 30, scale: 0.32 }, // sous-boss = moyen
};
const DEFAULT_SIZE = { min: 6, max: 25, scale: 0.22 };
// Taille par défaut pour les ennemis normaux (fracture, rampant, colosse, eclat)
export function updateEnemies(layer, pool, enemies) {
// Met à jour les sprites de tous les ennemis depuis les données serveur
// layer = Container PixiJS du layer "ennemis"
// pool = { id → Container } — sprites existants
// enemies = tableau d'EnemyState reçus dans msg.enemies
// 1. Supprimer les sprites des ennemis morts (plus dans la liste serveur)
const ids = new Set(enemies.map(e => e.id));
// ids = Set des IDs encore vivants ce tick
for (const [id, container] of Object.entries(pool)) {
if (!ids.has(id)) {
layer.removeChild(container);
delete pool[id];
delete _ePrevPos[id];
delete _eAnimState[id];
}
}
// 2. Créer ou mettre à jour chaque ennemi
for (const e of enemies) {
// e = objet ennemi : { id, type, x, y, hp, max_hp, is_boss, frozen }
if (!pool[e.id]) {
pool[e.id] = _createEnemyGraphic(e.type);
layer.addChild(pool[e.id]);
_ePrevPos[e.id] = { x: e.x, y: e.y };
_eAnimState[e.id] = { facing: 'south', frame: 0, timer: 0 };
}
const ctr = pool[e.id];
const pos = iso(e.x, e.y);
const prev = _ePrevPos[e.id];
const anim = _eAnimState[e.id];
// Détecter direction depuis le déplacement
const ddx = e.x - prev.x;
const ddy = e.y - prev.y;
const moving = Math.abs(ddx) > 0.01 || Math.abs(ddy) > 0.01;
if (moving) anim.facing = _enemyDir(ddx, ddy) ?? anim.facing;
_ePrevPos[e.id] = { x: e.x, y: e.y };
ctr.x = pos.x;
ctr.y = pos.y;
// Sprite Fracture
if (e.type === 'fracture' && _fractureReady && ctr._sprite) {
anim.timer += TICK_DT;
if (anim.timer >= F_RUN_TICK) {
anim.timer -= F_RUN_TICK;
anim.frame = (anim.frame + 1) % 4;
}
ctr._sprite.texture = moving
? _ft[`frun_${anim.facing}_${anim.frame}`]
: _ft[`f_${anim.facing}`];
}
// Sprite Rampant
if (e.type === 'rampant' && _rampantReady && ctr._sprite) {
anim.timer += TICK_DT;
if (anim.timer >= R_RUN_TICK) {
anim.timer -= R_RUN_TICK;
anim.frame = (anim.frame + 1) % 6;
}
ctr._sprite.texture = moving
? _rt[`rrun_${anim.facing}_${anim.frame}`]
: _rt[`r_${anim.facing}`];
}
// Sprite Colosse
if (e.type === 'colosse' && _colosseReady && ctr._sprite) {
anim.timer += TICK_DT;
if (anim.timer >= C_RUN_TICK) {
anim.timer -= C_RUN_TICK;
anim.frame = (anim.frame + 1) % 6;
}
ctr._sprite.texture = moving
? _ct[`crun_${anim.facing}_${anim.frame}`]
: _ct[`c_${anim.facing}`];
}
// Sprite Éclat
if (e.type === 'eclat' && _eclatReady && ctr._sprite) {
anim.timer += TICK_DT;
if (anim.timer >= E_RUN_TICK) {
anim.timer -= E_RUN_TICK;
anim.frame = (anim.frame + 1) % 8;
}
ctr._sprite.texture = moving
? _et[`erun_${anim.facing}_${anim.frame}`]
: _et[`e_${anim.facing}`];
}
// Sprite Vexaris
if (e.type === 'vexaris' && _vexarisReady && ctr._sprite) {
const prevAction = anim.action ?? 'idle';
let action;
if (e.is_charging) {
action = 'charge';
} else if (moving) {
action = 'run';
} else if (anim.action === 'burst' && anim.frame < 8) {
action = 'burst'; // laisser le burst se terminer
} else {
action = 'attack';
}
if (action !== prevAction) { anim.frame = 0; anim.timer = 0; }
anim.action = action;
anim.timer += TICK_DT;
if (action === 'charge') {
if (anim.timer >= V_CHG_TICK) { anim.timer -= V_CHG_TICK; anim.frame = (anim.frame + 1) % 7; }
ctr._sprite.texture = _vt[`vchg_${anim.facing}_${anim.frame}`];
} else if (action === 'run') {
if (anim.timer >= V_RUN_TICK) { anim.timer -= V_RUN_TICK; anim.frame = (anim.frame + 1) % 4; }
ctr._sprite.texture = _vt[`vrun_${anim.facing}_${anim.frame}`];
} else if (action === 'burst') {
if (anim.timer >= V_BST_TICK) { anim.timer -= V_BST_TICK; anim.frame = Math.min(8, anim.frame + 1); }
ctr._sprite.texture = _vt[`vbst_south_${anim.frame}`];
} else {
if (anim.timer >= V_ATK_TICK) { anim.timer -= V_ATK_TICK; anim.frame = (anim.frame + 1) % 7; }
ctr._sprite.texture = _vt[`vatk_${anim.facing}_${anim.frame}`];
}
}
ctr.tint = e.frozen ? 0x88bbff : 0xffffff;
// Si gelé (sort divin Seris) → teinte bleue
// Sinon → blanc = pas de teinte (couleur de base)
// e.frozen = bool envoyé par le serveur (frozen_timer > 0)
_updateHealthBar(ctr, e.hp, e.max_hp);
// Debug : hitbox de collision
if (ctr._hboxG) {
const hg = ctr._hboxG;
hg.clear();
if (_debugHitboxes) {
const rx = ENEMY_HITBOX_RADIUS * getTw() / 2;
const ry = ENEMY_HITBOX_RADIUS * getTh() / 2;
hg.ellipse(0, 0, rx, ry).stroke({ color: 0xff4400, width: 1.5, alpha: 0.9 });
}
}
}
}
function _updateHealthBar(ctr, hp, maxHp) {
// Met à jour la barre de vie d'un ennemi
// ctr = le Container de l'ennemi (contient barFg en tant que propriété custom)
// hp = points de vie actuels
// maxHp = points de vie max
const ratio = maxHp > 0 ? Math.max(0, hp / maxHp) : 0;
// ratio = pourcentage de vie restante (0.0 à 1.0)
// Math.max(0, ...) = éviter les valeurs négatives si hp < 0 (sécurité)
// maxHp > 0 ? ... : 0 = éviter la division par zéro
const barFg = ctr._barFg;
// _barFg = la Graphics de la barre de vie (stockée comme propriété custom sur le Container)
// Le _ devant = convention "propriété interne"
if (!barFg) return; // sécurité si le Container n'a pas été initialisé correctement
barFg.clear(); // effacer le dessin précédent de la barre
if (ratio > 0) {
// Redessiner la barre avec la nouvelle largeur proportionnelle au ratio
barFg.rect(ctr._barX, ctr._barY, ctr._barW * ratio, ctr._barH)
.fill({ color: 0xff3333 });
// ctr._barW * ratio = largeur totale × ratio HP → barre qui rétrécit
// 0xff3333 = rouge
}
// Si ratio = 0 → on ne dessine rien (ennemi presque mort)
}
function _enemyDir(dx, dy) {
if (Math.abs(dx) < 0.01 && Math.abs(dy) < 0.01) return null;
if (Math.abs(dx) >= Math.abs(dy)) return dx > 0 ? 'east' : 'west';
return dy > 0 ? 'south' : 'north';
}
function _createEnemyGraphic(type) {
// Crée le Container PixiJS pour un ennemi (appelé une seule fois par ennemi)
// type = string : "fracture", "rampant", "colosse", "eclat", "vexaris", "morveth", "general"
const th = getTh(); // pixels par unité monde (pour les tailles proportionnelles)
const container = new Container();
// Calculer le rayon selon le type
const sz = ENEMY_SIZES[type] ?? DEFAULT_SIZE;
// ENEMY_SIZES[type] = taille spécifique si boss/général, sinon DEFAULT_SIZE
// ?? = operateur nullish coalescing : si null ou undefined → utiliser DEFAULT_SIZE
const r = Math.max(sz.min, Math.min(sz.max, th * sz.scale));
// th * sz.scale = taille proportionnelle au zoom
// Math.max(sz.min, Math.min(sz.max, ...)) = clamp entre min et max
const color = ENEMY_COLORS[type] ?? 0xff0000;
// Sprite Rampant
// Canvas 48×48, char bbox y=[8..41], anchor y=0.75 → anchor canvas=36
// Char top au-dessus de l'anchor = 36-8 = 28 canvas pixels
// Scale = (th*1.4)/48 → char top à screen y = -28*(th*1.4/48) = -th*0.817
if (type === 'rampant' && _rampantReady) {
const shadow = new Graphics();
shadow.ellipse(0, r * 0.4, r * 0.5, r * 0.12).fill({ color: 0x000000, alpha: 0.3 });
const scale = (th * 1.4) / 48;
const sprite = new Sprite(_rt.r_south);
sprite.anchor.set(0.5, 0.75);
sprite.scale.set(scale);
const barW = th * 0.7;
const barH = Math.max(2, th * 0.035);
const barY = -(th * 0.82 + barH + 8); // 8px marge au-dessus de la tête
const barBg = new Graphics();
barBg.rect(-barW / 2, barY, barW, barH).fill({ color: 0x220000 });
const barFg = new Graphics();
barFg.rect(-barW / 2, barY, barW, barH).fill({ color: 0xff3333 });
const hboxG = new Graphics();
container.addChild(shadow, sprite, barBg, barFg, hboxG);
container._sprite = sprite;
container._barFg = barFg;
container._barX = -barW / 2;
container._barY = barY;
container._barW = barW;
container._barH = barH;
container._hboxG = hboxG;
return container;
}
// Sprite Fracture
// Canvas 68×68, char bbox y=[10..58], anchor y=0.75 → anchor canvas=51
// Char top au-dessus de l'anchor = 51-10 = 41 canvas pixels
// Scale = (th*1.2)/48 → char top à screen y = -41*(th*1.2/48) = -th*1.025
if (type === 'fracture' && _fractureReady) {
const shadow = new Graphics();
shadow.ellipse(0, r * 0.5, r * 0.6, r * 0.15).fill({ color: 0x000000, alpha: 0.3 });
const scale = (th * 1.2) / 48;
const sprite = new Sprite(_ft.f_south);
sprite.anchor.set(0.5, 0.75);
sprite.scale.set(scale);
const barW = th * 0.8;
const barH = Math.max(2, th * 0.04);
const barY = -(th * 1.025 + barH + 10); // 10px marge au-dessus de la tête
const barBg = new Graphics();
barBg.rect(-barW / 2, barY, barW, barH).fill({ color: 0x220000 });
const barFg = new Graphics();
barFg.rect(-barW / 2, barY, barW, barH).fill({ color: 0xff3333 });
const hboxG = new Graphics();
container.addChild(shadow, sprite, barBg, barFg, hboxG);
container._sprite = sprite;
container._barFg = barFg;
container._barX = -barW / 2;
container._barY = barY;
container._barW = barW;
container._barH = barH;
container._hboxG = hboxG;
return container;
}
// Sprite Colosse
// Canvas 92×92, char top à y=13, anchor y=0.75 → anchor=69, dist=56px
// Scale = (th*2.0)/92 → char_top = -56*(th*2.0/92) = -th*1.217
if (type === 'colosse' && _colosseReady) {
const shadow = new Graphics();
shadow.ellipse(0, r * 0.6, r * 0.9, r * 0.22).fill({ color: 0x000000, alpha: 0.35 });
const scale = (th * 2.0) / 92;
const sprite = new Sprite(_ct.c_south);
sprite.anchor.set(0.5, 0.75);
sprite.scale.set(scale);
const barW = th * 1.2;
const barH = Math.max(2, th * 0.05);
const barY = -(th * 1.22 + barH + 10);
const barBg = new Graphics();
barBg.rect(-barW / 2, barY, barW, barH).fill({ color: 0x220000 });
const barFg = new Graphics();
barFg.rect(-barW / 2, barY, barW, barH).fill({ color: 0xff3333 });
const hboxG = new Graphics();
container.addChild(shadow, sprite, barBg, barFg, hboxG);
container._sprite = sprite;
container._barFg = barFg;
container._barX = -barW / 2;
container._barY = barY;
container._barW = barW;
container._barH = barH;
container._hboxG = hboxG;
return container;
}
// Sprite Vexaris
// Canvas 92×92, char top à y≈12, anchor y=0.75 → anchor=69, dist=57px
// Scale = (th*2.2)/92 → char_top = -57*(th*2.2/92) = -th*1.363
if (type === 'vexaris' && _vexarisReady) {
const shadow = new Graphics();
shadow.ellipse(0, r * 0.7, r * 1.1, r * 0.28).fill({ color: 0x330033, alpha: 0.45 });
const scale = (th * 2.2) / 92;
const sprite = new Sprite(_vt.v_south);
sprite.anchor.set(0.5, 0.75);
sprite.scale.set(scale);
const barW = th * 1.6;
const barH = Math.max(3, th * 0.06);
const barY = -(th * 1.36 + barH + 12);
const barBg = new Graphics();
barBg.rect(-barW / 2, barY, barW, barH).fill({ color: 0x220022 });
const barFg = new Graphics();
barFg.rect(-barW / 2, barY, barW, barH).fill({ color: 0xaa22ff });
const hboxG = new Graphics();
container.addChild(shadow, sprite, barBg, barFg, hboxG);
container._sprite = sprite;
container._barFg = barFg;
container._barX = -barW / 2;
container._barY = barY;
container._barW = barW;
container._barH = barH;
container._hboxG = hboxG;
return container;
}
// Sprite Éclat
// Canvas 36×36, char top à y=4, anchor y=0.75 → anchor=27, dist=23px
// Scale = (th*1.0)/36 → char_top = -23*(th/36) = -th*0.639
if (type === 'eclat' && _eclatReady) {
const shadow = new Graphics();
shadow.ellipse(0, r * 0.3, r * 0.4, r * 0.10).fill({ color: 0x000000, alpha: 0.25 });
const scale = (th * 1.0) / 36;
const sprite = new Sprite(_et.e_south);
sprite.anchor.set(0.5, 0.75);
sprite.scale.set(scale);
const barW = th * 0.5;
const barH = Math.max(2, th * 0.03);
const barY = -(th * 0.64 + barH + 6);
const barBg = new Graphics();
barBg.rect(-barW / 2, barY, barW, barH).fill({ color: 0x220000 });
const barFg = new Graphics();
barFg.rect(-barW / 2, barY, barW, barH).fill({ color: 0xff3333 });
const hboxG = new Graphics();
container.addChild(shadow, sprite, barBg, barFg, hboxG);
container._sprite = sprite;
container._barFg = barFg;
container._barX = -barW / 2;
container._barY = barY;
container._barW = barW;
container._barH = barH;
container._hboxG = hboxG;
return container;
}
const body = new Graphics();
// Ombre au sol
body.ellipse(0, r * 0.5, r * 0.7, r * 0.18).fill({ color: 0x000000, alpha: 0.25 });
// Ellipse aplatie légèrement en dessous du centre (r*0.5) → simuler une ombre
// Cercle principal de l'ennemi
body.circle(0, 0, r).fill({ color });
// Contour blanc subtil
body.circle(0, 0, r).stroke({ color: 0xffffff, width: 1, alpha: 0.2 });
// Barre de vie
const barW = r * 2.4; // largeur totale de la barre = légèrement plus large que le cercle
const barH = Math.max(2, th * 0.04); // hauteur de la barre (au moins 2px)
const barY = -(r * 1.1 + barH + 3); // position Y = au-dessus du cercle (négatif = vers le haut)
const barBg = new Graphics();
// Fond de la barre (rouge très foncé = barre vide)
barBg.rect(-barW / 2, barY, barW, barH).fill({ color: 0x220000 });
// rect(x, y, largeur, hauteur) — centré horizontalement (-barW/2)
const barFg = new Graphics();
// Remplissage de la barre (rouge vif = vie restante)
// Dessiné à 100% au départ, puis mis à jour par _updateHealthBar()
barFg.rect(-barW / 2, barY, barW, barH).fill({ color: 0xff3333 });
const hboxG = new Graphics();
container.addChild(body, barBg, barFg, hboxG);
// Ordre d'ajout = ordre d'affichage (barFg par-dessus barBg par-dessus body)
// Stocker les infos de la barre sur le Container pour les réutiliser dans _updateHealthBar
// On les préfixe avec _ pour indiquer que c'est de l'état interne
container._barFg = barFg; // référence au Graphics à redessiner
container._barX = -barW / 2; // X de départ de la barre
container._barY = barY; // Y de la barre
container._barW = barW; // largeur TOTALE (× ratio HP = largeur réelle)
container._barH = barH; // hauteur de la barre
container._hboxG = hboxG; // Graphics de la hitbox debug
return container;
}