Private
Public Access
1
0
Files
it232Abschied/static/js/render.js
Sebastian Unterschütz ae3eb34c0e
All checks were successful
Dynamic Branch Deploy / build-and-deploy (push) Successful in 1m23s
fix texturen
2025-12-04 23:21:01 +01:00

322 lines
9.3 KiB
JavaScript

// ==========================================
// PIXI INITIALISIERUNG (V8)
// ==========================================
let floorGraphic = null;
async function initPixi() {
if (app) return;
app = new PIXI.Application();
// 1. Asynchrones Init (v8 Standard)
await app.init({
width: GAME_WIDTH,
height: GAME_HEIGHT,
backgroundColor: 0x1a1a1a, // Dunkelgrau
preference: 'webgpu', // Versuch WebGPU (schneller auf Handy!)
resolution: Math.min(window.devicePixelRatio || 1, 2), // Retina Limit
autoDensity: true,
antialias: false,
roundPixels: true // Wichtig für Pixelart
});
// 2. Canvas einhängen
document.getElementById('game-container').appendChild(app.canvas);
// 3. Layer erstellen
bgLayer = new PIXI.Container();
gameLayer = new PIXI.Container();
debugLayer = new PIXI.Graphics(); // Für Hitboxen
// Sortierung aktivieren (damit Player vor Obstacles ist)
gameLayer.sortableChildren = true;
app.stage.addChild(bgLayer);
app.stage.addChild(gameLayer);
app.stage.addChild(debugLayer);
// Einmalig Resizen
resize();
console.log(`🚀 Renderer: ${app.renderer.name}`);
}
function resize() {
if (!app || !app.canvas) return;
const windowWidth = window.innerWidth - 20;
const windowHeight = window.innerHeight - 20;
const targetRatio = GAME_WIDTH / GAME_HEIGHT;
const windowRatio = windowWidth / windowHeight;
let finalWidth, finalHeight;
if (windowRatio < targetRatio) {
finalWidth = windowWidth;
finalHeight = windowWidth / targetRatio;
} else {
finalHeight = windowHeight;
finalWidth = finalHeight * targetRatio;
}
// CSS Skalierung
app.canvas.style.width = `${Math.floor(finalWidth)}px`;
app.canvas.style.height = `${Math.floor(finalHeight)}px`;
}
window.addEventListener('resize', resize);
// ==========================================
// RENDER LOOP (RETAINED MODE)
// ==========================================
async function drawGame(alpha = 1.0) {
if (!app) {
if(!document.getElementById('game-container').querySelector('canvas')) await initPixi();
return;
}
// 1. HINTERGRUND
updateBackground();
// 2. BODEN (FIX: Einmalig erstellen oder updaten)
if (!floorGraphic) {
floorGraphic = new PIXI.Graphics();
bgLayer.addChild(floorGraphic); // Zum Background Layer hinzufügen
}
floorGraphic.clear();
// Boden: Dunkelgrau
floorGraphic.rect(0, GROUND_Y, GAME_WIDTH, 50).fill(0x333333);
// Grüne Linie oben drauf (Gras/Teppich)
floorGraphic.rect(0, GROUND_Y, GAME_WIDTH, 4).fill(0x4CAF50);
// 3. OBJEKTE SYNCEN
syncSprites(obstacles, spriteCache, 'obstacle', alpha);
syncSprites(platforms, platformCache, 'platform', alpha);
// 4. SPIELER
updatePlayer(alpha);
// 5. DEBUG
if (typeof DEBUG_SYNC !== 'undefined' && DEBUG_SYNC) {
drawDebugOverlay(alpha);
} else {
debugLayer.clear();
}
}
// ------------------------------------------------------
// HELPER: SYNC SYSTEM
// ------------------------------------------------------
function syncSprites(dataList, cacheMap, type, alpha) {
const usedObjects = new Set();
dataList.forEach(obj => {
usedObjects.add(obj);
let sprite = cacheMap.get(obj);
if (!sprite) {
sprite = createPixiSprite(obj, type);
gameLayer.addChild(sprite);
cacheMap.set(obj, sprite);
}
const def = obj.def || {};
const rX = (obj.prevX !== undefined) ? lerp(obj.prevX, obj.x, alpha) : obj.x;
const rY = obj.y;
if (obj.speech) {
let bubble = sprite.children.find(c => c.label === "bubble");
if (!bubble) {
bubble = createSpeechBubble(obj.speech);
bubble.y = -10;
if (def.height) bubble.y = -5;
sprite.addChild(bubble);
}
} else {
// Keine Sprache mehr? Blase entfernen falls vorhanden
const bubble = sprite.children.find(c => c.label === "bubble");
if (bubble) {
sprite.removeChild(bubble);
bubble.destroy();
}
}
if (type === 'platform') {
sprite.x = rX;
sprite.y = rY;
} else {
// Editor Werte
const scale = def.imgScale || 1.0;
const offX = def.imgOffsetX || 0;
const offY = def.imgOffsetY || 0;
const hbw = def.width || 30;
const hbh = def.height || 30;
const drawW = hbw * scale;
const baseX = rX + (hbw - drawW) / 2;
const baseY = rY + (hbh - (hbh * scale));
sprite.x = baseX + offX;
sprite.y = baseY + offY;
sprite.width = drawW;
sprite.height = hbh * scale;
}
});
// C. Aufräumen (Garbage Collection)
for (const [obj, sprite] of cacheMap.entries()) {
if (!usedObjects.has(obj)) {
gameLayer.removeChild(sprite);
sprite.destroy();
cacheMap.delete(obj);
}
}
}
function createPixiSprite(obj, type) {
if (type === 'platform') {
const g = new PIXI.Graphics();
// Holz Plattform
g.rect(0, 0, obj.w, obj.h).fill(0x8B4513); // Braun
g.rect(0, 0, obj.w, 5).fill(0xA0522D); // Hellbraun Oben
return g;
}
else {
const def = obj.def || {};
// CHECK: Ist die Textur im Cache?
// Wir nutzen PIXI.Assets.get(), das ist sicherer als cache.has
let texture = null;
try {
if (def.id) texture = PIXI.Assets.get(def.id);
} catch(e) {}
if (texture) {
const s = new PIXI.Sprite(texture);
return s;
} else {
// FALLBACK (Wenn Bild fehlt -> Magenta Box)
const g = new PIXI.Graphics();
let color = 0xFF00FF;
if (def.type === 'coin') color = 0xFFD700;
g.rect(0, 0, def.width||30, def.height||30).fill(color);
return g;
}
}
}
// ------------------------------------------------------
// PLAYER & BG
// ------------------------------------------------------
function updatePlayer(alpha) {
if (!pixiPlayer) {
if (PIXI.Assets.cache.has('player')) {
pixiPlayer = PIXI.Sprite.from('player');
} else {
pixiPlayer = new PIXI.Graphics().rect(0,0,30,50).fill(0xFF0000);
}
gameLayer.addChild(pixiPlayer);
pixiPlayer.zIndex = 100; // Immer im Vordergrund
}
let rY = lerp(player.prevY || player.y, player.y, alpha);
const drawY = isCrouching ? rY + 25 : rY;
pixiPlayer.x = player.x;
pixiPlayer.y = drawY;
}
function updateBackground() {
// FEHLERBEHEBUNG:
// Wir prüfen 'gameConfig.backgrounds' statt 'bgSprites'
if (!gameConfig || !gameConfig.backgrounds || gameConfig.backgrounds.length === 0) return;
const changeInterval = 10000;
const idx = Math.floor(score / changeInterval) % gameConfig.backgrounds.length;
// Der Key ist der Dateiname (so haben wir es in main.js geladen)
const bgKey = gameConfig.backgrounds[idx];
// Sicherstellen, dass Asset geladen ist
if (!PIXI.Assets.cache.has(bgKey)) return;
if (!bgSprite) {
bgSprite = new PIXI.Sprite();
bgSprite.width = GAME_WIDTH;
bgSprite.height = GAME_HEIGHT;
bgLayer.addChild(bgSprite);
}
// Textur wechseln wenn nötig
const tex = PIXI.Assets.get(bgKey);
if (tex && bgSprite.texture !== tex) {
bgSprite.texture = tex;
}
}
// ------------------------------------------------------
// DEBUG
// ------------------------------------------------------
function drawDebugOverlay(alpha) {
const g = debugLayer;
g.clear();
// Server (Cyan)
if (serverObstacles) {
serverObstacles.forEach(o => {
g.rect(o.x, o.y, o.w, o.h).stroke({ width: 1, color: 0x00FFFF });
});
}
// Client (Grün)
obstacles.forEach(o => {
const def = o.def || {};
const rX = (o.prevX !== undefined) ? lerp(o.prevX, o.x, alpha) : o.x;
g.rect(rX, o.y, def.width||30, def.height||30).stroke({ width: 1, color: 0x00FF00 });
});
}
// Helper: Erstellt eine Pixi-Sprechblase
function createSpeechBubble(text) {
const container = new PIXI.Container();
// 1. Text erstellen
const style = new PIXI.TextStyle({
fontFamily: 'monospace',
fontSize: 12,
fontWeight: 'bold',
fill: '#000000',
align: 'center'
});
const pixiText = new PIXI.Text({ text: text, style: style });
pixiText.anchor.set(0.5); // Text-Mitte ist Anker
// Maße berechnen
const w = pixiText.width + 10;
const h = pixiText.height + 6;
// 2. Hintergrund (Blase)
const g = new PIXI.Graphics();
g.rect(-w/2, -h/2, w, h).fill(0xFFFFFF); // Weißer Kasten
g.rect(-w/2, -h/2, w, h).stroke({ width: 2, color: 0x000000 }); // Schwarzer Rand
// Kleines Dreieck unten (optional, für den "Speech"-Look)
g.moveTo(-5, h/2).lineTo(0, h/2 + 5).lineTo(5, h/2).fill(0xFFFFFF);
// Zusammenfügen
container.addChild(g);
container.addChild(pixiText);
// Name setzen, damit wir es später wiederfinden
container.label = "bubble";
return container;
}