hockey-manager/src/systems/renderer.js
2025-09-16 13:08:41 +02:00

300 lines
8.7 KiB
JavaScript

class Renderer {
constructor(canvas) {
this.canvas = canvas;
this.ctx = canvas.getContext('2d');
this.camera = {
x: 0,
y: 0,
zoom: 1,
target: null,
smoothing: 0.1
};
this.setupCanvas();
}
setupCanvas() {
this.canvas.style.imageRendering = 'pixelated';
this.ctx.imageSmoothingEnabled = false;
}
clear() {
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
}
drawRink(gameState) {
const rink = gameState.rink;
this.ctx.save();
this.applyCamera();
this.ctx.fillStyle = '#f8f8f8';
this.ctx.fillRect(0, 0, rink.width, rink.height);
this.drawRinkLines(rink);
this.drawGoals(rink);
this.drawFaceoffDots(rink);
this.drawCreases(rink);
this.ctx.restore();
}
drawRinkLines(rink) {
this.ctx.strokeStyle = '#d32f2f';
this.ctx.lineWidth = 3;
this.ctx.beginPath();
this.ctx.rect(0, 0, rink.width, rink.height);
this.ctx.stroke();
this.ctx.strokeStyle = '#2196f3';
this.ctx.lineWidth = 2;
this.ctx.beginPath();
this.ctx.moveTo(rink.centerX, 0);
this.ctx.lineTo(rink.centerX, rink.height);
this.ctx.stroke();
const zoneWidth = rink.width / 3;
this.ctx.strokeStyle = '#2196f3';
this.ctx.setLineDash([10, 5]);
this.ctx.beginPath();
this.ctx.moveTo(zoneWidth, 0);
this.ctx.lineTo(zoneWidth, rink.height);
this.ctx.stroke();
this.ctx.beginPath();
this.ctx.moveTo(rink.width - zoneWidth, 0);
this.ctx.lineTo(rink.width - zoneWidth, rink.height);
this.ctx.stroke();
this.ctx.setLineDash([]);
this.ctx.beginPath();
this.ctx.arc(rink.centerX, rink.centerY, 100, 0, Math.PI * 2);
this.ctx.stroke();
}
drawGoals(rink) {
this.ctx.strokeStyle = '#d32f2f';
this.ctx.lineWidth = 4;
const goalY = rink.centerY;
const goalHeight = rink.goalHeight;
this.ctx.beginPath();
this.ctx.moveTo(0, goalY - goalHeight);
this.ctx.lineTo(-10, goalY - goalHeight);
this.ctx.lineTo(-10, goalY + goalHeight);
this.ctx.lineTo(0, goalY + goalHeight);
this.ctx.stroke();
this.ctx.beginPath();
this.ctx.moveTo(rink.width, goalY - goalHeight);
this.ctx.lineTo(rink.width + 10, goalY - goalHeight);
this.ctx.lineTo(rink.width + 10, goalY + goalHeight);
this.ctx.lineTo(rink.width, goalY + goalHeight);
this.ctx.stroke();
this.ctx.fillStyle = 'rgba(211, 47, 47, 0.1)';
this.ctx.fillRect(-10, goalY - goalHeight, 10, goalHeight * 2);
this.ctx.fillRect(rink.width, goalY - goalHeight, 10, goalHeight * 2);
}
drawCreases(rink) {
this.ctx.strokeStyle = '#4fc3f7';
this.ctx.lineWidth = 2;
this.ctx.fillStyle = 'rgba(79, 195, 247, 0.1)';
const creaseRadius = 60;
const goalY = rink.centerY;
this.ctx.beginPath();
this.ctx.arc(0, goalY, creaseRadius, -Math.PI/2, Math.PI/2);
this.ctx.fill();
this.ctx.stroke();
this.ctx.beginPath();
this.ctx.arc(rink.width, goalY, creaseRadius, Math.PI/2, -Math.PI/2);
this.ctx.fill();
this.ctx.stroke();
}
drawFaceoffDots(rink) {
this.ctx.fillStyle = '#d32f2f';
rink.faceoffDots.forEach(dot => {
this.ctx.beginPath();
this.ctx.arc(dot.x, dot.y, 8, 0, Math.PI * 2);
this.ctx.fill();
this.ctx.strokeStyle = '#d32f2f';
this.ctx.lineWidth = 2;
this.ctx.beginPath();
this.ctx.arc(dot.x, dot.y, 30, 0, Math.PI * 2);
this.ctx.stroke();
});
}
drawPlayers(players) {
this.ctx.save();
this.applyCamera();
players.forEach(player => {
player.render(this.ctx);
});
this.ctx.restore();
}
drawPuck(puck) {
this.ctx.save();
this.applyCamera();
puck.render(this.ctx);
this.ctx.restore();
}
drawUI(gameState) {
this.updateScoreBoard(gameState);
this.updateGameStats(gameState);
}
updateScoreBoard(gameState) {
document.querySelector('.team.home .score').textContent = gameState.homeScore;
document.querySelector('.team.away .score').textContent = gameState.awayScore;
document.getElementById('period').textContent = gameState.getPeriodName();
document.getElementById('clock').textContent = gameState.formatTime(gameState.timeRemaining);
}
updateGameStats(gameState) {
document.getElementById('home-shots').textContent = gameState.stats.home.shots;
document.getElementById('away-shots').textContent = gameState.stats.away.shots;
}
drawParticleEffect(position, type, color = '#ffff00') {
this.ctx.save();
this.applyCamera();
switch (type) {
case 'goal':
this.drawGoalEffect(position);
break;
case 'hit':
this.drawHitEffect(position, color);
break;
case 'save':
this.drawSaveEffect(position);
break;
}
this.ctx.restore();
}
drawGoalEffect(position) {
this.ctx.fillStyle = '#ffff00';
this.ctx.strokeStyle = '#ff8800';
this.ctx.lineWidth = 3;
for (let i = 0; i < 8; i++) {
const angle = (i / 8) * Math.PI * 2;
const x = position.x + Math.cos(angle) * 30;
const y = position.y + Math.sin(angle) * 30;
this.ctx.beginPath();
this.ctx.arc(x, y, 5, 0, Math.PI * 2);
this.ctx.fill();
this.ctx.stroke();
}
}
drawHitEffect(position, color) {
this.ctx.strokeStyle = color;
this.ctx.lineWidth = 4;
for (let i = 0; i < 6; i++) {
const angle = (i / 6) * Math.PI * 2;
const startX = position.x + Math.cos(angle) * 10;
const startY = position.y + Math.sin(angle) * 10;
const endX = position.x + Math.cos(angle) * 25;
const endY = position.y + Math.sin(angle) * 25;
this.ctx.beginPath();
this.ctx.moveTo(startX, startY);
this.ctx.lineTo(endX, endY);
this.ctx.stroke();
}
}
drawSaveEffect(position) {
this.ctx.fillStyle = '#4fc3f7';
this.ctx.strokeStyle = '#0288d1';
this.ctx.lineWidth = 2;
this.ctx.beginPath();
this.ctx.arc(position.x, position.y, 20, 0, Math.PI * 2);
this.ctx.stroke();
this.ctx.beginPath();
this.ctx.arc(position.x, position.y, 15, 0, Math.PI * 2);
this.ctx.stroke();
}
updateCamera(target) {
if (target) {
this.camera.target = target;
}
if (this.camera.target) {
const targetX = this.canvas.width / 2 - this.camera.target.x * this.camera.zoom;
const targetY = this.canvas.height / 2 - this.camera.target.y * this.camera.zoom;
this.camera.x += (targetX - this.camera.x) * this.camera.smoothing;
this.camera.y += (targetY - this.camera.y) * this.camera.smoothing;
}
}
applyCamera() {
this.ctx.translate(this.camera.x, this.camera.y);
this.ctx.scale(this.camera.zoom, this.camera.zoom);
}
setZoom(zoom) {
this.camera.zoom = Math.max(0.5, Math.min(2.0, zoom));
}
screenToWorld(screenPos) {
return new Vector2(
(screenPos.x - this.camera.x) / this.camera.zoom,
(screenPos.y - this.camera.y) / this.camera.zoom
);
}
worldToScreen(worldPos) {
return new Vector2(
worldPos.x * this.camera.zoom + this.camera.x,
worldPos.y * this.camera.zoom + this.camera.y
);
}
drawDebugInfo(gameState, players, puck) {
if (!window.debugMode) return;
this.ctx.save();
this.ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
this.ctx.fillRect(10, 10, 200, 150);
this.ctx.fillStyle = '#fff';
this.ctx.font = '12px monospace';
this.ctx.fillText(`FPS: ${Math.round(1000 / 16)}`, 20, 30);
this.ctx.fillText(`Players: ${players.length}`, 20, 50);
this.ctx.fillText(`Puck Speed: ${Math.round(puck.velocity.magnitude())}`, 20, 70);
this.ctx.fillText(`Game Time: ${gameState.formatTime(gameState.timeRemaining)}`, 20, 90);
this.ctx.fillText(`Period: ${gameState.period}`, 20, 110);
this.ctx.fillText(`Paused: ${gameState.isPaused}`, 20, 130);
this.ctx.fillText(`Speed: ${gameState.gameSpeed}x`, 20, 150);
this.ctx.restore();
}
}