通过本教程学习使用HTML5 Canvas和JavaScript创建完整的2D游戏
Canvas游戏开发基础
HTML5 Canvas是一个强大的元素,允许通过JavaScript动态绘制图形。它非常适合创建2D游戏、数据可视化和交互式动画。
专业提示: 使用Canvas时,双缓冲技术可以避免画面闪烁。在内存中绘制完整帧后再渲染到屏幕。
1. Canvas基本设置
首先在HTML中创建Canvas元素:
<canvas id="gameCanvas" width="800" height="600">
您的浏览器不支持Canvas,请升级浏览器
</canvas>
在JavaScript中获取上下文:
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
2. 游戏循环结构
所有游戏都需要一个主循环来更新游戏状态和重绘画面:
function gameLoop() {
// 1. 清除画布
ctx.clearRect(0, 0, canvas.width, canvas.height);
// 2. 更新游戏状态
updateGame();
// 3. 绘制游戏场景
drawGame();
// 4. 请求下一帧
requestAnimationFrame(gameLoop);
}
// 启动游戏循环
gameLoop();
3. 创建玩家飞船
使用路径绘制三角形作为飞船:
function drawShip(x, y) {
ctx.save();
ctx.translate(x, y);
// 绘制三角形飞船
ctx.beginPath();
ctx.moveTo(0, -15);
ctx.lineTo(10, 15);
ctx.lineTo(-10, 15);
ctx.closePath();
ctx.fillStyle = '#2ecc71';
ctx.fill();
ctx.strokeStyle = '#3498db';
ctx.lineWidth = 2;
ctx.stroke();
ctx.restore();
}
4. 处理用户输入
通过键盘事件控制飞船移动:
const keys = {};
window.addEventListener('keydown', e => {
keys[e.key] = true;
});
window.addEventListener('keyup', e => {
keys[e.key] = false;
});
function handleInput() {
if (keys['ArrowLeft']) ship.x -= 5;
if (keys['ArrowRight']) ship.x += 5;
if (keys['ArrowUp']) ship.y -= 5;
if (keys['ArrowDown']) ship.y += 5;
}
5. 添加敌人和碰撞检测
创建随机敌人并检测碰撞:
// 创建敌人
function createEnemy() {
return {
x: Math.random() * canvas.width,
y: -30,
radius: 15,
speed: Math.random() * 2 + 1
};
}
// 碰撞检测
function checkCollision(obj1, obj2) {
const dx = obj1.x - obj2.x;
const dy = obj1.y - obj2.y;
const distance = Math.sqrt(dx * dx + dy * dy);
return distance < obj1.radius + obj2.radius;
}
6. 添加粒子效果
使用粒子系统增强游戏体验:
class Particle {
constructor(x, y, color) {
this.x = x;
this.y = y;
this.color = color;
this.radius = Math.random() * 3 + 1;
this.speedX = Math.random() * 6 - 3;
this.speedY = Math.random() * 6 - 3;
this.alpha = 1;
}
update() {
this.x += this.speedX;
this.y += this.speedY;
this.alpha -= 0.01;
}
draw() {
ctx.save();
ctx.globalAlpha = this.alpha;
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
}
7. 完整游戏结构
游戏应包含以下核心模块:
- 游戏初始化
- 资源加载(图像、声音)
- 输入处理系统
- 游戏对象管理
- 物理和碰撞系统
- 渲染系统
- 游戏状态管理(菜单、游戏中、结束)
太空射击游戏演示
游戏说明
- 使用方向键移动飞船
- 空格键发射子弹
- 躲避陨石和敌人飞船
- 收集能量包恢复生命
- 每1000分提升一个等级
注意: 此演示仅包含核心游戏机制,完整游戏可扩展添加音效、更多敌人类型、BOSS战和升级系统。
// Canvas游戏实现
const canvas = document.getElementById(‘gameCanvas’);
const ctx = canvas.getContext(‘2d’);
const scoreElement = document.getElementById(‘score’);
const livesElement = document.getElementById(‘lives’);
const levelElement = document.getElementById(‘level’);
const startBtn = document.getElementById(‘startBtn’);
const pauseBtn = document.getElementById(‘pauseBtn’);
const resetBtn = document.getElementById(‘resetBtn’);
// 游戏状态
const gameState = {
score: 0,
lives: 3,
level: 1,
isRunning: false,
isPaused: false
};
// 玩家飞船
const ship = {
x: canvas.width / 2,
y: canvas.height – 50,
width: 30,
height: 40,
speed: 6,
color: ‘#2ecc71’,
bullets: []
};
// 游戏对象
let enemies = [];
let particles = [];
let powerUps = [];
let stars = [];
// 键盘状态
const keys = {};
// 初始化星星背景
function initStars() {
stars = [];
for (let i = 0; i {
ctx.beginPath();
ctx.arc(star.x, star.y, star.size, 0, Math.PI * 2);
ctx.fill();
});
}
// 更新星星位置
function updateStars() {
stars.forEach(star => {
star.y += star.speed;
if (star.y > canvas.height) {
star.y = 0;
star.x = Math.random() * canvas.width;
}
});
}
// 绘制玩家飞船
function drawShip() {
ctx.save();
ctx.translate(ship.x, ship.y);
// 飞船主体
ctx.beginPath();
ctx.moveTo(0, -15);
ctx.lineTo(10, 15);
ctx.lineTo(-10, 15);
ctx.closePath();
ctx.fillStyle = ship.color;
ctx.fill();
ctx.strokeStyle = ‘#3498db’;
ctx.lineWidth = 2;
ctx.stroke();
// 飞船引擎
ctx.beginPath();
ctx.moveTo(-6, 15);
ctx.lineTo(0, 25);
ctx.lineTo(6, 15);
ctx.strokeStyle = ‘#e74c3c’;
ctx.lineWidth = 2;
ctx.stroke();
ctx.restore();
}
// 绘制敌人
function drawEnemies() {
enemies.forEach(enemy => {
ctx.save();
ctx.translate(enemy.x, enemy.y);
// 敌人主体
ctx.beginPath();
ctx.arc(0, 0, enemy.radius, 0, Math.PI * 2);
ctx.fillStyle = enemy.color;
ctx.fill();
ctx.strokeStyle = ‘#c0392b’;
ctx.lineWidth = 2;
ctx.stroke();
// 敌人特征
ctx.beginPath();
ctx.arc(0, 0, enemy.radius * 0.6, 0, Math.PI * 2);
ctx.strokeStyle = ‘#e74c3c’;
ctx.stroke();
ctx.restore();
});
}
// 绘制子弹
function drawBullets() {
ship.bullets.forEach(bullet => {
ctx.save();
ctx.translate(bullet.x, bullet.y);
ctx.beginPath();
ctx.rect(-2, -10, 4, 20);
ctx.fillStyle = ‘#f1c40f’;
ctx.fill();
ctx.strokeStyle = ‘#e67e22’;
ctx.lineWidth = 1;
ctx.stroke();
ctx.restore();
});
}
// 绘制粒子
function drawParticles() {
particles.forEach(particle => {
ctx.save();
ctx.globalAlpha = particle.alpha;
ctx.fillStyle = particle.color;
ctx.beginPath();
ctx.arc(particle.x, particle.y, particle.radius, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
});
}
// 绘制能量包
function drawPowerUps() {
powerUps.forEach(powerUp => {
ctx.save();
ctx.translate(powerUp.x, powerUp.y);
ctx.beginPath();
ctx.rect(-10, -10, 20, 20);
ctx.fillStyle = ‘#9b59b6’;
ctx.fill();
ctx.strokeStyle = ‘#8e44ad’;
ctx.lineWidth = 2;
ctx.stroke();
ctx.fillStyle = ‘#ffffff’;
ctx.font = ‘bold 16px Arial’;
ctx.textAlign = ‘center’;
ctx.textBaseline = ‘middle’;
ctx.fillText(‘+’, 0, 0);
ctx.restore();
});
}
// 创建敌人
function createEnemy() {
const radius = Math.random() * 15 + 10;
return {
x: Math.random() * (canvas.width – radius * 2) + radius,
y: -radius,
radius: radius,
speed: Math.random() * 2 + 1,
color: `hsl(${Math.random() * 360}, 70%, 60%)`
};
}
// 创建粒子
function createParticles(x, y, color, count) {
for (let i = 0; i < count; i++) {
particles.push({
x: x,
y: y,
color: color,
radius: Math.random() * 3 + 1,
speedX: Math.random() * 6 – 3,
speedY: Math.random() * 6 – 3,
alpha: 1
});
}
}
// 创建能量包
function createPowerUp(x, y) {
powerUps.push({
x: x,
y: y,
width: 20,
height: 20,
speed: 2
});
}
// 发射子弹
function fireBullet() {
ship.bullets.push({
x: ship.x,
y: ship.y – 20,
width: 4,
height: 20,
speed: 10
});
}
// 碰撞检测
function checkCollision(obj1, obj2) {
const dx = obj1.x – obj2.x;
const dy = obj1.y – obj2.y;
const distance = Math.sqrt(dx * dx + dy * dy);
return distance ship.width/2) ship.x -= ship.speed;
if (keys[‘ArrowRight’] && ship.x ship.height/2) ship.y -= ship.speed;
if (keys[‘ArrowDown’] && ship.y < canvas.height – ship.height/2) ship.y += ship.speed;
// 生成敌人
if (Math.random() {
enemy.y += enemy.speed;
// 检测敌人与玩家碰撞
if (checkCollision(ship, enemy)) {
createParticles(enemy.x, enemy.y, enemy.color, 20);
enemies.splice(index, 1);
gameState.lives–;
livesElement.textContent = gameState.lives;
return;
}
// 移除超出屏幕的敌人
if (enemy.y > canvas.height + enemy.radius) {
enemies.splice(index, 1);
}
});
// 更新子弹位置
ship.bullets.forEach((bullet, bIndex) => {
bullet.y -= bullet.speed;
// 检测子弹与敌人碰撞
enemies.forEach((enemy, eIndex) => {
if (checkCollision(bullet, enemy)) {
createParticles(enemy.x, enemy.y, enemy.color, 20);
enemies.splice(eIndex, 1);
ship.bullets.splice(bIndex, 1);
gameState.score += 100;
scoreElement.textContent = gameState.score;
// 随机生成能量包
if (Math.random() < 0.3) {
createPowerUp(enemy.x, enemy.y);
}
// 更新等级
gameState.level = Math.floor(gameState.score / 1000) + 1;
levelElement.textContent = gameState.level;
return;
}
});
// 移除超出屏幕的子弹
if (bullet.y {
powerUp.y += powerUp.speed;
// 检测能量包与玩家碰撞
if (checkCollision(ship, powerUp)) {
powerUps.splice(index, 1);
gameState.lives = Math.min(gameState.lives + 1, 5);
livesElement.textContent = gameState.lives;
return;
}
// 移除超出屏幕的能量包
if (powerUp.y > canvas.height) {
powerUps.splice(index, 1);
}
});
// 更新粒子
particles.forEach((particle, index) => {
particle.x += particle.speedX;
particle.y += particle.speedY;
particle.alpha -= 0.02;
if (particle.alpha <= 0) {
particles.splice(index, 1);
}
});
// 更新星星背景
updateStars();
// 检查游戏结束
if (gameState.lives {
if (!gameState.isRunning) {
initGame();
}
gameState.isPaused = false;
});
pauseBtn.addEventListener(‘click’, () => {
gameState.isPaused = !gameState.isPaused;
});
resetBtn.addEventListener(‘click’, initGame);
window.addEventListener(‘keydown’, e => {
keys[e.key] = true;
// 空格键发射子弹
if (e.key === ‘ ‘ && gameState.isRunning && !gameState.isPaused) {
fireBullet();
}
});
window.addEventListener(‘keyup’, e => {
keys[e.key] = false;
});
// 启动游戏循环
initStars();
gameLoop();