HTML5 Canvas 星空战机游戏开发全解析 - 教程

HTML5 Canvas 星空战机游戏开发全解析

一、游戏介绍

这是一款基于HTML5 Canvas开发的2D射击游戏,具有以下特色功能:

  • 纯代码绘制的星空动态背景
  • ✈️ 三种不同特性的敌人类型
  • 键盘控制的玩家战机
  • 完整的分数统计和排行榜系统
  • ⚙️ 三种难度可选
    在这里插入图片描述

二、核心代码解析

1. 游戏初始化

const canvas = document.getElementById('gameCanvas'
)
;
const ctx = canvas.getContext('2d'
)
;
let game = {
// 游戏状态对象
stars: []
, // 星星数组
bullets: []
, // 玩家子弹
enemies: []
, // 敌人数组
score: 0
, // 当前分数
level: 1
, // 当前关卡
player: {
// 玩家对象
x: canvas.width/2
,
y: canvas.height-100
,
width: 40
,
health: 100
}
}
;

2. 游戏主循环

function gameLoop(
) {
// 1. 清空画布
ctx.clearRect(0
, 0
, canvas.width, canvas.height)
;
// 2. 更新游戏状态
updateGame(
)
;
// 3. 绘制所有元素
drawBackground(
)
;
drawPlayer(
)
;
drawEnemies(
)
;
drawBullets(
)
;
// 4. 请求下一帧
requestAnimationFrame(gameLoop)
;
}

3. 玩家控制实现

// 键盘事件监听
const keys = {
}
;
window.addEventListener('keydown'
, e => keys[e.key] = true
)
;
window.addEventListener('keyup'
, e => keys[e.key] = false
)
;
// 玩家移动逻辑
function movePlayer(
) {
if (keys['ArrowLeft']
) {
// 左移限制:不能超出左边界
game.player.x = Math.max(game.player.width/2
, game.player.x - 8
)
;
}
if (keys['ArrowRight']
) {
// 右移限制:不能超出右边界
game.player.x = Math.min(canvas.width-game.player.width/2
, game.player.x + 8
)
;
}
if (keys[' ']
) {
// 空格键射击
fireBullet(
)
;
}
}

三、关键技术点

1. 敌人系统设计

敌人类型速度生命值分数特性
基础型3110普通移动
快速型5120高速移动
坦克型2550高生命值

2. 碰撞检测优化

采用圆形碰撞检测算法:

function checkCollision(bullet, enemy
) {
const dx = bullet.x - enemy.x;
const dy = bullet.y - enemy.y;
const distance = Math.sqrt(dx * dx + dy * dy)
;
return distance <
(bullet.radius + enemy.width/2
)
;
}

3. 排行榜实现

使用localStorage存储数据:

function saveHighScore(name, score
) {
const scores = JSON.parse(localStorage.getItem('highScores'
)
) || []
;
scores.push({
name, score
}
)
;
scores.sort((a, b
) => b.score - a.score)
;
localStorage.setItem('highScores'
, JSON.stringify(scores.slice(0
, 10
)
)
)
;
}

在这里插入图片描述

四、完整代码

<!DOCTYPE html>
  <html>
    <head>
    <title>星空战机</title>
      <style>
        body {
        margin: 0;
        overflow: hidden;
        font-family: Arial, sans-serif;
        }
        canvas {
        display: block;
        }
        #ui {
        position: absolute;
        top: 20px;
        left: 20px;
        color: white;
        font-size: 16px;
        text-shadow: 2px 2px 4px rgba(0,0,0,0.5)
        ;
        }
        #menu {
        position: absolute;
        top: 50%;
        left: 50%;
        transform: translate(-50%, -50%)
        ;
        background: rgba(0, 0, 0, 0.8)
        ;
        padding: 20px;
        border-radius: 10px;
        color: white;
        text-align: center;
        }
        button {
        padding: 10px 20px;
        margin: 10px;
        font-size: 16px;
        cursor: pointer;
        border: none;
        border-radius: 5px;
        background: #3498db;
        color: white;
        }
      </style>
    </head>
    <body>
    <canvas id="gameCanvas"></canvas>
    <div id="ui"></div>
        <div id="menu">
      <h1>星空战机</h1>
      <p>选择难度:</p>
          <button onclick="startGame('easy'
      )">简单</button>
        <button onclick="startGame('normal'
    )">普通</button>
      <button onclick="startGame('hard'
  )">困难</button>
