尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Egret弹珠游戏源码解析:碰撞检测与反弹角度实战
简介这份 Egret 弹珠游戏源码面向具备一定 TypeScript 基础、希望入门 HTML5 游戏开发的开发者也可作为高校课程设计或练手项目的参考素材。资源基于白鹭时代 Egret 引擎构建完整呈现了弹珠类小游戏的核心实现涵盖碰撞检测、物理模拟、Tween 动画、触摸交互、得分与关卡逻辑以及音效管理等技术点其中 public_playBall 模块集中承载了游戏主逻辑与公共资源。压缩包为 rar 格式共 110 个文件以 28 个 js、26 个 ts 脚本为主辅以 25 张 png 素材、11 个 map、10 个 json 配置、5 个 mp3 音效及 html 入口文件整体约 1.06MB结构紧凑便于阅读。目前已有 1286 人学习下载。通过梳理源码目录与关键类读者可理解 Egret 的资源加载、事件驱动与渲染机制并借鉴弹珠运动与碰撞的算法思路为后续独立开发小游戏打下基础。1. egret弹珠游戏源码从一份物理碰撞逻辑说起很多人第一次拿到 egret弹珠游戏源码第一反应是「这不就是个打砖块吗」然后打开 Main.ts 一看几百行物理碰撞加状态机糊在一起直接劝退。我当初也是这么想的直到某次需要给一个互动展项做弹珠玩法原型deadline 只有三天重新写物理引擎肯定来不及才回头认真拆了一份 egret弹珠游戏源码发现它真正值钱的地方不是画面而是那套「碰撞检测 反弹角度 连击计分」的闭环逻辑。这份源码解决的核心问题是在 2D 环境下用尽量少的数学把弹珠的入射角、反弹角、挡板反弹、砖块消除、道具掉落串成一条可玩的链路。它适合三类人想学 Egret 引擎实战的前端、需要快速搭弹珠/打砖块类小游戏原型的独立开发者、以及要把弹珠玩法嵌进 H5 活动页的运营开发。下面我按「先跑起来、再改参数、最后避坑」的顺序把这份源码拆到你能直接复现的程度。2. 把 egret弹珠游戏源码跑起来环境、目录与最小启动命令2.1 为什么选 Egret 而不是直接上 Canvas 手写Egret 是一套基于 TypeScript 的 HTML5 游戏引擎核心优势是显示对象树和事件系统。弹珠游戏里砖块动辄几十上百个如果纯 Canvas 手写每一帧都要自己管理重绘和碰撞遍历用 Egret 的DisplayObjectContainer砖块作为子节点挂上去移除时直接removeChild渲染层自动处理。另一个原因是 Egret 自带egret.Tween和计时器弹珠的缓动、道具闪烁不用额外引第三方库。常见做法是物理部分不依赖引擎自己写一个轻量碰撞函数渲染和事件交给 Egret。这样即使以后换 Cocos 或 Pixi物理逻辑也能平移。我一般会把物理计算和显示更新分成两个模块源码里如果混在一起第一件事就是拆开。2.2 目录结构与关键文件定位一份典型的 egret弹珠游戏源码目录大致如下不同版本会有差异以你手上实际为准路径作用改动频率src/Main.ts入口初始化舞台和游戏场景低src/GameScene.ts游戏主循环、状态管理中src/Ball.ts弹珠对象含速度、半径、反弹逻辑高src/Paddle.ts挡板含鼠标/触摸跟随中src/Brick.ts砖块含血量、消除、掉落判定高src/Physics.ts碰撞检测与反弹计算高resource/图集、音效低先别急着改逻辑第一步是确认入口。打开Main.ts找到new GameScene()那一行确认舞台尺寸和适配模式。H5 弹珠游戏最常见的翻车点是适配stage.scaleMode设成NO_SCALE在手机上会被裁切设成EXACT_FIT又会让弹珠变成椭圆。稳妥做法是用FIXED_WIDTH然后根据设计分辨率算高度。2.3 最小启动命令与本地调试Egret 项目用egret命令行工具管理。假设你已经装好引擎和工具链标准启动流程是# 安装依赖如果项目带 package.json npm install # 编译并启动本地服务器默认端口 3000 egret run # 只编译不启动用于排查编译错误 egret build如果egret run报找不到egret命令说明工具没进全局 PATH用npx egret run或把引擎目录下的tools/bin加进环境变量。编译通过后浏览器打开http://localhost:3000看到弹珠在动就说明环境没问题。提示第一次跑建议把GameScene.ts里的debugDraw开关打开如果有把碰撞盒画出来后面调反弹角度会直观很多。2.4 主循环里到底发生了什么Egret 的帧循环靠egret.startTick或addEventListener(egret.Event.ENTER_FRAME)驱动。弹珠游戏的核心循环做四件事更新弹珠位置、检测碰撞、处理消除与计分、重绘。源码里如果用了Tween做弹珠移动要注意Tween是按时间插值不适合做物理反弹反弹必须自己按速度向量累加。// 每帧更新弹珠位置dt 为毫秒 private updateBall(dt: number): void { const ball this.ball; // 速度单位是像素/毫秒乘 dt 得到本帧位移 ball.x ball.vx * dt; ball.y ball.vy * dt; // 边界反弹左右墙 if (ball.x - ball.radius 0 || ball.x ball.radius this.stageW) { ball.vx -ball.vx; // 修正位置防止卡进墙里 ball.x Math.max(ball.radius, Math.min(this.stageW - ball.radius, ball.x)); } // 顶部反弹 if (ball.y - ball.radius 0) { ball.vy -ball.vy; ball.y ball.radius; } }这段逻辑说明vx/vy是速度分量单位要和dt匹配。如果dt用秒速度就是像素/秒用毫秒就统一毫秒。参数上radius决定碰撞盒大小改小会让弹珠更容易穿过砖块缝隙改大会让反弹更早触发。失败时先看dt是否稳定Egret 在切后台再回来时dt可能突变导致弹珠瞬移穿墙常见做法是给dt设上限比如Math.min(dt, 33)。3. 碰撞与反弹egret弹珠游戏源码里最该改的三个参数3.1 圆与矩形碰撞的判定写法弹珠是圆砖块和挡板是矩形最常用的判定是「圆心到矩形最近点的距离小于半径」。源码里如果用的是 AABB轴对齐包围盒直接比x/y范围边角会误判弹珠擦着砖块角飞过也会被判定碰撞手感很怪。// 圆与矩形碰撞检测返回是否碰撞及法线方向 function circleRectCollide( cx: number, cy: number, r: number, rx: number, ry: number, rw: number, rh: number ): { hit: boolean; nx: number; ny: number } { // 找到矩形上离圆心最近的点 const closestX Math.max(rx, Math.min(cx, rx rw)); const closestY Math.max(ry, Math.min(cy, ry rh)); const dx cx - closestX; const dy cy - closestY; const distSq dx * dx dy * dy; if (distSq r * r) return { hit: false, nx: 0, ny: 0 }; // 法线方向从最近点指向圆心 const dist Math.sqrt(distSq) || 0.0001; return { hit: true, nx: dx / dist, ny: dy / dist }; }逻辑说明closestX/closestY把圆心投影到矩形范围dx/dy是圆心到最近点的向量。如果距离平方大于半径平方说明没碰上。返回的法线nx/ny用来做反弹v v - 2 * (v·n) * n。参数上r是弹珠半径rw/rh是砖块宽高。注意当圆心正好在矩形内部时dist为 0要加一个极小值兜底否则除零会得到NaN弹珠直接消失。3.2 反弹角度为什么你的弹珠总是水平来回飞很多源码的反弹只做了vx -vx或vy -vy结果弹珠永远在固定角度来回玩两下就腻。正确的做法是用碰撞法线做向量反射这样打在砖块侧面和顶面会有不同角度。// 沿法线反射速度向量 function reflect(vx: number, vy: number, nx: number, ny: number): { vx: number; vy: number } { const dot vx * nx vy * ny; return { vx: vx - 2 * dot * nx, vy: vy - 2 * dot * ny }; }参数说明vx/vy是当前速度nx/ny是上一步算出的单位法线。反射后速度大小不变方向关于法线对称。如果反射后vy绝对值太小弹珠会接近水平飞行体验很差。我一般会加一个最小垂直速度约束if (Math.abs(vy) 50) vy vy 0 ? 50 : -50;这个 50 就是手感调节的关键参数调大弹珠更「跳」调小更「平」。3.3 挡板反弹加一点角度偏移让玩法活起来挡板反弹如果只做法线反射弹珠永远垂直弹回玩家只能靠移动挡板改变落点。常见做法是根据弹珠击中挡板的位置给水平速度加一个偏移量越靠边偏移越大。// 挡板反弹hitRatio 为 -1 到 1表示击中位置 function paddleBounce(ballVx: number, ballVy: number, hitRatio: number, speed: number): { vx: number; vy: number } { // 水平速度由击中位置决定垂直速度固定向上 const vx hitRatio * speed * 0.8; const vy -Math.sqrt(Math.max(speed * speed - vx * vx, 1)); return { vx, vy }; }逻辑说明hitRatio由(ball.x - paddle.x) / (paddle.width / 2)算出范围约 -1 到 1。speed是弹珠总速度保持恒定只改变方向。0.8是水平分量系数调大弹珠更「飘」调小更「直」。这个参数是弹珠手感的核心建议在 0.6 到 0.9 之间试。失败情况如果speed * speed - vx * vx为负说明水平分量超过了总速度sqrt会得到NaN所以要用Math.max兜底。4. 砖块、道具与计分egret弹珠游戏源码的玩法扩展点4.1 砖块数据结构与批量消除砖块不要每个都写一个类实例去挂事件性能会崩。常见做法是用一个二维数组存砖块状态渲染层用对象池复用Bitmap。源码里如果每个砖块都addEventListener砖块一多就卡。// 砖块数据0 空1 普通2 多血3 不可破坏 const brickMap: number[][] []; const BRICK_ROWS 6; const BRICK_COLS 10; // 初始化 for (let r 0; r BRICK_ROWS; r) { brickMap[r] []; for (let c 0; c BRICK_COLS; c) { brickMap[r][c] r 2 ? 2 : 1; // 前两行多血 } } // 消除命中后减血为 0 则置空并回收显示对象 function hitBrick(row: number, col: number): boolean { if (brickMap[row][col] 0 || brickMap[row][col] 3) return false; brickMap[row][col]--; if (brickMap[row][col] 0) { recycleBrickView(row, col); return true; // 真正消除 } return false; }参数说明BRICK_ROWS/COLS决定关卡密度行列越多单帧碰撞遍历越重移动端建议不超过 8x12。brickMap的值用数字表示状态比对象省内存。回收显示对象时把它从舞台移除并 push 进对象池下次生成直接取避免频繁new。4.2 道具掉落的概率与触发时机道具是弹珠游戏的留存关键。源码里如果道具是固定位置掉落玩家很快摸清规律。常见做法是砖块消除时按概率触发概率随连击数提升。// 道具掉落判定combo 为当前连击数 function tryDropItem(combo: number): string | null { const baseRate 0.08; // 基础掉落率 const comboBonus Math.min(combo * 0.01, 0.15); // 连击加成上限 15% const rate baseRate comboBonus; if (Math.random() rate) return null; const items [expand, multi, slow]; return items[Math.floor(Math.random() * items.length)]; }逻辑说明baseRate是基础概率comboBonus让连击越高越容易掉道具但设了上限防止刷屏。items是道具类型expand加长挡板、multi分裂弹珠、slow减速。参数上0.08和0.01需要根据关卡长度调关卡越长基础率可以越低。失败情况如果Math.random()在每帧都调用会掉一堆道具必须只在砖块消除的那一帧调用一次。4.3 计分与连击的边界处理计分逻辑看着简单但连击计时器是翻车重灾区。常见做法是每次消除重置一个倒计时倒计时结束连击归零。private combo: number 0; private comboTimer: number 0; private readonly COMBO_WINDOW 800; // 毫秒 // 消除时调用 private onBrickDestroyed(): void { this.combo; this.comboTimer this.COMBO_WINDOW; this.score 10 * this.combo; } // 每帧更新 private updateCombo(dt: number): void { if (this.comboTimer 0) { this.comboTimer - dt; if (this.comboTimer 0) { this.combo 0; } } }参数说明COMBO_WINDOW是连击窗口800ms 意味着两次消除间隔超过 0.8 秒连击断掉。调大更容易连击调小更考验操作。10 * combo是分数公式连击越高单次得分越多但要注意分数上限避免整数溢出虽然 JS 里概率极低但显示时可能变成科学计数法建议用toFixed(0)格式化。5. egret弹珠游戏源码避坑五个我亲自踩过的坑5.1 弹珠穿墙现象是弹珠偶尔飞出边界原因是帧间隔突变现象游戏切到后台再切回来弹珠直接出现在墙外或者消失。原因Egret 在页面不可见时可能暂停 tick恢复后第一帧dt非常大弹珠一帧位移几百像素直接跳过碰撞检测。解决给dt设上限const safeDt Math.min(dt, 33);并且在egret.Event.ACTIVATE事件里重置上一帧时间戳。5.2 砖块消除后碰撞还在现象是弹珠在空位置反弹现象砖块已经消失弹珠飞到那个位置还会反弹。原因brickMap置空了但碰撞检测还在遍历旧的显示对象数组或者对象池回收后没把碰撞标记清掉。解决碰撞检测只遍历brickMap中值不为 0 的格子显示对象和逻辑数据分离回收时把brickMap对应位置置 0。5.3 挡板跟随延迟现象是手机上挡板慢半拍现象鼠标操作正常触摸屏上挡板跟不上手指。原因用了MOUSE_MOVE事件移动端触摸事件是TOUCH_MOVE而且触摸有默认滚动行为。解决同时监听TOUCH_MOVE和MOUSE_MOVE在触摸事件里event.preventDefault()阻止页面滚动并且挡板位置直接赋值而不是用Tween缓动。5.4 弹珠卡在挡板和墙的夹角现象是弹珠原地抖动现象弹珠掉到挡板和侧墙之间速度反复取反看起来在抖。原因两个碰撞同时触发法线方向冲突速度被反射两次回到原方向。解决碰撞检测按优先级处理先检测墙再检测挡板每次反射后立即修正位置把弹珠推出碰撞体并且加一个「同一帧只反射一次」的标记。5.5 音效播放延迟现象是消除音效慢半拍现象砖块消除后音效延迟几百毫秒才响。原因每次消除都new egret.Sound()并加载加载是异步的。解决游戏初始化时预加载所有音效用egret.Sound实例的play()方法复用并且设置soundChannel.volume控制音量。移动端还要注意首次播放需要用户交互触发常见做法是在开始按钮的点击回调里先播一个静音音效解锁。6. 进阶把 egret弹珠游戏源码改成可配置关卡系统6.1 用 JSON 描述关卡把硬编码赶出去源码里砖块布局如果是写死的双重循环加一关就要改代码。进阶做法是把关卡抽成 JSON运行时解析。下面是一个最小关卡描述{ rows: 6, cols: 10, brickHp: [ [2,2,2,2,2,2,2,2,2,2], [2,2,2,2,2,2,2,2,2,2], [1,1,1,1,1,1,1,1,1,1], [1,1,1,1,1,1,1,1,1,1], [0,0,1,1,1,1,1,1,0,0], [0,0,0,1,1,1,1,0,0,0] ], ballSpeed: 0.45, paddleWidth: 120 }解析时把brickHp直接赋给brickMapballSpeed和paddleWidth作为参数传给对应对象。这样策划改关卡不用碰代码你也能用同一套逻辑跑多关。6.2 关卡切换与状态重置的检查清单切关卡时最容易漏重置状态导致上一关的弹珠、道具、连击带到下一关。我一般会写一个resetLevel()按顺序做这几件事停止所有Tween、清空brickMap、回收所有砖块显示对象、重置弹珠位置和速度、重置挡板位置、连击归零、道具效果清除、计时器归零。顺序不能乱先停动画再清数据否则回调里可能访问到已清空的对象。6.3 用速度上限和下限控制难度曲线弹珠速度是难度最直接的杠杆。我的习惯是给速度设上下限每消除一定数量砖块提速一次但不超过上限。private ballSpeed: number 0.4; // 像素/毫秒 private readonly MIN_SPEED 0.35; private readonly MAX_SPEED 0.9; private onBrickDestroyed(): void { this.destroyedCount; if (this.destroyedCount % 5 0) { this.ballSpeed Math.min(this.ballSpeed * 1.05, this.MAX_SPEED); this.applySpeed(); } } private applySpeed(): void { const ball this.ball; const current Math.sqrt(ball.vx * ball.vx ball.vy * ball.vy) || 0.0001; const ratio this.ballSpeed / current; ball.vx * ratio; ball.vy * ratio; }逻辑说明每消除 5 块砖速度乘 1.05上限 0.9。applySpeed按当前速度比例缩放保持方向不变。参数上1.05是增速系数调大难度上升快调小更平缓。MIN_SPEED用于开局或道具减速后的下限防止弹珠慢到卡住。6.4 一个验证手感的小技巧改完参数别急着打包在GameScene里加一个隐藏的调试面板按键盘数字键直接调速度、挡板宽度、连击窗口。我一般用egret.addEventListener(egret.Event.ENTER_FRAME)里检测this.debugKeys按 1 速度加 0.05按 2 减 0.05按 3 挡板加 10 像素。这样调手感不用反复改代码重编译几分钟就能试出一组舒服的参数。这个习惯帮我省了无数个「改一行编译一次」的下午希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

