
-
个人首页: VON
-
鸿蒙系列专栏: 鸿蒙开发小型案例总结
-
综合案例 :鸿蒙综合案例开发
-
鸿蒙6.0:从0开始的开源鸿蒙6.0.0
-
鸿蒙5.0:鸿蒙5.0零基础入门到项目实战
-
本文章所属专栏:Electron for HarmonyOS
太空打砖块(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. `main.js` ------ Electron 主进程](#1.
- 三、运行与打包
-
- [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测试


华为真机测试

五、扩展建议
- 增加音效:使用 Web Audio API 播放碰撞、得分音效;
- 多关卡:通关后自动生成更难布局;
- 本地高分榜 :通过
localStorage保存最高分; - 粒子特效:砖块破碎时添加爆炸动画;
- 主题切换:支持"复古 CRT"或"霓虹未来"风格。
六、为什么选择 Electron 做小游戏?
- ✅ 跨平台:一套代码运行于 Windows/macOS/Linux;
- ✅ 开发高效:直接使用 Web 技术(Canvas/WebGL);
- ✅ 分发简单:打包为独立 EXE/DMG,用户无需安装浏览器;
- ✅ 可扩展:后续可集成 Node.js 实现存档、联网对战等。
💡 提示 :虽然 Electron 应用体积较大(约 100MB+),但对于小型游戏或内部工具仍是极佳选择。若追求极致性能,可考虑 Tauri 或 Web + PWA 方案。