使用JavaScript和Canvas的2D坦克大战游戏
来自 Wikiprompt,自由的提示词百科全书
使用JavaScript和Canvas的2D坦克大战游戏 一个全面的提示,用于使用JavaScript和HTML5 Canvas生成一个功能完整的2D坦克对战游戏,包含对游戏玩法、地形、AI和自定义的详细规格说明。
提示词内容收藏
🌐
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
let gridSize = 36;
let tileSize = Math.floor(Math.min(window.innerWidth, window.innerHeight) / gridSize);
canvas.width = tileSize * gridSize;
canvas.height = tileSize * gridSize;
window.addEventListener('resize', () => {
tileSize = Math.floor(Math.min(window.innerWidth, window.innerHeight) / gridSize);
canvas.width = tileSize * gridSize;
canvas.height = tileSize * gridSize;
drawMap();
});
let map = [];
let player = {};
let enemies = [];
let bullets = [];
let explosions = [];
let keys = {};
let touchStart = null;
let lastShot = 0;
let score = 0;
let lives = 3;
let level = 1;
let gameOver = false;
let gameWin = false;
let enemyCount = 5;
let playerColor = '#4CAF50';
let highScore = localStorage.getItem('tankHighScore') || 0;
let gameStarted = false;
let gamePaused = false;
const terrainTypes = {
solid: 0,
breakable: 1,
water: 2,
mud: 3,
ground: 4
};
const enemyColors = {
aggressive: '#FF5722',
defensive: '#2196F3',
patrolling: '#FFC107'
};
function generateMap() {
map = [];
for (let i = 0; i < gridSize; i++) {
map[i] = [];
for (let j = 0; j < gridSize; j++) {
if (i === 0 || j === 0 || i === gridSize - 1 || j === gridSize - 1) {
map[i][j] = terrainTypes.solid;
} else if (Math.random() < 0.1) {
map[i][j] = terrainTypes.breakable;
} else if (Math.random() < 0.05) {
map[i][j] = terrainTypes.water;
} else if (Math.random() < 0.05) {
map[i][j] = terrainTypes.mud;
} else {
map[i][j] = terrainTypes.ground;
}
}
}
}
function drawMap() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < gridSize; i++) {
for (let j = 0; j < gridSize; j++) {
const x = j * tileSize;
const y = i * tileSize;
switch (map[i][j]) {
case terrainTypes.solid:
ctx.fillStyle = '#333';
ctx.fillRect(x, y, tileSize, tileSize);
ctx.strokeStyle = '#555';
ctx.strokeRect(x, y, tileSize, tileSize);
break;
case terrainTypes.breakable:
ctx.fillStyle = '#8B4513';
ctx.fillRect(x, y, tileSize, tileSize);
ctx.strokeStyle = '#A0522D';
ctx.strokeRect(x, y, tileSize, tileSize);
break;
case terrainTypes.water:
ctx.fillStyle = '#1E90FF';
ctx.fillRect(x, y, tileSize, tileSize);
ctx.strokeStyle = '#4682B4';
ctx.strokeRect(x, y, tileSize, tileSize);
break;
case terrainTypes.mud:
ctx.fillStyle = '#8B7355';
ctx.fillRect(x, y, tileSize, tileSize);
ctx.strokeStyle = '#A0522D';
ctx.strokeRect(x, y, tileSize, tileSize);
break;
default:
ctx.fillStyle = '#2E8B57';
ctx.fillRect(x, y, tileSize, tileSize);
ctx.strokeStyle = '#3CB371';
ctx.strokeRect(x, y, tileSize, tileSize);
}
}
}
}
function createPlayer() {
player = {
x: Math.floor(gridSize / 2),
y: gridSize - 2,
direction: 'up',
color: playerColor,
cooldown: 0,
speed: 1,
lives: 3
};
}
function createEnemies(count) {
enemies = [];
for (let i = 0; i < count; i++) {
let x, y;
do {
x = Math.floor(Math.random() * (gridSize - 2)) + 1;
y = Math.floor(Math.random() * (gridSize - 2)) + 1;
} while (map[y][x] !== terrainTypes.ground || (Math.abs(x - player.x) < 5 && Math.abs(y - player.y) < 5));
const behavior = ['aggressive', 'defensive', 'patrolling'][Math.floor(Math.random() * 3)];
enemies.push({
x: x,
y: y,
direction: ['up', 'down', 'left', 'right'][Math.floor(Math.random() * 4)],
color: enemyColors[behavior],
behavior: behavior,
cooldown: 0,
speed: 0.5 + level * 0.1,
alive: true
});
}
}
function drawTank(tank, isPlayer) {
const x = tank.x * tileSize;
const y = tank.y * tileSize;
ctx.fillStyle = tank.color;
ctx.fillRect(x + tileSize * 0.2, y + tileSize * 0.2, tileSize * 0.6, tileSize * 0.6);
ctx.fillStyle = '#000';
ctx.fillRect(x + tileSize * 0.4, y + tileSize * 0.4, tileSize * 0.2, tileSize * 0.2);
ctx.fillStyle = '#FFF';
ctx.fillRect(x + tileSize * 0.45, y + tileSize * 0.45, tileSize * 0.1, tileSize * 0.1);
ctx.strokeStyle = '#000';
ctx.lineWidth = 2;
ctx.strokeRect(x + tileSize * 0.2, y + tileSize * 0.2, tileSize * 0.6, tileSize * 0.6);
ctx.beginPath();
ctx.moveTo(x + tileSize / 2, y + tileSize / 2);
switch (tank.direction) {
case 'up':
ctx.lineTo(x + tileSize / 2, y);
break;
case 'down':
ctx.lineTo(x + tileSize / 2, y + tileSize);
break;
case 'left':
ctx.lineTo(x, y + tileSize / 2);
break;
case 'right':
ctx.lineTo(x + tileSize, y + tileSize / 2);
break;
}
ctx.stroke();
}
function drawBullets() {
bullets.forEach(bullet => {
ctx.fillStyle = '#FFD700';
ctx.beginPath();
ctx.arc(bullet.x * tileSize + tileSize / 2, bullet.y * tileSize + tileSize / 2, tileSize * 0.2, 0, Math.PI * 2);
ctx.fill();
});
}
function drawExplosions() {
explosions.forEach(explosion => {
ctx.fillStyle = `rgba(255, 165, 0, ${explosion.alpha})`;
ctx.beginPath();
ctx.arc(explosion.x * tileSize + tileSize / 2, explosion.y * tileSize + tileSize / 2, explosion.radius * tileSize, 0, Math.PI * 2);
ctx.fill();
});
}
function updatePlayer() {
if (gameOver || gameWin || !gameStarted || gamePaused) return;
let dx = 0, dy = 0;
if (keys['ArrowUp']) { dy = -1; player.direction = 'up'; }
if (keys['ArrowDown']) { dy = 1; player.direction = 'down'; }
if (keys['ArrowLeft']) { dx = -1; player.direction = 'left'; }
if (keys['ArrowRight']) { dx = 1; player.direction = 'right'; }
if (dx !== 0 || dy !== 0) {
const newX = player.x + dx;
const newY = player.y + dy;
if (newX >= 0 && newX < gridSize && newY >= 0 && newY < gridSize && map[newY][newX] !== terrainTypes.solid && map[newY][newX] !== terrainTypes.water) {
if (map[newY][newX] === terrainTypes.mud) {
player.x += dx * 0.5;
player.y += dy * 0.5;
} else {
player.x = newX;
player.y = newY;
}
}
}
if (keys[' '] && Date.now() - lastShot > 2000) {
shoot(player);
lastShot = Date.now();
}
}
function updateEnemies() {
if (gameOver || gameWin || !gameStarted || gamePaused) return;
enemies.forEach(enemy => {
if (!enemy.alive) return;
let dx = 0, dy = 0;
if (enemy.behavior === 'aggressive') {
const diffX = player.x - enemy.x;
const diffY = player.y - enemy.y;
if (Math.abs(diffX) > Math.abs(diffY)) {
dx = Math.sign(diffX);
enemy.direction = dx > 0 ? 'right' : 'left';
} else {
dy = Math.sign(diffY);
enemy.direction = dy > 0 ? 'down' : 'up';
}
} else if (enemy.behavior === 'defensive') {
if (Math.random() < 0.3) {
const dir = ['up', 'down', 'left', 'right'][Math.floor(Math.random() * 4)];
enemy.direction = dir;
}
switch (enemy.direction) {
case 'up': dy = -1; break;
case 'down': dy = 1; break;
case 'left': dx = -1; break;
case 'right': dx = 1; break;
}
} else {
if (Math.random() < 0.1) {
const dir = ['up', 'down', 'left', 'right'][Math.floor(Math.random() * 4)];
enemy.direction = dir;
}
switch (enemy.direction) {
case 'up': dy = -1; break;
case 'down': dy = 1; break;
case 'left': dx = -1; break;
case 'right': dx = 1; break;
}
}
const newX = enemy.x + dx * enemy.speed;
const newY = enemy.y + dy * enemy.speed;
if (newX >= 0 && newX < gridSize && newY >= 0 && newY < gridSize && map[Math.floor(newY)][Math.floor(newX)] !== terrainTypes.solid && map[Math.floor(newY)][Math.floor(newX)] !== terrainTypes.water) {
enemy.x = newX;
enemy.y = newY;
}
if (Date.now() - enemy.cooldown > 2000) {
shoot(enemy);
enemy.cooldown = Date.now();
}
});
}
function shoot(tank) {
let dx = 0, dy = 0;
switch (tank.direction) {
case 'up': dy = -1; break;
case 'down': dy = 1; break;
case 'left': dx = -1; break;
case 'right': dx = 1; break;
}
bullets.push({
x: tank.x + dx,
y: tank.y + dy,
dx: dx,
dy: dy,
owner: tank === player ? 'player' : 'enemy'
});
}
function updateBullets() {
bullets.forEach((bullet, index) => {
bullet.x += bullet.dx;
bullet.y += bullet.dy;
if (bullet.x < 0 || bullet.x >= gridSize || bullet.y < 0 || bullet.y >= gridSize) {
bullets.splice(index, 1);
return;
}
if (map[Math.floor(bullet.y)][Math.floor(bullet.x)] === terrainTypes.breakable) {
map[Math.floor(bullet.y)][Math.floor(bullet.x)] = terrainTypes.ground;
bullets.splice(index, 1);
return;
}
if (map[Math.floor(bullet.y)][Math.floor(bullet.x)] === terrainTypes.solid) {
bullets.splice(index, 1);
return;
}
if (bullet.owner === 'player') {
enemies.forEach(enemy => {
if (enemy.alive && Math.floor(enemy.x) === Math.floor(bullet.x) && Math.floor(enemy.y) === Math.floor(bullet.y)) {
enemy.alive = false;
score += 100;
explosions.push({ x: bullet.x, y: bullet.y, radius: 1, alpha: 1 });
bullets.splice(index, 1);
}
});
} else {
if (Math.floor(player.x) === Math.floor(bullet.x) && Math.floor(player.y) === Math.floor(bullet.y)) {
lives--;
player.lives = lives;
explosions.push({ x: bullet.x, y: bullet.y, radius: 1, alpha: 1 });
bullets.splice(index, 1);
if (lives <= 0) {
gameOver = true;
}
}
}
});
}
function updateExplosions() {
explosions.forEach((explosion, index) => {
explosion.alpha -= 0.05;
explosion.radius += 0.1;
if (explosion.alpha <= 0) {
explosions.splice(index, 1);
}
});
}
function checkWin() {
if (enemies.every(enemy => !enemy.alive)) {
gameWin = true;
score += Math.max(0, 1000 - Math.floor(Date.now() / 1000));
if (score > highScore) {
highScore = score;
localStorage.setItem('tankHighScore', highScore);
}
}
}
function drawUI() {
ctx.fillStyle = '#FFF';
ctx.font = '20px Arial';
ctx.fillText(`Score: ${score}`, 10, 30);
ctx.fillText(`Lives: ${lives}`, 10, 60);
ctx.fillText(`Level: ${level}`, 10, 90);
ctx.fillText(`High Score: ${highScore}`, 10, 120);
}
function drawGameOver() {
ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#FFF';
ctx.font = '40px Arial';
ctx.textAlign = 'center';
ctx.fillText('GAME OVER', canvas.width / 2, canvas.height / 2 - 20);
ctx.font = '20px Arial';
ctx.fillText(`Score: ${score}`, canvas.width / 2, canvas.height / 2 + 30);
ctx.fillText('Press R to restart', canvas.width / 2, canvas.height / 2 + 60);
ctx.textAlign = 'left';
}
function drawWin() {
ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#FFF';
ctx.font = '40px Arial';
ctx.textAlign = 'center';
ctx.fillText('YOU WIN!', canvas.width / 2, canvas.height / 2 - 20);
ctx.font = '20px Arial';
ctx.fillText(`Score: ${score}`, canvas.width / 2, canvas.height / 2 + 30);
ctx.fillText('Press R to restart', canvas.width / 2, canvas.height / 2 + 60);
ctx.textAlign = 'left';
}
function drawStartScreen() {
ctx.fillStyle = 'rgba(0, 0, 0, 0.8)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = '#FFF';
ctx.font = '40px Arial';
ctx.textAlign = 'center';
ctx.fillText('TANK BATTLE', canvas.width / 2, canvas.height / 2 - 100);
ctx.font = '20px Arial';
ctx.fillText('Arrow Keys / Swipe to move', canvas.width / 2, canvas.height / 2 - 40);
ctx.fillText('Space / Tap to shoot', canvas.width / 2, canvas.height / 2);
ctx.fillText('Press Enter to start', canvas.width / 2, canvas.height / 2 + 40);
ctx.fillText('Enemies: ' + enemyCount, canvas.width / 2, canvas.height / 2 + 80);
ctx.fillText('Press +/- to change enemy count', canvas.width / 2, canvas.height / 2 + 110);
ctx.fillText('Press C to change color', canvas.width / 2, canvas.height / 2 + 140);
ctx.textAlign = 'left';
}
function gameLoop() {
drawMap();
updatePlayer();
updateEnemies();
updateBullets();
updateExplosions();
checkWin();
drawTank(player, true);
enemies.forEach(enemy => {
if (enemy.alive) drawTank(enemy, false);
});
drawBullets();
drawExplosions();
drawUI();
if (gameOver) drawGameOver();
if (gameWin) drawWin();
if (!gameStarted) drawStartScreen();
requestAnimationFrame(gameLoop);
}
document.addEventListener('keydown', (e) => {
keys[e.key] = true;
if (e.key === 'Enter' && !gameStarted) {
gameStarted = true;
generateMap();
createPlayer();
createEnemies(enemyCount);
}
if (e.key === 'r' && (gameOver || gameWin)) {
gameOver = false;
gameWin = false;
lives = 3;
score = 0;
level = 1;
generateMap();
createPlayer();
createEnemies(enemyCount);
}
if (e.key === '+' && !gameStarted) {
enemyCount = Math.min(10, enemyCount + 1);
}
if (e.key === '-' && !gameStarted) {
enemyCount = Math.max(1, enemyCount - 1);
}
if (e.key === 'c' && !gameStarted) {
const colors = ['#4CAF50', '#FF5722', '#2196F3', '#FFC107', '#9C27B0'];
playerColor = colors[Math.floor(Math.random() * colors.length)];
}
});
document.addEventListener('keyup', (e) => {
keys[e.key] = false;
});
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
touchStart = { x: e.touches[0].clientX, y: e.touches[0].clientY };
if (!gameStarted) {
gameStarted = true;
generateMap();
createPlayer();
createEnemies(enemyCount);
}
});
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
if (touchStart) {
const dx = e.touches[0].clientX - touchStart.x;
const dy = e.touches[0].clientY - touchStart.y;
if (Math.abs(dx) > Math.abs(dy)) {
keys['ArrowRight'] = dx > 0;
keys['ArrowLeft'] = dx < 0;
keys['ArrowUp'] = false;
keys['ArrowDown'] = false;
} else {
keys['ArrowDown'] = dy > 0;
keys['ArrowUp'] = dy < 0;
keys['ArrowLeft'] = false;
keys['ArrowRight'] = false;
}
touchStart = { x: e.touches[0].clientX, y: e.touches[0].clientY };
}
});
canvas.addEventListener('touchend', (e) => {
e.preventDefault();
keys['ArrowUp'] = false;
keys['ArrowDown'] = false;
keys['ArrowLeft'] = false;
keys['ArrowRight'] = false;
if (gameStarted && !gameOver && !gameWin) {
shoot(player);
}
touchStart = null;
});
generateMap();
createPlayer();
createEnemies(enemyCount);
gameLoop();
用法
此提示词专为 coding 设计。复制上方内容并粘贴到你常用的 AI 工具中。
为获得最佳效果,可将占位符(方括号或大写字母标示)替换为你的具体需求。
讨论
0 条评论