Spring @Transactional 事务失效的七大场景与排查修复指南

Spring @Transactional 事务失效的七大场景与排查修复指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/14 4:15:04
Decepticon 红队技能库深度解析:Kubernetes Pod 逃逸到节点(Container Escape)实战手册

Decepticon 红队技能库深度解析:Kubernetes Pod 逃逸到节点(Container Escape)实战手册

【免费下载链接】Decepticon Autonomous Hacking Agent for Red Team 项目地址: https://gitcode.com/gh_mirrors/de/Decepticon 点击查看 免费下载 本篇以 Decepticon 红队自动化 Agent 的技能库文档 k8s-pod-escape/SKILL.md 为核心,系统讲解从"…

📅 2026/10/14 4:15:04
前端实时推送技术解析:轮询、SSE与WebSocket对比选型

前端实时推送技术解析:轮询、SSE与WebSocket对比选型

1. HTTP协议为什么做不了真正的"服务器主动推送"搞实时推送之前,得先把一个基础问题聊透:HTTP协议本质上是一个请求-响应模型。客户端发一个请求,服务端返回一个响应,连接结束。很多业务开发的同事对这块理解比较粗&…

📅 2026/10/14 4:15:04
MORE NEWS

更多资讯

📰

在职大龄考生高效通过法考的系统方法论

