class IsometricGame { constructor() { this.canvas = document.getElementById('gameCanvas'); this.ctx = this.canvas.getContext('2d'); // Grid properties this.gridSize = 10; this.tileWidth = 50; this.tileHeight = 25; // Player properties this.player = { x: 0, y: 0, targetX: 0, targetY: 0, size: 20, path: [], // Array to store waypoints currentWaypoint: null }; // Handle window resize this.resizeCanvas(); window.addEventListener('resize', () => this.resizeCanvas()); this.setupEventListeners(); this.gameLoop(); } resizeCanvas() { this.canvas.width = window.innerWidth; this.canvas.height = window.innerHeight; // Recalculate grid offset to center it this.offsetX = this.canvas.width / 2; this.offsetY = this.canvas.height / 3; // Scale tile size based on screen size const minDimension = Math.min(this.canvas.width, this.canvas.height); const scaleFactor = minDimension / 800; // 800 is our reference size this.tileWidth = 50 * scaleFactor; this.tileHeight = 25 * scaleFactor; this.player.size = 20 * scaleFactor; } toIsometric(x, y) { return { x: (x - y) * this.tileWidth / 2, y: (x + y) * this.tileHeight / 2 }; } fromIsometric(screenX, screenY) { // Convert screen coordinates back to grid coordinates screenX -= this.offsetX; screenY -= this.offsetY; const x = (screenX / this.tileWidth + screenY / this.tileHeight) / 1; const y = (screenY / this.tileHeight - screenX / this.tileWidth) / 1; return { x: Math.round(x), y: Math.round(y) }; } drawGrid() { for (let x = 0; x < this.gridSize; x++) { for (let y = 0; y < this.gridSize; y++) { const iso = this.toIsometric(x, y); // Draw tile this.ctx.beginPath(); this.ctx.moveTo(iso.x + this.offsetX, iso.y + this.offsetY - this.tileHeight/2); this.ctx.lineTo(iso.x + this.offsetX + this.tileWidth/2, iso.y + this.offsetY); this.ctx.lineTo(iso.x + this.offsetX, iso.y + this.offsetY + this.tileHeight/2); this.ctx.lineTo(iso.x + this.offsetX - this.tileWidth/2, iso.y + this.offsetY); this.ctx.closePath(); this.ctx.strokeStyle = '#666'; this.ctx.stroke(); this.ctx.fillStyle = '#fff'; this.ctx.fill(); } } } drawPlayer() { // Convert player grid position to isometric coordinates const iso = this.toIsometric(this.player.x, this.player.y); // Draw player shadow (slightly offset and darker) this.ctx.beginPath(); this.ctx.ellipse( iso.x + this.offsetX, iso.y + this.offsetY + 2, this.player.size * 0.8, this.player.size * 0.3, 0, 0, Math.PI * 2 ); this.ctx.fillStyle = 'rgba(0,0,0,0.2)'; this.ctx.fill(); // Draw player "body" (rectangle) this.ctx.beginPath(); this.ctx.fillStyle = '#ff4444'; this.ctx.strokeStyle = '#aa0000'; // Draw a thin rectangle for the body const bodyHeight = this.player.size * 2; const bodyWidth = this.player.size * 0.8; this.ctx.save(); this.ctx.translate(iso.x + this.offsetX, iso.y + this.offsetY); this.ctx.scale(1, 0.5); // Apply isometric perspective this.ctx.fillRect(-bodyWidth/2, -bodyHeight, bodyWidth, bodyHeight); this.ctx.strokeRect(-bodyWidth/2, -bodyHeight, bodyWidth, bodyHeight); this.ctx.restore(); // Draw player head (circle) this.ctx.beginPath(); this.ctx.ellipse( iso.x + this.offsetX, iso.y + this.offsetY - this.player.size, this.player.size, this.player.size * 0.5, 0, 0, Math.PI * 2 ); this.ctx.fillStyle = '#ff4444'; this.ctx.fill(); this.ctx.strokeStyle = '#aa0000'; this.ctx.stroke(); } findPath(startX, startY, endX, endY) { // Simple pathfinding that follows grid edges const path = []; // First move along X axis if (startX !== endX) { const stepX = startX < endX ? 1 : -1; for (let x = startX + stepX; stepX > 0 ? x <= endX : x >= endX; x += stepX) { path.push({ x: x, y: startY }); } } // Then move along Y axis if (startY !== endY) { const stepY = startY < endY ? 1 : -1; for (let y = startY + stepY; stepY > 0 ? y <= endY : y >= endY; y += stepY) { path.push({ x: endX, y: y }); } } return path; } updatePlayer() { const speed = 0.1; // If we don't have a current waypoint but have a path, get next waypoint if (!this.player.currentWaypoint && this.player.path.length > 0) { this.player.currentWaypoint = this.player.path.shift(); } // Move towards current waypoint if (this.player.currentWaypoint) { const dx = this.player.currentWaypoint.x - this.player.x; const dy = this.player.currentWaypoint.y - this.player.y; // Move towards waypoint this.player.x += dx * speed; this.player.y += dy * speed; // Check if we're close enough to waypoint to consider it reached const distance = Math.sqrt(dx * dx + dy * dy); if (distance < 0.1) { this.player.x = this.player.currentWaypoint.x; this.player.y = this.player.currentWaypoint.y; this.player.currentWaypoint = null; } } } setupEventListeners() { this.canvas.addEventListener('click', (e) => { const rect = this.canvas.getBoundingClientRect(); const clickX = e.clientX - rect.left; const clickY = e.clientY - rect.top; const gridPos = this.fromIsometric(clickX, clickY); // Only move if within grid bounds if (gridPos.x >= 0 && gridPos.x < this.gridSize && gridPos.y >= 0 && gridPos.y < this.gridSize) { // Set target and calculate path this.player.targetX = Math.round(gridPos.x); this.player.targetY = Math.round(gridPos.y); // Calculate new path this.player.path = this.findPath( Math.round(this.player.x), Math.round(this.player.y), this.player.targetX, this.player.targetY ); // Clear current waypoint to start new path this.player.currentWaypoint = null; } }); } gameLoop() { // Clear canvas this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); // Draw game elements this.drawGrid(); this.updatePlayer(); this.drawPlayer(); // Continue game loop requestAnimationFrame(() => this.gameLoop()); } } // Start the game when the page loads window.onload = () => { new IsometricGame(); };