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);