Electron 游戏开发实战:从零构建复古打砖块(Canvas + Vanilla JS)

张开发
2026/4/11 11:05:27 15 分钟阅读

分享文章

Electron 游戏开发实战:从零构建复古打砖块(Canvas + Vanilla JS)
1. 为什么选择Electron开发复古打砖块游戏第一次接触Electron是在2016年当时我正在开发一个跨平台的桌面应用。Electron最吸引我的地方在于它能让前端开发者用熟悉的Web技术栈HTML、CSS、JavaScript来构建桌面应用。对于复古打砖块这类2D小游戏来说Electron简直是量身定制的解决方案。传统游戏开发通常需要学习专门的游戏引擎比如Unity或Unreal。但如果你已经掌握了前端开发技能用ElectronCanvas开发游戏可以省去大量学习成本。我实测下来从零开始构建一个基础版的打砖块游戏熟练的话2小时就能完成核心功能。Electron的另一个优势是跨平台打包。你只需要写一套代码就能生成Windows、macOS和Linux三个平台的可执行文件。记得2018年我参与过一个教育类游戏项目用Electron打包后直接分发到学校的不同电脑上学生们打开就能玩完全不需要安装额外运行环境。2. 项目结构与初始化2.1 创建基础项目先初始化项目结构这是我推荐的标准目录布局breakout-game/ ├── main.js # Electron主进程 ├── preload.js # 预加载脚本 ├── index.html # 游戏主界面 ├── package.json └── assets/ # 资源文件 └── sounds/ # 音效初始化package.json很简单mkdir breakout-game cd breakout-game npm init -y npm install electron --save-dev2.2 配置主进程main.js是Electron应用的入口文件这里有个坑我踩过窗口尺寸最好设为固定值否则不同分辨率下游戏体验会不一致。这是我的配置const { app, BrowserWindow } require(electron) const path require(path) function createWindow() { const win new BrowserWindow({ width: 800, // 固定宽度 height: 600, // 固定高度 resizable: false, // 禁止调整窗口大小 webPreferences: { preload: path.join(__dirname, preload.js), contextIsolation: true } }) win.loadFile(index.html) win.setMenu(null) // 隐藏菜单栏 } app.whenReady().then(createWindow)3. 游戏核心逻辑实现3.1 Canvas基础设置在index.html中我们先设置好Canvas画布。这里有个性能优化技巧给Canvas添加will-change属性可以提升渲染性能。canvas idgameCanvas width760 height500 stylewill-change: transform; /canvasJavaScript部分初始化画布上下文const canvas document.getElementById(gameCanvas) const ctx canvas.getContext(2d) // 高清屏适配 function resizeCanvas() { const scale window.devicePixelRatio canvas.width 760 * scale canvas.height 500 * scale ctx.scale(scale, scale) } window.addEventListener(resize, resizeCanvas) resizeCanvas()3.2 游戏对象定义定义游戏中的三个核心对象挡板、小球和砖块。我建议使用对象字面量而不是类这样代码更简洁// 挡板 const paddle { width: 100, height: 15, x: canvas.width / 2 - 50, speed: 8, color: #4CAF50 } // 小球 const ball { x: canvas.width / 2, y: canvas.height - 30, radius: 8, dx: 4, dy: -4, color: #FFFFFF } // 砖块 const brick { rowCount: 5, colCount: 10, width: 70, height: 20, padding: 10, offsetTop: 50, offsetLeft: 30, colors: [#FF5252, #FFEB3B, #4CAF50, #2196F3] }3.3 碰撞检测实现碰撞检测是游戏的核心逻辑这里我优化了原始算法增加了击打位置影响反弹角度的效果function checkCollisions() { // 墙壁碰撞 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 hitPos (ball.x - (paddle.x paddle.width/2)) / (paddle.width/2) ball.dx hitPos * 5 // 控制反弹力度 ball.dy -Math.abs(ball.dy) } // 砖块碰撞 for(let r 0; r brick.rowCount; r) { for(let c 0; c brick.colCount; c) { const b bricks[r][c] if(b.status 1) { if( ball.x ball.radius b.x ball.x - ball.radius b.x brick.width ball.y ball.radius b.y ball.y - ball.radius b.y brick.height ) { ball.dy -ball.dy b.status 0 score 10 updateScore() // 检查是否通关 if(score brick.rowCount * brick.colCount * 10) { showMessage(You Win!) } } } } } }4. 游戏打包与分发4.1 添加打包脚本在package.json中添加打包命令{ scripts: { start: electron ., package-win: electron-packager . Breakout --platformwin32 --archx64 --outdist, package-mac: electron-packager . Breakout --platformdarwin --archx64 --outdist, package-linux: electron-packager . Breakout --platformlinux --archx64 --outdist } }安装打包工具npm install electron-packager --save-dev4.2 优化打包体积Electron应用体积较大是常见问题通过以下方法可以适当减小体积使用electron-builder代替electron-packager启用压缩--asar排除无用文件在package.json中添加files: [*.js, assets/]我的优化后打包命令electron-packager . Breakout --platformwin32 --archx64 --outdist --asar --overwrite --prune5. 游戏功能扩展建议基础版本完成后可以考虑添加这些增强功能音效系统使用Web Audio API添加击打音效const hitSound new Audio(assets/sounds/hit.wav) hitSound.volume 0.3 // 碰撞时播放 hitSound.currentTime 0 hitSound.play()多关卡系统通过修改砖块布局实现const levels [ { rows: 3, cols: 8, colorScheme: [red,blue] }, { rows: 5, cols: 10, colorScheme: [yellow,green] } ]本地存储使用Electron的IPC通信保存最高分// preload.js const { ipcRenderer } require(electron) window.saveHighScore (score) ipcRenderer.send(save-score, score)粒子效果砖块击碎时添加爆炸动画function createParticles(x, y, color) { for(let i 0; i 10; i) { particles.push({ x, y, size: Math.random() * 3 1, color, speedX: Math.random() * 6 - 3, speedY: Math.random() * 6 - 3, life: 30 }) } }6. 常见问题与解决方案在开发过程中我遇到过几个典型问题游戏卡顿原因是没使用requestAnimationFrame。解决方法function gameLoop() { ctx.clearRect(0, 0, canvas.width, canvas.height) // 绘制逻辑... requestAnimationFrame(gameLoop) }键盘响应延迟改用keydown/keyup事件缓存状态const keys {} window.addEventListener(keydown, e keys[e.key] true) window.addEventListener(keyup, e keys[e.key] false) function handleInput() { if(keys[ArrowLeft]) paddle.x - paddle.speed if(keys[ArrowRight]) paddle.x paddle.speed }高分屏显示模糊需要根据devicePixelRatio调整Canvas尺寸const scale window.devicePixelRatio canvas.width 760 * scale canvas.height 500 * scale ctx.scale(scale, scale)打包后资源加载失败使用path.join处理资源路径win.loadFile(path.join(__dirname, index.html))开发这类小游戏最有趣的地方在于你可以不断添加新功能来挑战自己。上周我刚给这个游戏加了个子弹时间特效当小球接近挡板时时间会变慢这只需要在游戏循环中简单修改deltaTime值就能实现。

更多文章