
简介这份 Egret 弹珠游戏源码面向具备一定 TypeScript 基础、希望入门 HTML5 游戏开发的开发者借助白鹭时代 Egret 引擎的跨平台渲染与资源管理能力完整呈现一款弹珠小游戏的实现思路。压缩包共 110 个文件约 1.06MB以 28 个 js、26 个 ts 脚本为核心配合 25 张 png 素材、11 个 map、10 个 json 配置、5 个 mp3 音效及 html 入口文件覆盖逻辑、资源与配置各环节。源码围绕碰撞检测、物理模拟、Tween 动画、触摸交互、得分与关卡逻辑、音效管理等关键技术展开public_playBall 目录中可看到主入口、弹珠实体、场景与音效等模块划分便于理解游戏循环与实体行为组织方式。目前已有 1286 人学习适合对照源码拆解弹珠运动与碰撞算法快速积累 Egret 项目实战经验。1. 从一份 egret 弹珠游戏源码说起它到底能跑出什么效果前阵子有个做小游戏外包的朋友接了个需求客户要一款「弹珠台」玩法的休闲游戏预算不高但要求两周出可玩版本。他翻了一圈开源仓库最后锁定了一份 egret 弹珠游戏源码。我帮他一起拆了这套东西发现它比想象中完整——不是那种只丢几个类文件的半成品而是包含了弹珠发射、挡板碰撞、砖块消除、分数结算、关卡切换的完整闭环。如果你也在找一份能直接改、能上手的 egret 弹珠项目这份源码值得花时间研究。egret 这套引擎在国内 H5 小游戏圈子里沉淀了很多年弹珠玩法又是物理碰撞的经典练手场景。这份源码解决的核心问题是把「弹珠运动 碰撞检测 实时渲染」这条链路用 egret 的显示对象和帧循环串起来让你不用从零搭物理框架。适合谁一是想学 egret 实战但缺完整案例的前端二是需要快速出弹珠类 Demo 接需求的外包三是想把弹珠玩法移植到微信小游戏或抖音小游戏的开发者。下面我按「先看懂结构、再跑起来、最后改出自己东西」的顺序拆一遍。2. 拆开源码看骨架egret 弹珠的物理循环与显示层级拿到一份游戏源码最忌讳上来就改代码。我一般先花二十分钟把目录结构和核心类过一遍搞清楚「谁在驱动谁」。egret 弹珠这类项目骨架其实就三件事一个主循环、一套碰撞判定、一层显示对象管理。把这三件事理清后面改参数、换素材、加关卡都是顺水推舟。2.1 主循环与帧驱动egret 的 tick 是怎么转起来的egret 引擎本身不强制你用某个游戏循环但弹珠游戏必须有一个稳定的帧驱动来更新弹珠位置。这份源码里主逻辑挂在egret.Event.ENTER_FRAME上每帧做三件事更新弹珠速度、检测碰撞、重绘显示。核心代码大致长这样// 主游戏类继承 egret.DisplayObjectContainer class MarbleGame extends egret.DisplayObjectContainer { private ball: egret.Shape; // 弹珠显示对象 private vx: number 4; // 水平速度单位像素/帧 private vy: number -6; // 垂直速度负值向上 private gravity: number 0.15; // 每帧重力增量 constructor() { super(); this.ball new egret.Shape(); this.ball.graphics.beginFill(0xff0000); this.ball.graphics.drawCircle(0, 0, 8); this.ball.graphics.endFill(); this.addChild(this.ball); // 注册帧循环 this.addEventListener(egret.Event.ENTER_FRAME, this.onTick, this); } private onTick(): void { // 1. 重力加速 this.vy this.gravity; // 2. 位置更新 this.ball.x this.vx; this.ball.y this.vy; // 3. 边界反弹简化版 if (this.ball.x 8 || this.ball.x 480 - 8) { this.vx -this.vx; } if (this.ball.y 8) { this.vy -this.vy; } // 4. 碰撞检测在下一节展开 this.checkCollision(); } }这段代码的逻辑说明ENTER_FRAME是 egret 的帧事件默认跟浏览器requestAnimationFrame对齐大约 60 帧每秒。vx和vy是速度分量gravity每帧叠加到vy上模拟重力。参数怎么调gravity设 0.15 时弹珠下落偏「轻快」想要更「沉」的手感可以加到 0.3vx初始值决定发射角度4 和 -6 的组合大约是 56 度仰角。注意egret 的坐标系原点在左上角y 轴向下为正所以向上运动是负值这个和数学坐标系相反新手最容易在这里翻车。2.2 碰撞检测圆形弹珠和矩形挡板怎么判弹珠游戏的核心手感来自碰撞判定。这份源码用的是「圆-矩形」检测比像素级检测省性能又比纯 AABB 精确。原理是先找到矩形上离圆心最近的点再算这个点到圆心的距离是否小于半径。代码实现// 圆与矩形碰撞检测 private checkCollision(): void { const ballR 8; // 弹珠半径 // 遍历所有挡板/砖块 for (let brick of this.bricks) { // 找矩形上离圆心最近的点 let closestX Math.max(brick.x, Math.min(this.ball.x, brick.x brick.width)); let closestY Math.max(brick.y, Math.min(this.ball.y, brick.y brick.height)); // 算距离 let dx this.ball.x - closestX; let dy this.ball.y - closestY; let dist Math.sqrt(dx * dx dy * dy); if (dist ballR) { // 碰撞响应根据最近点方向反弹 if (Math.abs(dx) Math.abs(dy)) { this.vx -this.vx; // 水平反弹 } else { this.vy -this.vy; // 垂直反弹 } // 把弹珠推回矩形外防止卡住 this.ball.x dx 0 ? (ballR - dist) : -(ballR - dist); this.ball.y dy 0 ? (ballR - dist) : -(ballR - dist); brick.onHit(); // 触发砖块消除逻辑 } } }逻辑说明closestX和closestY把圆心坐标「夹」到矩形范围内得到矩形上离圆心最近的点。dx、dy是圆心到最近点的向量dist是距离。当距离小于半径时判定碰撞。反弹方向用Math.abs(dx) Math.abs(dy)判断——水平差大就水平反弹垂直差大就垂直反弹这是简化处理真实物理应该按法线反射但弹珠游戏里这种简化反而手感更「跟手」。参数说明ballR要和显示对象的drawCircle半径一致否则会出现「看着没碰到却反弹」的玄学现象。brick.width和brick.height是砖块尺寸如果砖块用了缩放这里要乘上scaleX、scaleY不然判定框和视觉框对不上。我一般会在调试阶段把碰撞框画出来确认无误再隐藏。2.3 显示层级与资源加载谁盖在谁上面egret 的显示列表是树形结构后addChild的对象盖在先加的上面。弹珠游戏里层级顺序很关键背景在最底、砖块居中、弹珠和挡板在上、UI 分数在最顶。这份源码用了一个layerMap来管理// 层级管理 private layerMap: { [key: string]: egret.DisplayObjectContainer } {}; private initLayers(): void { const layers [bg, brick, ball, paddle, ui]; for (let name of layers) { let layer new egret.DisplayObjectContainer(); this.layerMap[name] layer; this.addChild(layer); // 按数组顺序添加后面的盖前面 } } // 使用把弹珠加到 ball 层 this.layerMap[ball].addChild(this.ball);这样分层的好处是改层级不用动业务代码调layerMap的添加顺序就行。资源加载方面源码用的是RES.getResAsync异步加载纹理注意 egret 的RES模块需要在egret.runEgret之前配置好resource.json否则会报「资源未找到」。常见做法是把图片、音效、配置表都挂在default.res.json里用RES.loadConfig一次性加载。3. 把源码跑起来环境配置、编译与第一个可玩版本看懂骨架之后下一步是让它真正跑起来。egret 项目的环境配置有几个固定套路但版本差异会带来不少坑。这一章我按「装工具 → 建项目 → 导源码 → 编译调试」的顺序走一遍每一步都给出可抄的命令和配置。3.1 环境准备Egret Engine 与白鹭工具的版本选择egret 的开发工具链主要是 Egret Engine引擎运行时和 Egret WingIDE现在叫白鹭开发工具。这份源码基于 egret 5.x 的 API 写的如果你装的是 4.x 或更早版本DisplayObjectContainer的部分方法会不兼容。我一般建议组件推荐版本说明Egret Engine5.4.x源码 API 对齐稳定Egret Wing4.x支持 5.x 项目结构TypeScript3.xegret 5.x 内置不用单独装Node.js14 LTS编译脚本依赖别用太新的安装步骤先从 egret 官网下载 Egret Engine 安装包装完后命令行执行egret能看到版本号就说明环境变量配好了。然后装 Egret Wing用它打开项目目录。注意不要用 VSCode 直接开 egret 项目虽然能编辑代码但编译和调试的配置容易乱Wing 里一键编译更省事。提示如果你之前装过其他版本的 egret先卸载干净再装多个版本共存会导致egret build找不到正确的引擎路径。3.2 导入源码与目录结构对齐拿到源码包后先看根目录有没有egretProperties.json这是 egret 项目的配置文件。标准结构大致是project/ ├── egretProperties.json # 引擎配置 ├── tsconfig.json # TS 编译配置 ├── src/ # 源码目录 │ ├── Main.ts # 入口 │ └── game/ # 游戏逻辑 ├── resource/ # 资源目录 │ ├── default.res.json # 资源清单 │ └── assets/ # 图片音效 └── bin-debug/ # 编译输出导入时最容易出问题的是egretProperties.json里的engineVersion字段。如果源码写的是5.4.0而你装的是5.2.0编译会报版本不匹配。解决办法是打开这个文件把engineVersion改成你本地实际装的版本号。另一个坑是resource/default.res.json里的路径如果源码作者用的是绝对路径或者带盘符的路径换机器后资源全加载不出来要手动改成相对路径。3.3 编译、调试与真机预览环境对齐后在项目根目录执行编译# 编译项目debug 模式带 sourcemap egret build # 如果报错找不到 egret用完整路径 # Windows: C:\Egret\EgretEngine\5.4.0\egret build # macOS: /Applications/EgretEngine/5.4.0/egret build # 启动本地服务器预览 egret runegret build会把src下的 TypeScript 编译成 JavaScript输出到bin-debug。egret run会起一个本地 HTTP 服务默认端口 3000浏览器打开http://localhost:3000就能看到游戏。调试时按 F12 打开控制台egret 的日志会打在 console 里。如果弹珠不动先看 console 有没有报错再看ENTER_FRAME有没有注册成功——常见原因是addEventListener写在了super()之前导致监听器没挂上。真机预览方面egret 支持发布成 H5 后用手机浏览器打开。执行egret publish --runtime web会生成bin-release/web目录把这个目录丢到任意静态服务器上就能访问。注意手机上的触摸事件和鼠标事件不一样源码里如果只监听了egret.TouchEvent.TOUCH_MOVE在 PC 上要用鼠标拖拽测试得确认事件类型是否兼容。4. 改出你自己的弹珠玩法参数调优与功能扩展能跑起来只是第一步真正有价值的是把它改成你想要的样子。这一章讲三个最常改的方向手感参数、关卡数据、以及加一个新道具。每个方向都给出具体改法和参数范围你照着调就能出效果。4.1 手感调优速度、重力、反弹系数的黄金区间弹珠游戏好不好玩八成看手感。手感由三个参数决定初速度、重力、反弹系数。这份源码里这三个值散落在不同文件我一般会先把它们抽到一个GameConfig.ts里统一管理// GameConfig.ts 集中管理手感参数 export const GameConfig { ballSpeed: 8, // 弹珠初速度像素/帧 gravity: 0.15, // 重力加速度 bounceDamping: 0.98, // 反弹衰减1 表示不衰减 paddleSpeed: 12, // 挡板移动速度 maxBallSpeed: 20, // 速度上限防止穿透 };参数说明ballSpeed在 6 到 12 之间比较合适低于 6 弹珠太慢显得拖沓高于 12 容易一帧穿过砖块导致「穿透」bug。gravity0.1 到 0.3 是常见区间0.15 偏休闲0.25 偏硬核。bounceDamping设 0.98 表示每次反弹损失 2% 速度这样弹珠不会永远弹下去最终会慢下来。maxBallSpeed是保险丝防止速度累积到一帧位移超过砖块厚度。调参的方法改完值后重新egret build在浏览器里试玩。我一般会开一个调试面板实时显示当前速度和帧率方便对比。如果发现弹珠偶尔「穿墙」八成是速度太快加上帧率波动把maxBallSpeed降到 15 以下通常能解决。4.2 关卡数据外置用 JSON 配置砖块布局源码里砖块布局如果是硬编码在代码里的改一关就要动代码很不优雅。常见做法是把关卡数据抽成 JSON{ level1: { rows: 5, cols: 8, brickWidth: 50, brickHeight: 20, gap: 4, layout: [ 11111111, 11111111, 11000011, 11111111, 11111111 ] } }layout里 1 表示有砖块0 表示空位。加载时遍历这个二维数组按brickWidth gap算间距生成砖块对象。这样加关卡只需要加一个 JSON 节点不用碰逻辑代码。注意gap不要设太大否则弹珠容易从缝隙里穿过去视觉上也显得松散。我一般gap设 2 到 4 像素。4.3 加一个「多球道具」从碰撞回调到对象池扩展玩法最直接的是加道具。以「多球」为例弹珠碰到特定砖块后分裂出两颗新弹珠。实现思路是维护一个弹珠数组碰撞回调里判断砖块类型是道具砖就克隆弹珠// 弹珠数组管理 private balls: Ball[] []; private onBrickHit(brick: Brick): void { if (brick.type multi) { // 克隆两颗速度方向偏移 for (let i 0; i 2; i) { let newBall this.ballPool.get(); // 从对象池取 newBall.vx this.ball.vx * (i 0 ? 0.8 : 1.2); newBall.vy this.ball.vy; this.balls.push(newBall); this.layerMap[ball].addChild(newBall); } } brick.destroy(); }这里用了对象池ballPool因为弹珠频繁创建销毁会触发 GC导致帧率抖动。对象池的常见实现是维护一个空闲数组get时取一个或新建recycle时推回数组。注意多球之后碰撞检测要遍历所有弹珠性能开销翻倍弹珠数量建议控制在 5 颗以内。5. 避坑与排查egret 弹珠源码里最容易翻车的五个地方这份源码我前后帮人调过三次踩的坑高度集中。下面五条按「现象 → 原因 → 解决」写你遇到问题时可以直接对号入座。现象一弹珠卡在砖块里抖动出不来。原因是碰撞响应只反转了速度没有把弹珠推出矩形。当弹珠速度慢、每帧位移小于重叠深度时就会反复触发碰撞。解决在反弹后加位置修正把弹珠沿最近点方向推出ballR - dist的距离代码在 2.2 节已经给出。现象二编译报「Cannot find module egret」。原因是tsconfig.json里的types路径没指向本地引擎。解决打开tsconfig.json确认compilerOptions.types包含 egret 的.d.ts路径通常是node_modules/egret/egret.d.ts或引擎安装目录下的types文件夹。现象三手机上触摸挡板没反应。原因是源码只监听了鼠标事件egret.MouseEvent移动端不触发。解决把事件类型换成egret.TouchEvent.TOUCH_MOVE或者两个都监听。注意TOUCH_MOVE的坐标要用stageX、stageY不要用localX否则挡板位置会偏。现象四弹珠速度越来越快最后穿墙。原因是反弹时没有做速度衰减或者重力持续叠加没有上限。解决加bounceDamping系数每次反弹乘 0.98同时给vy设一个上限超过maxBallSpeed就截断。现象五发布到小游戏平台后白屏。原因是资源路径用了本地绝对路径小游戏环境读不到。解决检查default.res.json里所有url字段改成相对路径并且确认资源文件确实被打包进了发布目录。常见做法是用egret publish后检查bin-release里的资源是否完整。6. 进阶技巧用对象池和脏矩形把帧率稳住源码跑通、玩法改完之后如果要做成能上线的版本性能是绕不过去的坎。egret 弹珠这类游戏性能瓶颈通常不在逻辑计算而在渲染和 GC。我分享两个实战中验证有效的技巧。第一个是对象池。弹珠、砖块、粒子特效这些频繁创建销毁的对象全部走对象池。实现上维护一个pool: any[]get时pop一个或新建recycle时push回去并重置属性。注意回收时要removeChild并从显示列表移除否则对象池里的对象还在渲染白白消耗性能。我一般会在对象池里加一个active标记get时置 truerecycle时置 false帧循环里只更新 active 的对象。第二个是脏矩形渲染。egret 默认每帧重绘整个舞台但弹珠游戏里背景和静态砖块其实不用每帧重绘。可以在egretProperties.json里开启dirtyRect支持然后对静态层调用cacheAsBitmap。这样引擎只重绘弹珠和挡板所在的区域帧率能提升 30% 以上。注意cacheAsBitmap对频繁变化的显示对象不适用砖块被消除后要手动refreshCache否则会残留旧图像。验证方法在浏览器里按 F12 打开 Performance 面板录制 10 秒游戏过程看 FPS 曲线是否稳定在 55 以上。如果掉帧先看 GC 频率频繁 GC 说明对象创建太多加对象池再看渲染耗时如果paint时间占比高开脏矩形。我习惯在每次改完性能相关代码后用同一段操作路径录三次取平均帧率对比避免单次波动误判。从那以后我每次拿到一份游戏源码都强制先跑一遍性能录制再动手改功能不然改到一半发现帧率崩了回头找原因更痛苦。希望这份拆解帮到你少走点弯路。本文还有配套的精品资源点击获取