window.gameState = null; const initGame = () => { const canvas = document.getElementById('gameCanvas'); const ctx = canvas.getContext('2d'); // Set canvas to full viewport size const resizeCanvas = () => { canvas.width = window.innerWidth; canvas.height = window.innerHeight; }; window.addEventListener('resize', resizeCanvas); resizeCanvas(); // Game state let gameState = { time: 0, player: createPlayer(100, 100), // Starting position camera: createCamera(0, 0), world: createWorld(), debug: { enabled: false, mouseX: 0, mouseY: 0 } }; // Make gameState globally accessible window.gameState = gameState; // Throttle mouse move updates let mouseMoveThrottle; canvas.addEventListener('mousemove', (e) => { if (!mouseMoveThrottle) { mouseMoveThrottle = setTimeout(() => { gameState.debug.mouseX = e.clientX + gameState.camera.x; gameState.debug.mouseY = e.clientY + gameState.camera.y; mouseMoveThrottle = null; }, 16); // ~60fps } }); // Add debug toggle listener window.addEventListener('keydown', (e) => { if (e.key === 'd') { gameState.debug.enabled = !gameState.debug.enabled; } }); // Game loop const gameLoop = (timestamp) => { // Calculate delta time const deltaTime = timestamp - gameState.time; gameState.time = timestamp; // Update gameState = updateGame(gameState, deltaTime); // Render render(ctx, gameState); // Next frame requestAnimationFrame(gameLoop); }; // Start the game loop requestAnimationFrame(gameLoop); }; const updateGame = (state, deltaTime) => { const updatedPlayer = updatePlayer(state.player, deltaTime); const updatedCamera = updateCamera(state.camera, updatedPlayer); return { ...state, player: updatedPlayer, camera: updatedCamera }; }; const render = (ctx, state) => { // Calculate groundY once at the start const groundY = ctx.canvas.height - state.world.groundHeight; // Create gradient for sky - cache this instead of recreating every frame if (!state.cachedGradient) { const gradient = ctx.createLinearGradient(0, 0, 0, groundY); gradient.addColorStop(0, '#1a1a2e'); gradient.addColorStop(0.4, '#2d1b3d'); gradient.addColorStop(0.7, '#462639'); gradient.addColorStop(1, '#1f1f2f'); state.cachedGradient = gradient; } // Fill sky using cached gradient ctx.fillStyle = state.cachedGradient; ctx.fillRect(0, 0, ctx.canvas.width, groundY); // Implement view frustum culling - only render visible objects const viewBounds = { left: state.camera.x - 100, // Add small buffer right: state.camera.x + state.camera.width + 100, top: state.camera.y - 100, bottom: state.camera.y + state.camera.height + 100 }; // Apply camera transform ctx.save(); ctx.translate(-state.camera.x, -state.camera.y); // Fill black background ctx.fillStyle = '#000000'; ctx.fillRect( viewBounds.left, groundY, viewBounds.right - viewBounds.left, ctx.canvas.height ); // 1. Render only visible background objects state.world.backgroundTrees.forEach(tree => { if (tree.x > viewBounds.left && tree.x < viewBounds.right) { renderTree(ctx, tree, groundY); } }); state.world.backgroundRocks.forEach(rock => { if (rock.x > viewBounds.left && rock.x < viewBounds.right) { renderRock(ctx, rock, groundY); } }); state.world.backgroundGrass.forEach(grass => { if (grass.x > viewBounds.left && grass.x < viewBounds.right) { renderGrass(ctx, grass, groundY, state.time); } }); // 2. Render platforms state.world.platforms.forEach(platform => { ctx.fillStyle = platform.color; ctx.fillRect(platform.x, platform.y, platform.width, platform.height); }); // 3. Render player renderPlayer(ctx, state.player); // 4. Render foreground objects state.world.foregroundTrees.forEach(tree => { if (tree.x > viewBounds.left && tree.x < viewBounds.right) { renderTree(ctx, tree, groundY); } }); state.world.foregroundRocks.forEach(rock => { if (rock.x > viewBounds.left && rock.x < viewBounds.right) { renderRock(ctx, rock, groundY); } }); state.world.foregroundGrass.forEach(grass => { if (grass.x > viewBounds.left && grass.x < viewBounds.right) { renderGrass(ctx, grass, groundY, state.time); } }); // 5. Render ground (just the grass top) ctx.fillStyle = '#4a4'; ctx.fillRect( state.camera.x - 1000, groundY, ctx.canvas.width + 2000, 1 // Only render the grass line ); // Render debug information if enabled if (state.debug.enabled) { ctx.restore(); ctx.fillStyle = '#ffffff'; ctx.font = '14px monospace'; const text = `x: ${Math.round(state.debug.mouseX)}, y: ${Math.round(state.debug.mouseY)}`; ctx.fillText(text, 10, ctx.canvas.height - 20); } else { ctx.restore(); } }; // Initialize the game when the window loads window.addEventListener('load', initGame);