1. 项目概述"在职大龄通过法考,不靠鸡血,靠系统"这个标题背后,隐藏着一个现代职场人突破自我限制的典型案例。作为一名35岁在职考生,我用8个月时间从零基础到以总分412分通过法律职业资格考试,期间没有辞职备…

📰

基于YOLOv8的外墙裂缝检测系统:从数据到部署实战

1. 项目缘起与整体设计思路外墙裂缝检测这件事,最早我是被一个做物业工程的朋友拉进坑的。他手里管着十几栋高层,每年雨季前后都要安排人吊着绳子一块一块墙面去看,效率低不说,危险系数还高。后来他问我,能不能用无人机…

📰

SSD1322 OLED驱动开发:从数据手册到灰度屏调试的完整避坑指南

简介:SSD1322数据手册由 Solomon Systech 官方发布,是一款面向 OLED/PLED 显示驱动控制器的高性能技术文档,适合硬件工程师、嵌入式开发者和显示模组设计人员查阅,帮助解决 480128 点阵屏驱动配置、引脚连接和命令调试等实际问题。…

📰

AI编程助手提示词瘦身:从1.4万字到200字,Token消耗降85%

1. 从 1.4 万到 200 字,这个提示词瘦身思路值得每个做 AI 应用的人看一遍如果你正在用某个 AI 编程助手,或者自己搭过基于大模型的代码生成工具,大概率遇到过这种情况:明明只是让它改一个函数,结果它把整个项目的架构、…

📰

局部遮阴下光伏PSO-MPPT控制模型与Simulink仿真

1. 项目概述与核心需求解析1.1 这个项目到底解决什么问题光伏发电系统里,最大功率点追踪(MPPT)一直是老生常谈的话题。但常规的MPPT算法有一个致命的假设前提——组件表面光照均匀。一旦实际使用中出现局部遮阴,比如云朵飘过、楼宇…

📰

C++与机器学习框架:从选型到部署的全链路工程实战

先别急着谈算法调参那一套——如果你写过几年C,又曾被 Python 系的机器学习项目折磨过,应该会对这个标题特有共鸣:C搞机器学习到底行不行?答案是不仅行,而且在生产环境里,很多时候它是唯一靠谱的行。所谓“…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