

技术栈:Electron + HTML5 Canvas + Vanilla JS 特点:无框架、单 HTML 文件、支持键盘控制、计分系统、关卡重置 适用人群:前端开发者、Electron 初学者、游戏编程爱好者 完成时间:约 15 分钟
space-breakout/
├── main.js # Electron 主进程
├── preload.js # 预加载脚本(安全上下文隔离)
└── index.html # 游戏主页面(含 Canvas + JS 逻辑)main.js —— Electron 主进程// 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();
});preload.js —— 预加载脚本(可选,本例为空)// preload.js
// 本游戏无需调用原生 API,故留空
// 若后续需添加保存分数到本地等功能,可在此暴露 ipcRendererindex.html —— 游戏主逻辑(Canvas + JS)<!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>npm init -y
npm install electron --save-dev{
"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"
}
}npm startnpm install -g electron-packager
npm run package-win # Windows
npm run package-mac # macOSElectron测试


华为真机测试

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