318 lines
9.4 KiB
JavaScript
318 lines
9.4 KiB
JavaScript
/**
|
|
* Enhanced map canvas for RTS mode with terrain, units, and buildings
|
|
*/
|
|
export class RTSMapCanvas {
|
|
constructor(canvasId = 'rts-game-map') {
|
|
this.canvas = document.getElementById(canvasId);
|
|
if (!this.canvas) {
|
|
this.canvas = document.createElement('canvas');
|
|
this.canvas.id = canvasId;
|
|
}
|
|
|
|
this.ctx = this.canvas.getContext('2d');
|
|
this.zoom = 1;
|
|
this.offsetX = 0;
|
|
this.offsetY = 0;
|
|
this.selectedUnit = null;
|
|
this.selectedBuilding = null;
|
|
|
|
this.initializeCanvas();
|
|
this.setupEventListeners();
|
|
}
|
|
|
|
initializeCanvas() {
|
|
this.canvas.width = 800;
|
|
this.canvas.height = 600;
|
|
this.drawTerrain();
|
|
this.drawGrid();
|
|
}
|
|
|
|
setupEventListeners() {
|
|
this.canvas.addEventListener('click', (e) => this.handleClick(e));
|
|
this.canvas.addEventListener('mousemove', (e) => this.handleMouseMove(e));
|
|
}
|
|
|
|
drawTerrain() {
|
|
const ctx = this.ctx;
|
|
|
|
// Clear canvas
|
|
ctx.fillStyle = '#1a2332';
|
|
ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
|
|
|
|
// Draw terrain features
|
|
this.drawForests();
|
|
this.drawMountains();
|
|
this.drawRivers();
|
|
this.drawSettlement();
|
|
}
|
|
|
|
drawGrid() {
|
|
const ctx = this.ctx;
|
|
ctx.strokeStyle = '#334155';
|
|
ctx.lineWidth = 0.5;
|
|
ctx.globalAlpha = 0.3;
|
|
|
|
const gridSize = 40;
|
|
|
|
// Vertical lines
|
|
for (let x = 0; x <= this.canvas.width; x += gridSize) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(x, 0);
|
|
ctx.lineTo(x, this.canvas.height);
|
|
ctx.stroke();
|
|
}
|
|
|
|
// Horizontal lines
|
|
for (let y = 0; y <= this.canvas.height; y += gridSize) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, y);
|
|
ctx.lineTo(this.canvas.width, y);
|
|
ctx.stroke();
|
|
}
|
|
|
|
ctx.globalAlpha = 1;
|
|
}
|
|
|
|
drawForests() {
|
|
const ctx = this.ctx;
|
|
ctx.fillStyle = '#22c55e';
|
|
|
|
// Forest patches
|
|
const forests = [
|
|
{ x: 100, y: 100, radius: 60 },
|
|
{ x: 600, y: 150, radius: 80 },
|
|
{ x: 200, y: 400, radius: 50 },
|
|
{ x: 650, y: 450, radius: 70 }
|
|
];
|
|
|
|
forests.forEach(forest => {
|
|
ctx.beginPath();
|
|
ctx.arc(forest.x, forest.y, forest.radius, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
|
|
// Add tree symbols
|
|
for (let i = 0; i < 8; i++) {
|
|
const angle = (i / 8) * Math.PI * 2;
|
|
const treeX = forest.x + Math.cos(angle) * (forest.radius * 0.7);
|
|
const treeY = forest.y + Math.sin(angle) * (forest.radius * 0.7);
|
|
|
|
ctx.fillStyle = '#16a34a';
|
|
ctx.fillRect(treeX - 2, treeY - 2, 4, 4);
|
|
}
|
|
ctx.fillStyle = '#22c55e';
|
|
});
|
|
}
|
|
|
|
drawMountains() {
|
|
const ctx = this.ctx;
|
|
ctx.fillStyle = '#64748b';
|
|
|
|
const mountains = [
|
|
{ x: 300, y: 80, width: 100, height: 80 },
|
|
{ x: 500, y: 300, width: 120, height: 100 }
|
|
];
|
|
|
|
mountains.forEach(mountain => {
|
|
ctx.beginPath();
|
|
ctx.moveTo(mountain.x, mountain.y + mountain.height);
|
|
ctx.lineTo(mountain.x + mountain.width / 2, mountain.y);
|
|
ctx.lineTo(mountain.x + mountain.width, mountain.y + mountain.height);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
});
|
|
}
|
|
|
|
drawRivers() {
|
|
const ctx = this.ctx;
|
|
ctx.strokeStyle = '#3b82f6';
|
|
ctx.lineWidth = 8;
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, 200);
|
|
ctx.quadraticCurveTo(200, 180, 400, 220);
|
|
ctx.quadraticCurveTo(600, 260, 800, 240);
|
|
ctx.stroke();
|
|
}
|
|
|
|
drawSettlement() {
|
|
const ctx = this.ctx;
|
|
const centerX = 400;
|
|
const centerY = 350;
|
|
|
|
// Town hall (main building)
|
|
ctx.fillStyle = '#8b5cf6';
|
|
ctx.fillRect(centerX - 20, centerY - 20, 40, 40);
|
|
|
|
// Surrounding buildings
|
|
const buildings = [
|
|
{ x: centerX - 60, y: centerY - 10, type: 'barracks', color: '#ef4444' },
|
|
{ x: centerX + 40, y: centerY - 10, type: 'storage', color: '#f59e0b' },
|
|
{ x: centerX - 10, y: centerY - 60, type: 'farm', color: '#22c55e' },
|
|
{ x: centerX - 10, y: centerY + 40, type: 'market', color: '#3b82f6' }
|
|
];
|
|
|
|
buildings.forEach(building => {
|
|
ctx.fillStyle = building.color;
|
|
ctx.fillRect(building.x - 15, building.y - 15, 30, 30);
|
|
});
|
|
|
|
// Draw units around settlement
|
|
this.drawUnits();
|
|
}
|
|
|
|
drawUnits() {
|
|
const ctx = this.ctx;
|
|
const centerX = 400;
|
|
const centerY = 350;
|
|
|
|
const units = [
|
|
{ x: centerX - 80, y: centerY + 20, type: 'warrior' },
|
|
{ x: centerX - 70, y: centerY + 35, type: 'warrior' },
|
|
{ x: centerX + 60, y: centerY + 25, type: 'archer' },
|
|
{ x: centerX + 20, y: centerY - 80, type: 'cavalry' }
|
|
];
|
|
|
|
units.forEach(unit => {
|
|
if (unit.type === 'warrior') {
|
|
ctx.fillStyle = '#dc2626';
|
|
ctx.beginPath();
|
|
ctx.arc(unit.x, unit.y, 6, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
} else if (unit.type === 'archer') {
|
|
ctx.fillStyle = '#059669';
|
|
ctx.fillRect(unit.x - 5, unit.y - 5, 10, 10);
|
|
} else if (unit.type === 'cavalry') {
|
|
ctx.fillStyle = '#7c3aed';
|
|
ctx.beginPath();
|
|
ctx.moveTo(unit.x, unit.y - 8);
|
|
ctx.lineTo(unit.x - 6, unit.y + 4);
|
|
ctx.lineTo(unit.x + 6, unit.y + 4);
|
|
ctx.closePath();
|
|
ctx.fill();
|
|
}
|
|
});
|
|
}
|
|
|
|
handleClick(e) {
|
|
const rect = this.canvas.getBoundingClientRect();
|
|
const x = e.clientX - rect.left;
|
|
const y = e.clientY - rect.top;
|
|
|
|
console.log(`Map clicked at: ${x}, ${y}`);
|
|
|
|
// Check if clicked on a unit or building
|
|
// This is a simplified example - in a real game you'd have proper hit detection
|
|
this.showMapInfo(x, y);
|
|
}
|
|
|
|
handleMouseMove(e) {
|
|
const rect = this.canvas.getBoundingClientRect();
|
|
const x = e.clientX - rect.left;
|
|
const y = e.clientY - rect.top;
|
|
|
|
// Update cursor based on what's under it
|
|
this.canvas.style.cursor = 'default';
|
|
|
|
// Check for interactive elements
|
|
if (this.isNearSettlement(x, y)) {
|
|
this.canvas.style.cursor = 'pointer';
|
|
}
|
|
}
|
|
|
|
isNearSettlement(x, y) {
|
|
const centerX = 400;
|
|
const centerY = 350;
|
|
const distance = Math.sqrt((x - centerX) ** 2 + (y - centerY) ** 2);
|
|
return distance < 100;
|
|
}
|
|
|
|
showMapInfo(x, y) {
|
|
const overlay = document.getElementById('rts-map-overlay');
|
|
if (overlay) {
|
|
if (this.isNearSettlement(x, y)) {
|
|
overlay.innerHTML = `
|
|
<div style="position: absolute; top: ${y + 10}px; left: ${x + 10}px;
|
|
background: rgba(0,0,0,0.8); color: white; padding: 8px;
|
|
border-radius: 4px; font-size: 12px; pointer-events: auto;">
|
|
<strong>Main Settlement</strong><br>
|
|
Population: 150<br>
|
|
Defense: High<br>
|
|
<button onclick="this.parentElement.remove()" style="margin-top: 4px;">Close</button>
|
|
</div>
|
|
`;
|
|
} else {
|
|
overlay.innerHTML = '';
|
|
}
|
|
}
|
|
}
|
|
|
|
zoomIn() {
|
|
this.zoom = Math.min(this.zoom * 1.2, 3);
|
|
this.redraw();
|
|
}
|
|
|
|
zoomOut() {
|
|
this.zoom = Math.max(this.zoom / 1.2, 0.5);
|
|
this.redraw();
|
|
}
|
|
|
|
centerMap() {
|
|
this.offsetX = 0;
|
|
this.offsetY = 0;
|
|
this.zoom = 1;
|
|
this.redraw();
|
|
}
|
|
|
|
redraw() {
|
|
this.ctx.save();
|
|
this.ctx.scale(this.zoom, this.zoom);
|
|
this.ctx.translate(this.offsetX, this.offsetY);
|
|
|
|
this.drawTerrain();
|
|
this.drawGrid();
|
|
|
|
this.ctx.restore();
|
|
}
|
|
|
|
updateFromGameState(gameState) {
|
|
// Update the map based on game state changes
|
|
console.log('Updating map from game state:', gameState);
|
|
this.redraw();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Legacy function for backward compatibility
|
|
* @param {HTMLElement} rootEl - The root element to append to (optional).
|
|
* @returns {HTMLCanvasElement}
|
|
*/
|
|
export function createMapCanvas(rootEl) {
|
|
const canvas = document.createElement('canvas');
|
|
canvas.id = 'rts-map-canvas';
|
|
canvas.width = 640;
|
|
canvas.height = 640;
|
|
|
|
const ctx = canvas.getContext('2d');
|
|
ctx.strokeStyle = '#444';
|
|
ctx.lineWidth = 1;
|
|
|
|
for (let i = 0; i <= canvas.width; i += 40) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(i, 0);
|
|
ctx.lineTo(i, canvas.height);
|
|
ctx.stroke();
|
|
}
|
|
|
|
for (let i = 0; i <= canvas.height; i += 40) {
|
|
ctx.beginPath();
|
|
ctx.moveTo(0, i);
|
|
ctx.lineTo(canvas.width, i);
|
|
ctx.stroke();
|
|
}
|
|
|
|
if (rootEl) {
|
|
rootEl.appendChild(canvas);
|
|
}
|
|
|
|
return canvas;
|
|
} |