<div id="highScores"></div>
</div>
<script>
  // 初始化
  const canvas = document.getElementById('gameCanvas'
  )
  ;
  const ctx = canvas.getContext('2d'
  )
  ;
  const ui = document.getElementById('ui'
  )
  ;
  const menu = document.getElementById('menu'
  )
  ;
  const highScores = document.getElementById('highScores'
  )
  ;
  let game = {
  stars: []
  ,
  bullets: []
  ,
  enemyBullets: []
  ,
  enemies: []
  ,
  powerups: []
  ,
  explosions: []
  ,
  score: 0
  ,
  level: 1
  ,
  difficulty: 'normal'
  ,
  player: {
  x: 0
  , // 初始位置会在 startGame 中设置
  y: 0
  , // 初始位置会在 startGame 中设置
  width: 40
  ,
  height: 60
  ,
  speed: 8
  ,
  canShoot: true
  ,
  health: 100
  ,
  weapon: 'normal'
  ,
  shield: 0
  }
  ,
  running: false
  }
  ;
  // 调整画布大小
  function resizeCanvas(
  ) {
  canvas.width = window.innerWidth;
  canvas.height = window.innerHeight;
  if (game.running) {
  // 如果游戏运行中调整画布大小,重置玩家位置
  game.player.x = canvas.width / 2
  ;
  game.player.y = canvas.height - 100
  ;
  }
  }
  resizeCanvas(
  )
  ;
  window.addEventListener('resize'
  , resizeCanvas)
  ;
  // 显示高分榜
  function showHighScores(
  ) {
  const scores = JSON.parse(localStorage.getItem('highScores'
  )
  ) || []
  ;
highScores.innerHTML = '<h2>高分榜</h2>' +
  scores.map((score, i
  ) =>
  `<div>
    ${i + 1
    }. ${score.name
    }: ${score.score
  }</div>
  `
  ).join(''
  )
  ;
  }
  // 开始游戏
  function startGame(difficulty
  ) {
  game = {
  ...game,
  stars: []
  ,
  bullets: []
  ,
  enemyBullets: []
  ,
  enemies: []
  ,
  powerups: []
  ,
  explosions: []
  ,
  score: 0
  ,
  level: 1
  ,
  difficulty,
  player: {
  ...game.player,
  x: canvas.width / 2
  , // 初始位置在画布中央
  y: canvas.height - 100
  , // 初始位置在画布底部
  health: difficulty === 'easy' ? 150 : difficulty === 'normal' ? 100 : 75
  ,
  weapon: 'normal'
  ,
  shield: 0
  }
  ,
  running: true
  }
  ;
  menu.style.display = 'none'
  ;
  createStars(
  )
  ;
  gameLoop(
  )
  ;
  }
  // 游戏结束
  function gameOver(
  ) {
  game.running = false
  ;
  const name = prompt('游戏结束!请输入你的名字:'
  )
  ;
  if (name) {
  saveHighScore(name, game.score)
  ;
  }
  menu.style.display = 'block'
  ;
  showHighScores(
  )
  ;
  }
  // 保存高分
  function saveHighScore(name, score
  ) {
  const scores = JSON.parse(localStorage.getItem('highScores'
  )
  ) || []
  ;
  scores.push({
  name, score
  }
  )
  ;
  scores.sort((a, b
  ) => b.score - a.score)
  ;
  localStorage.setItem('highScores'
  , JSON.stringify(scores.slice(0
  , 10
  )
  )
  )
  ;
  }
  // 生成星星
  function createStars(
  ) {
  for (
  let i = 0
  ; i <
  200
  ; i++
  ) {
  game.stars.push({
  x: Math.random(
  ) * canvas.width,
  y: Math.random(
  ) * canvas.height,
  size: Math.random(
  ) * 3
  ,
  alpha: Math.random(
  )
  }
  )
  ;
  }
  }
  // 绘制背景
  function drawBackground(
  ) {
  const gradient = ctx.createLinearGradient(0
  , 0
  , 0
  , canvas.height)
  ;
  gradient.addColorStop(0
  , "#000428"
  )
  ;
  gradient.addColorStop(1
  , "#004e92"
  )
  ;
  ctx.fillStyle = gradient;
  ctx.fillRect(0
  , 0
  , canvas.width, canvas.height)
  ;
  ctx.fillStyle = "white"
  ;
  game.stars.forEach(star =>
  {
  ctx.globalAlpha = star.alpha;
  ctx.beginPath(
  )
  ;
  ctx.arc(star.x, star.y, star.size, 0
  , Math.PI * 2
  )
  ;
  ctx.fill(
  )
  ;
  }
  )
  ;
  ctx.globalAlpha = 1
  ;
  }
  // 绘制玩家
  function drawPlayer(
  ) {
  // 护盾
  if (game.player.shield >
  0
  ) {
  ctx.strokeStyle = `rgba(0, 255, 255, ${game.player.shield / 100
  })`
  ;
  ctx.lineWidth = 2
  ;
  ctx.beginPath(
  )
  ;
  ctx.arc(game.player.x, game.player.y, 40
  , 0
  , Math.PI * 2
  )
  ;
  ctx.stroke(
  )
  ;
  }
  // 机身
  ctx.fillStyle = "#4a90e2"
  ;
  ctx.beginPath(
  )
  ;
  ctx.moveTo(game.player.x, game.player.y)
  ;
  ctx.lineTo(game.player.x - game.player.width / 2
  , game.player.y + game.player.height)
  ;
  ctx.lineTo(game.player.x + game.player.width / 2
  , game.player.y + game.player.height)
  ;
  ctx.closePath(
  )
  ;
  ctx.fill(
  )
  ;
  // 机翼
  ctx.fillStyle = "#2c3e50"
  ;
  ctx.fillRect(game.player.x - 25
  , game.player.y + 20
  , 50
  , 15
  )
  ;
  // 推进器火焰
  ctx.fillStyle = `hsl(${Math.random(
  ) * 30 + 30
  }, 100%, 50%)`
  ;
  ctx.beginPath(
  )
  ;
  ctx.ellipse(game.player.x, game.player.y + game.player.height + 5
  , 8
  , 15
  , 0
  , 0
  , Math.PI * 2
  )
  ;
  ctx.fill(
  )
  ;
  }
  // 生成敌人
  function createEnemy(
  ) {
  const enemyTypes = ['basic'
  , 'fast'
  , 'tank']
  ;
  const type = enemyTypes[Math.floor(Math.random(
  ) * enemyTypes.length)]
  ;
  game.enemies.push({
  x: Math.random(
  ) * canvas.width,
  y: -50
  ,
  width: 40
  ,
  height: 40
  ,
  speed: type === 'fast' ? 5 : type === 'tank' ? 2 : 3
  ,
  health: type === 'tank' ? 5 : 1
  ,
  type
  }
  )
  ;
  }
  // 敌人射击
  function enemyShoot(
  ) {
  game.enemies.forEach(enemy =>
  {
  if (Math.random(
  ) <
  0.02
  ) {
  game.enemyBullets.push({
  x: enemy.x,
  y: enemy.y + enemy.height / 2
  ,
  speed: 5
  }
  )
  ;
  }
  }
  )
  ;
  }
  // 碰撞检测
  function checkCollisions(
  ) {
  // 玩家子弹击中敌人
  game.bullets.forEach((bullet, bIndex
  ) =>
  {
  game.enemies.forEach((enemy, eIndex
  ) =>
  {
  if (bullet.x > enemy.x - enemy.width / 2 &&
  bullet.x < enemy.x + enemy.width / 2 &&
  bullet.y > enemy.y - enemy.height / 2 &&
  bullet.y < enemy.y + enemy.height / 2
  ) {
  game.score += 10
  ;
  game.bullets.splice(bIndex, 1
  )
  ;
  enemy.health -= 1
  ;
  if (enemy.health <= 0
  ) {
  game.enemies.splice(eIndex, 1
  )
  ;
  }
  }
  }
  )
  ;
  }
  )
  ;
  // 敌人子弹击中玩家
  game.enemyBullets.forEach((bullet, index
  ) =>
  {
  if (bullet.x > game.player.x - game.player.width / 2 &&
  bullet.x < game.player.x + game.player.width / 2 &&
  bullet.y > game.player.y - game.player.height / 2 &&
  bullet.y < game.player.y + game.player.height / 2
  ) {
  game.player.health -= 10
  ;
  game.enemyBullets.splice(index, 1
  )
  ;
  if (game.player.health <= 0
  ) {
  gameOver(
  )
  ;
  }
  }
  }
  )
  ;
  }
  // 更新关卡
  function updateLevel(
  ) {
  if (game.score >= game.level * 1000
  ) {
  game.level++
  ;
  }
  }
  // 游戏主循环
  function gameLoop(
  ) {
  if (!game.running)
  return
  ;
  // 更新游戏状态
  updateGame(
  )
  ;
  // 绘制游戏元素
  drawBackground(
  )
  ;
  drawPlayer(
  )
  ;
  drawBullets(
  )
  ;
  drawEnemies(
  )
  ;
  drawUI(
  )
  ;
  requestAnimationFrame(gameLoop)
  ;
  }
  // 更新游戏状态
  function updateGame(
  ) {
  // 更新星星位置
  game.stars.forEach(star =>
  {
  star.y += 2
  ;
  if (star.y > canvas.height) {
  star.y = 0
  ;
  star.x = Math.random(
  ) * canvas.width;
  }
  }
  )
  ;
  // 更新子弹位置
  game.bullets.forEach(bullet => bullet.y -= 10
  )
  ;
  game.bullets = game.bullets.filter(bullet => bullet.y >
  0
  )
  ;
  // 更新敌人子弹位置
  game.enemyBullets.forEach(bullet => bullet.y += bullet.speed)
  ;
  game.enemyBullets = game.enemyBullets.filter(bullet => bullet.y < canvas.height)
  ;
  // 更新敌人位置
  game.enemies.forEach(enemy => enemy.y += enemy.speed)
  ;
  game.enemies = game.enemies.filter(enemy => enemy.y < canvas.height + 50
  )
  ;
  // 生成敌人
  if (Math.random(
  ) <
  0.03
  ) {
  createEnemy(
  )
  ;
  }
  // 敌人射击
  enemyShoot(
  )
  ;
  // 碰撞检测
  checkCollisions(
  )
  ;
  // 更新关卡
  updateLevel(
  )
  ;
  }
  // 绘制UI
  function drawUI(
  ) {
  ui.innerHTML = `
  <div>分数: ${game.score
  }</div>
  <div>生命值: ${game.player.health
  }</div>
  <div>护盾: ${game.player.shield
  }</div>
  <div>武器: ${game.player.weapon
  }</div>
  <div>关卡: ${game.level
  }</div>
  
  `
  ;
  }
  // 绘制子弹
  function drawBullets(
  ) {
  ctx.fillStyle = "#ffdd57"
  ;
  game.bullets.forEach(bullet =>
  {
  ctx.beginPath(
  )
  ;
  ctx.arc(bullet.x, bullet.y, 5
  , 0
  , Math.PI * 2
  )
  ;
  ctx.fill(
  )
  ;
  }
  )
  ;
  ctx.fillStyle = "#e74c3c"
  ;
  game.enemyBullets.forEach(bullet =>
  {
  ctx.beginPath(
  )
  ;
  ctx.arc(bullet.x, bullet.y, 5
  , 0
  , Math.PI * 2
  )
  ;
  ctx.fill(
  )
  ;
  }
  )
  ;
  }
  // 绘制敌人
  function drawEnemies(
  ) {
  ctx.fillStyle = "#e74c3c"
  ;
  game.enemies.forEach(enemy =>
  {
  ctx.beginPath(
  )
  ;
  ctx.ellipse(enemy.x, enemy.y, enemy.width / 2
  , enemy.height / 2
  , 0
  , 0
  , Math.PI * 2
  )
  ;
  ctx.fill(
  )
  ;
  }
  )
  ;
  }
  // 键盘控制
  const keys = {
  }
  ;
  window.addEventListener('keydown'
  , e => keys[e.key] = true
  )
  ;
  window.addEventListener('keyup'
  , e => keys[e.key] = false
  )
  ;
  // 玩家移动
  function movePlayer(
  ) {
  if (keys['ArrowLeft'] && game.player.x > game.player.width / 2
  ) {
  game.player.x -= game.player.speed;
  }
  if (keys['ArrowRight'] && game.player.x < canvas.width - game.player.width / 2
  ) {
  game.player.x += game.player.speed;
  }
  if (keys[' '] && game.player.canShoot) {
  game.bullets.push({
  x: game.player.x, y: game.player.y
  }
  )
  ;
  game.player.canShoot = false
  ;
  setTimeout((
  ) => game.player.canShoot = true
  , 200
  )
  ;
  }
  }
  // 运行游戏
  setInterval(movePlayer, 1000 / 60
  )
  ;
  showHighScores(
  )
  ;
</script>
</body>
</html>

在这里插入图片描述

五、扩展方向

  1. 添加BOSS战系统
  2. 实现武器升级机制
  3. 加入粒子爆炸特效
  4. 添加背景音乐和音效
posted @ 2025-07-18 11:19  yfceshi  阅读(35)  评论(0)    收藏  举报