Electron 小游戏实战:太空打砖块(Space Breakout)

太空打砖块(Space Breakout)

  • [🕹️ Electron 小游戏实战:太空打砖块(Space Breakout)](#🕹️ Electron 小游戏实战:太空打砖块(Space Breakout))
    • 一、项目结构
    • 二、核心代码实现
      • [1. `main.js` ------ Electron 主进程](#1. main.js —— Electron 主进程)
      • [2. `preload.js` ------ 预加载脚本(可选,本例为空)](#2. preload.js —— 预加载脚本(可选,本例为空))
      • [3. `index.html` ------ 游戏主逻辑(Canvas + JS)](#3. index.html —— 游戏主逻辑(Canvas + JS))
    • 三、运行与打包
      • [1. 安装依赖](#1. 安装依赖)
      • [2. 添加启动脚本(package.json)](#2. 添加启动脚本(package.json))
      • [3. 启动游戏](#3. 启动游戏)
      • [4. (可选)打包为可执行文件](#4. (可选)打包为可执行文件)
    • 四、游戏玩法说明
    • 五、扩展建议
    • [六、为什么选择 Electron 做小游戏?](#六、为什么选择 Electron 做小游戏?)

🕹️ Electron 小游戏实战:太空打砖块(Space Breakout)

技术栈 :Electron + HTML5 Canvas + Vanilla JS
特点 :无框架、单 HTML 文件、支持键盘控制、计分系统、关卡重置
适用人群 :前端开发者、Electron 初学者、游戏编程爱好者
完成时间:约 15 分钟


一、项目结构

复制代码
space-breakout/
├── main.js          # Electron 主进程
├── preload.js       # 预加载脚本(安全上下文隔离)
└── index.html       # 游戏主页面(含 Canvas + JS 逻辑)

二、核心代码实现

1. main.js ------ Electron 主进程

js 复制代码
// main.js
const { app, BrowserWindow } = require('electron');
const path = require('path');

function createWindow() {
  const win = new BrowserWindow({
    width: 800,
    height: 600,
    backgroundColor: '#000',
    webPreferences: {
      preload: path.join(__dirname, 'preload.js'),
      contextIsolation: true,
      enableRemoteModule: false
    },
    autoHideMenuBar: true // 隐藏菜单栏,更像游戏
  });

  win.loadFile('index.html');
}

app.whenReady().then(createWindow);

app.on('window-all-closed', () => {
  if (process.platform !== 'darwin') app.quit();
});

2. preload.js ------ 预加载脚本(可选,本例为空)

js 复制代码
// preload.js
// 本游戏无需调用原生 API,故留空
// 若后续需添加保存分数到本地等功能,可在此暴露 ipcRenderer

3. index.html ------ 游戏主逻辑(Canvas + JS)

html 复制代码
<!DOCTYPE html>
<html>
<head>
  <meta charset="UTF-8">
  <title>Space Breakout</title>
  <style>
    body {
      margin: 0;
      padding: 20px;
      background: #000;
      color: white;
      font-family: 'Courier New', monospace;
      overflow: hidden;
      display: flex;
      flex-direction: column;
      align-items: center;
    }
    canvas {
      border: 2px solid #0f0;
      box-shadow: 0 0 20px rgba(0, 255, 0, 0.3);
    }
    #score {
      font-size: 24px;
      margin-top: 10px;
    }
    #instructions {
      margin-top: 10px;
      color: #0a0;
      font-size: 14px;
    }
  </style>
</head>
<body>
  <div id="score">Score: 0</div>
  <canvas id="gameCanvas" width="760" height="500"></canvas>
  <div id="instructions">← → 移动挡板 | 空格键重新开始</div>

  <script>
    const canvas = document.getElementById('gameCanvas');
    const ctx = canvas.getContext('2d');
    const scoreEl = document.getElementById('score');

    // 游戏状态
    let score = 0;
    let gameOver = false;

    // 挡板 (Paddle)
    const paddle = {
      width: 100,
      height: 15,
      x: canvas.width / 2 - 50,
      speed: 10
    };

    // 球 (Ball)
    const ball = {
      x: canvas.width / 2,
      y: canvas.height - 30,
      radius: 8,
      dx: 4,
      dy: -4,
      speed: 4
    };

    // 砖块 (Bricks)
    const brickRowCount = 5;
    const brickColumnCount = 10;
    const brickWidth = 70;
    const brickHeight = 20;
    const brickPadding = 10;
    const brickOffsetTop = 50;
    const brickOffsetLeft = 30;

    const bricks = [];
    for (let r = 0; r < brickRowCount; r++) {
      bricks[r] = [];
      for (let c = 0; c < brickColumnCount; c++) {
        bricks[r][c] = { x: 0, y: 0, status: 1 };
      }
    }

    // 键盘控制
    const keys = {};
    window.addEventListener('keydown', (e) => {
      keys[e.key] = true;
      // 空格键重启
      if (e.key === ' ' && gameOver) resetGame();
    });
    window.addEventListener('keyup', (e) => {
      keys[e.key] = false;
    });

    // 碰撞检测
    function collisionDetection() {
      for (let r = 0; r < brickRowCount; r++) {
        for (let c = 0; c < brickColumnCount; c++) {
          const b = bricks[r][c];
          if (b.status === 1) {
            if (
              ball.x + ball.radius > b.x &&
              ball.x - ball.radius < b.x + brickWidth &&
              ball.y + ball.radius > b.y &&
              ball.y - ball.radius < b.y + brickHeight
            ) {
              ball.dy = -ball.dy;
              b.status = 0;
              score += 10;
              scoreEl.textContent = `Score: ${score}`;

              // 检查胜利
              if (score === brickRowCount * brickColumnCount * 10) {
                setTimeout(() => {
                  alert('🎉 You Win!');
                  resetGame();
                }, 300);
              }
            }
          }
        }
      }
    }

    // 绘制挡板
    function drawPaddle() {
      ctx.beginPath();
      ctx.rect(paddle.x, canvas.height - paddle.height, paddle.width, paddle.height);
      ctx.fillStyle = '#0f0';
      ctx.fill();
      ctx.closePath();
    }

    // 绘制球
    function drawBall() {
      ctx.beginPath();
      ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
      ctx.fillStyle = '#fff';
      ctx.fill();
      ctx.closePath();
    }

    // 绘制砖块
    function drawBricks() {
      for (let r = 0; r < brickRowCount; r++) {
        for (let c = 0; c < brickColumnCount; c++) {
          if (bricks[r][c].status === 1) {
            const brickX = c * (brickWidth + brickPadding) + brickOffsetLeft;
            const brickY = r * (brickHeight + brickPadding) + brickOffsetTop;
            bricks[r][c].x = brickX;
            bricks[r][c].y = brickY;
            ctx.beginPath();
            ctx.rect(brickX, brickY, brickWidth, brickHeight);
            ctx.fillStyle = r % 2 === 0 ? '#f00' : '#00f';
            ctx.fill();
            ctx.closePath();
          }
        }
      }
    }

    // 更新球位置
    function moveBall() {
      ball.x += ball.dx;
      ball.y += ball.dy;

      // 墙壁反弹
      if (ball.x + ball.radius > canvas.width || ball.x - ball.radius < 0) {
        ball.dx = -ball.dx;
      }
      if (ball.y - ball.radius < 0) {
        ball.dy = -ball.dy;
      }

      // 挡板反弹
      if (
        ball.y + ball.radius > canvas.height - paddle.height &&
        ball.x > paddle.x &&
        ball.x < paddle.x + paddle.width
      ) {
        // 根据击中位置调整角度
        const hitPoint = (ball.x - (paddle.x + paddle.width / 2)) / (paddle.width / 2);
        ball.dx = hitPoint * ball.speed;
        ball.dy = -Math.abs(ball.dy);
      }

      // 掉落判定
      if (ball.y + ball.radius > canvas.height) {
        gameOver = true;
        setTimeout(() => {
          alert(`Game Over! Final Score: ${score}`);
          resetGame();
        }, 300);
      }
    }

    // 控制挡板
    function movePaddle() {
      if (keys['ArrowLeft'] && paddle.x > 0) {
        paddle.x -= paddle.speed;
      }
      if (keys['ArrowRight'] && paddle.x < canvas.width - paddle.width) {
        paddle.x += paddle.speed;
      }
    }

    // 重置游戏
    function resetGame() {
      score = 0;
      gameOver = false;
      scoreEl.textContent = `Score: ${score}`;
      ball.x = canvas.width / 2;
      ball.y = canvas.height - 30;
      ball.dx = 4;
      ball.dy = -4;
      paddle.x = canvas.width / 2 - 50;

      // 重置砖块
      for (let r = 0; r < brickRowCount; r++) {
        for (let c = 0; c < brickColumnCount; c++) {
          bricks[r][c].status = 1;
        }
      }
    }

    // 主循环
    function gameLoop() {
      if (gameOver) return;

      ctx.clearRect(0, 0, canvas.width, canvas.height);
      drawBricks();
      drawPaddle();
      drawBall();
      collisionDetection();
      moveBall();
      movePaddle();

      requestAnimationFrame(gameLoop);
    }

    // 启动游戏
    resetGame();
    gameLoop();
  </script>
</body>
</html>

三、运行与打包

1. 安装依赖

bash 复制代码
npm init -y
npm install electron --save-dev

2. 添加启动脚本(package.json)

json 复制代码
{
  "name": "space-breakout",
  "main": "main.js",
  "scripts": {
    "start": "electron .",
    "package-win": "electron-packager . SpaceBreakout --platform=win32 --arch=x64 --out=dist",
    "package-mac": "electron-packager . SpaceBreakout --platform=darwin --arch=x64 --out=dist"
  }
}

3. 启动游戏

bash 复制代码
npm start

4. (可选)打包为可执行文件

bash 复制代码
npm install -g electron-packager
npm run package-win  # Windows
npm run package-mac  # macOS

四、游戏玩法说明

  • ← → 方向键:移动底部绿色挡板
  • 空格键:游戏结束后按空格重新开始
  • 目标:用球击碎所有彩色砖块
  • 失败条件:球从挡板下方掉落

测试

Electron测试

华为真机测试

五、扩展建议

  1. 增加音效:使用 Web Audio API 播放碰撞、得分音效;
  2. 多关卡:通关后自动生成更难布局;
  3. 本地高分榜 :通过 localStorage 保存最高分;
  4. 粒子特效:砖块破碎时添加爆炸动画;
  5. 主题切换:支持"复古 CRT"或"霓虹未来"风格。

六、为什么选择 Electron 做小游戏?

  • 跨平台:一套代码运行于 Windows/macOS/Linux;
  • 开发高效:直接使用 Web 技术(Canvas/WebGL);
  • 分发简单:打包为独立 EXE/DMG,用户无需安装浏览器;
  • 可扩展:后续可集成 Node.js 实现存档、联网对战等。

💡 提示 :虽然 Electron 应用体积较大(约 100MB+),但对于小型游戏或内部工具仍是极佳选择。若追求极致性能,可考虑 TauriWeb + PWA 方案。

相关推荐
新晨4372 小时前
Vue 3 定时器清理的最佳实践
javascript·vue.js
重铸码农荣光2 小时前
深入理解 JavaScript 原型机制:从“如何拿到小米 SU7”说起
前端·javascript
Zyx20072 小时前
深入 JavaScript 事件机制:从冒泡到事件委托的高效实践
javascript
乐观的用户2 小时前
搞懂虚拟列表实现原理与步骤
前端·vue.js
Heo2 小时前
Webpack高级之常用配置项
前端·javascript·面试
一千柯橘2 小时前
Electron 的打包
electron
Mike_jia2 小时前
DBSyncer:开源数据同步中间件全景实战指南
前端
y***86692 小时前
JavaScript在Node.js中的Electron
javascript·electron·node.js
烛阴3 小时前
从`new`关键字开始:精通C#类与对象
前端·c#