ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Egret弹珠游戏开发实战:手写碰撞检测与H5小游戏性能优化技巧

Egret弹珠游戏开发实战:手写碰撞检测与H5小游戏性能优化技巧 简介基于Egret引擎开发的一套弹珠游戏完整源码出自CSDN作者zy646346925面向HTML5游戏开发初学者和希望进阶的开发者适合用来学习Egret游戏框架的实际应用。项目使用TypeScript编写核心逻辑配合WebGL渲染与Canvas技术完整覆盖碰撞检测、物理模拟、动画系统、用户交互及音效管理等关键知识点并可通过p2Physics库实现真实反弹效果。压缩包为rar格式共110个文件以js、ts源码为主辅以png图片素材、json配置文件、mp3音频及map文件整体大小仅1.06MB结构精简便于快速下载研读。已有1279人下载学习资源热度较高。通过分析源码可以掌握Egret的项目组织方式、资源加载流程、事件监听机制和游戏主循环写法public_playBall文件夹更清晰展示了游戏入口、弹珠实体、场景构建、音效控制等模块的分离设计对独立开发完整小游戏很有参考价值。1. 弹珠游戏核心思路与方案选型1.1 弹珠游戏的技术点拆解Egret引擎做的弹珠游戏说白了就是经典的打砖块玩法一颗小球在场景里弹来弹去挡板接住球防止它掉落球撞碎砖块得分清完所有砖块进入下一关。这个玩法的核心逻辑其实只有三块球的匀速直线运动、球与矩形物体挡板、砖块的碰撞检测、碰撞后的反弹方向计算。很多人一上来就想用物理引擎我觉得有点杀鸡用牛刀了。我用Egret做过好几个弹珠类的原型从早期的Pomelo弹珠到后来的H5版本踩过不少坑。这个项目的源码我在设计时就确定了几个关键决策第一不用Egret自带的物理系统纯手写碰撞检测第二所有物体用矩形包围盒处理球用圆心加半径的圆形模型第三渲染层全部用egret.Sprite不碰滤镜和复杂渲染。这三个决策让整个项目的代码量控制在几百行以内逻辑一眼能看穿非常适合用来学习或二次开发。1.2 为什么选择纯手写碰撞而不是物理引擎Egret确实集成了Box2D物理引擎做弹珠类游戏好像挺对口。但实际写下来你会发现Box2D在H5小游戏里有两个麻烦一是它跑在独立的物理世界里你需要同步显示对象和物理刚体的位置多了一层映射关系二是它的调参成本不低摩擦力、恢复系数、密度这些参数对新手来说就是劝退神器。手写碰撞检测就六个字距离判断加方向翻转。球是圆的砖块和挡板是矩形我要做的就是把球的圆心投影到矩形的四条边上找到离圆心最近的那个点如果圆心到这个点的距离小于球半径就说明碰上了。方向翻转也好办看圆心是从矩形的左右方向还是上下方向进入的决定翻转X轴速度还是Y轴速度。这套逻辑写出来大概四五十行不需要引入任何引擎。特别强调一下这里有个常见的坑正方形砖块碰撞时容易出现“卡角”现象后文我会专门讲解决办法。1.3 这套方案适配什么场景、适合谁来学习如果你是准备做产品级商业游戏的我劝你直接上成熟的物理引擎或者找一个现成的打砖块框架改改就好。但这个源码面向的是另一批人想学Egret游戏开发流程的初学者、需要快速出交互原型的策划、或者想搞懂碰撞检测底层原理的开发者。用纯手写方案的最大好处是只有一个入口一条调用链。整个游戏从启动到结束流程清晰得像一本教材Main创建场景场景里放球、挡板、砖块一个update循环驱动所有逻辑碰撞了回调处理。这样的架构让你可以在半天内把源码从头看到尾然后随意改成自己的玩法。而且这类小游戏的H5版本粘性特别高放在朋友圈里传播起来效果很好我一个朋友把砖块换成自己产品的logo配合邀请好友助力机制一个月带来了两万多自然新增。2. 整体架构设计与资源准备2.1 代码结构模块划分与分层思想拿到这个源码你先看目录结构这是理解整个项目最快的路径。整个项目分了这么几个模块Ball.ts球对象负责位置、速度、半径、颜色以及移动逻辑Paddle.ts挡板对象负责宽度、高度、位置以及跟随触摸/鼠标移动的逻辑Brick.ts方块对象负责宽高、颜色、存活状态GameScene.ts游戏主场景负责创建以上所有对象、启动游戏循环、处理碰撞、管理得分和关卡GameConfig.ts全局配置包含所有可调参数比如球速、砖块行列数、颜色方案等为什么要单独拆一个GameConfig.ts这个是我反复踩坑后的经验。做小游戏最怕改一个参数要满世界找尤其是球速、砖块间距这种手感相关的参数需要反复调试。集中到一个文件里调手感的时候改一行就能立刻试效果不用在代码里翻来翻去。我见过太多项目把配置散落在各个类的构造函数里最后想调整一个值要找半天非常痛苦。从分层上讲这里遵循了最简单的MVC思路GameScene是Controller和View的合体小游戏里没必要拆太细Ball、Paddle、Brick是Model和View的合体GameConfig是配置文件。这种分层方式对小型项目来说刚刚好——既保证了逻辑不纠缠在一起又不会因为文件太多把小项目搞复杂。2.2 素材处理矢量绘制还是位图素材这个源码里所有的图形都是用Egret的Graphics接口代码绘制的没有用到一张外部图片。比如砖块就是beginFill设置颜色然后drawRect画一个圆角矩形球就是一个drawCircle。这个选择是故意的原因有两个。第一加载外部素材意味着要处理加载时序、图集配置、发布时的资源路径等问题对教学型项目来说是额外负担。代码绘制完全绕开了这层项目在任何环境跑起来都不缺素材分享出去别人拿起来就能跑。第二代码绘制的图形在缩放时是矢量计算不会像位图一样在低分辨率设备上发虚。当然如果你要做到上线级别的水准我建议还是用美术出的图。但在这个源码里核心要展示的是逻辑而非美术代码绘制反而是最干净的方案。真到了要换素材的时候也只需要改Brick和Paddle的绘制函数对外暴露的接口不变完全不影响游戏逻辑。这就是模块化设计的好处显示层怎么实现对逻辑层是透明的。2.3 屏幕适配与不同分辨率兼容弹珠游戏对屏幕适配的要求比一般游戏高因为砖块的布局是固定的行列结构如果舞台宽度不够最两边的砖块就会被切掉。这个源码里的适配策略是这样的首先在index.html和egretProperties.json里把舞台设计尺寸定为750 * 1334iPhone 6/7/8的经典设计尺寸然后开启自适应缩放模式。这个尺寸是经过验证的750的宽度下我放8列砖块每块宽度70间隔10再加上左右各留50像素的边距刚好铺满。如果你想要9列或者10列可以调整砖块和间距的数值但记住一个公式总宽 列数 * 砖块宽 (列数 - 1) * 间距 2 * 左右边距。其次对于长宽比不同的设备我在GameScene的onAddedToStage里做了一次动态适配如果实际显示宽度小于设计宽度就整体缩放场景如果高度不够就把挡板位置往上提一点同时砖块区域往下压一点。这个适配逻辑写出来大概二十行但能保证游戏在手机、Pad、PC浏览器上都不会出现元素超出屏幕的情况。这里有个细节我特别想分享适配一定要在完成布局计算之后、首次渲染之前执行否则会出现一帧的闪烁。我最初就是先addChild再适配结果每次启动都闪一下排查了半天才发现是时序问题。3. 核心功能拆解与实操要点3.1 球的移动基于帧循环的速度积分球在每一帧里的移动逻辑是整个游戏的心脏这也是我一开始就想到的问题怎么让球动起来。代码很简单核心就一个公式ball.x ball.speedX * deltaTime; ball.y ball.speedY * deltaTime;其中deltaTime是这一帧到上一帧的时间差单位是秒。这里有个特别重要的点一定要乘以deltaTime而不是每帧固定移动固定距离。如果不乘不同帧率设备上的球速会不一样——60帧的设备上球跑得飞快30帧的设备上球像在爬。乘以时间差之后球的移动速度就变成了“每秒多少像素”在任何设备上表现一致。关于速度的单位和量级我调过很多次300像素每秒是一个手感不错的起步值。这个速度下玩家有充足的时间反应但又不会觉得无聊。每撞碎一块砖可以把速度提高2个像素每秒给游戏增加一点点挑战性。这个加速机制可以保证游戏在中后期依然有紧张感不会让玩家觉得越打越干。不过要注意速度有上限我在GameConfig里设了MAX_BALL_SPEED 600超过之后就不再加速了。不设上限的话后期球速会快到一个帧内移动超过砖块宽度的程度碰撞检测就直接穿透了这是所有弹珠游戏在后期都会遇到的经典Bug。3.2 碰撞检测圆形与矩形的相交判定碰撞检测是整个源码里最具技术含量的部分值得多花点篇幅讲清楚。球是圆的挡板和砖块是矩形这两种形状的碰撞判定业界标准做法是“找矩形上离圆心最近的点”。我把这个逻辑封装成了一个函数输入是圆形的圆心坐标和半径、矩形的边界输出是是否碰撞private checkCircleRectCollision( circleX: number, circleY: number, circleRadius: number, rectX: number, rectY: number, rectWidth: number, rectHeight: number ): boolean { // 计算圆心在矩形坐标系下的相对位置 let closestX Math.max(rectX, Math.min(circleX, rectX rectWidth)); let closestY Math.max(rectY, Math.min(circleY, rectY rectHeight)); let dx circleX - closestX; let dy circleY - closestY; // 用距离平方和与半径平方比较避免开方运算 return (dx * dx dy * dy) (circleRadius * circleRadius); }这个函数的精妙之处在于closestX和closestY的计算方式直接覆盖了三种情况圆心在矩形内部最近点就是圆心本身、圆心在矩形外部正对着某条边最近点是边上对应的投影、圆心在矩形的角附近最近点是矩形顶点。也就是说一个函数同时处理了边碰撞和角碰撞不需要再单独判断是从哪个方向撞进来的。碰撞之后要确定反弹方向这才是真正考验基本功的地方。我的做法是用球心与矩形中心的相对位置来判断。如果球心在矩形的左上方球就应该往右上方反弹也就是speedX变正、speedY变负。但这里有个陷阱如果球从下面撞上来球心也可能在矩形的左下方这时speedY应该变正才对。所以不能只看相对位置还要结合球当前的飞行方向来判断。我用的判断逻辑是这样的先算出球心到矩形中心的偏移量offsetX和offsetY然后看偏移量在哪个方向占主导。如果|offsetX| |offsetY|说明球是从左右方向撞过来的反转speedX否则说明是从上下方向撞过来的反转speedY。这个逻辑简单可靠也恰好避免了“卡角”问题——卡角的本质是球同时从两个方向撞到矩形导致X和Y速度同时被反转球就被“卡”在角落来回弹。只反转主导方向的那一个轴就不会出现这种情况。上次有个朋友跑完源码后来问我为什么他的球在撞到砖块角落时会偶尔穿过砖块他用的就是同时反转两个轴的方案结果在极端情况下出现了穿透。我把这段主导方向判断的逻辑发给他之后问题立刻解决了。这就是为什么要写源码解析文章的原因光看一个成品源码不讲解很多人真的看不懂里面的门道。3.3 挡板控制触摸事件与限位挡板的交互逻辑属于小游戏里最简单但最容易出体验问题的一类。Egret中监听触摸事件的方式是在舞台或场景上挂事件监听器this.stage.addEventListener(egret.TouchEvent.TOUCH_MOVE, this.onTouchMove, this);回调里的核心逻辑简单说就是“让挡板的中心跟着手指的X坐标走”。但这里有个必须处理的细节挡板不能移出屏幕边界。玩家的手指滑出屏幕边缘时挡板如果还跟着走那一半挡板就跑到屏幕外了球从边缘反弹时就会出现奇怪的判定。处理方式用一个clamp函数把挡板的X坐标限制在有效范围内let minX paddle.width / 2; let maxX this.stage.stageWidth - paddle.width / 2; let targetX Math.max(minX, Math.min(e.stageX, maxX)); paddle.x targetX - paddle.width / 2;这里stageX是触摸点在舞台上的全局坐标。要注意的是如果你的场景做了缩放适配触摸事件的stageX是经过引擎转换后的逻辑坐标直接拿来用就行不需要自己再换算。但如果你监听的是某个子对象上那就要用localX区别是stageX是相对于舞台的坐标localX是相对于当前监听对象的坐标。我用的是stageX因为挡板的父容器就是舞台。体验层面还有一个值得说的细节挡板的移动响应是否需要做平滑处理。如果直接让挡板跳跃到手指位置会显得很生硬。我加了一个简单的线性插值paddle.x (targetX - paddle.x) * 0.3;意思是每次移动向目标位置靠近30%这样挡板会有一个轻微的“追赶”效果手感上更柔和、更符合物理直觉。0.3这个系数我试过好几个值太大比如0.8就几乎没有平滑效果太小比如0.05会显得挡板反应迟钝、跟不上手速。0.3到0.4之间是比较稳妥的区间这个参数因人而异建议你实际试玩后自己微调。3.4 砖块的布局与生命周期管理砖块的生成逻辑不复杂就是一个双重循环的网格排布我前面提到过每块砖的宽度、间距这些参数都从GameConfig里读取。但有一个细节很容易被忽略砖块的颜色必须根据行数做渐变处理。我看了很多新手写的打砖块游戏全部砖块一种颜色玩起来非常费眼因为玩家的视线无法快速锁定“哪一行是最后一行”“还有几行没打完”。这个源码里我用了一个HSL颜色数组从上到下依次是红、橙、黄、绿、蓝、紫每一行一个色系。这不仅是美观问题还承担了“进度提示”的功能。玩家一眼就能看出还剩哪几种颜色的砖对应着还差几行没清完这种潜意识的进度感知会让游戏体验顺畅很多。砖块被打碎之后需要把它从场景中移除同时从管理它的数组中移除。这里有一个我每次都强调的坑JavaScript/TypeScript里用splice删除数组元素时必须倒序遍历或者用filter重新赋值。如果你正序遍历删除第i个元素后后面的元素会往前挪一位但你继续访问i1就会跳过原来i1位置上的那个元素。表现就是总有一块砖“打不碎”。这是所有用数组管理对象列表的开发者在初期都会踩的坑我当年第一次写俄罗斯方块时就在这里耗了一个晚上。正确的做法是this.bricks this.bricks.filter(brick !brick.isDestroyed);或者倒序循环for (let i this.bricks.length - 1; i 0; i--) { if (this.bricks[i].isDestroyed) { this.removeChild(this.bricks[i]); this.bricks.splice(i, 1); } }我用的是filter方案因为代码更简洁、意图更清晰而且不会出现索引错乱的问题。砖块定义了isDestroyed这个标记后在碰撞回调里只需要把这个标记置为true等所有碰撞检测循环结束之后再做统一清理。这种“先标记、后清理”的模式还有一个额外的好处同一帧内球的残留速度如果穿越了多个砖块可以一次性处理掉不会出现一次移动只撞碎碎一块砖的死板情况。3.5 游戏状态管理开始、运行、暂停、结束弹珠游戏的状态不算复杂但如果不加管理代码会变成一团乱麻。这个源码里我把游戏分成了四个状态READY球停在挡板上等玩家点击、PLAYING球在运动、PAUSED暂时中断、OVER球掉了或者通关了。每个状态用一个枚举定义任何逻辑进来第一件事就是检查当前状态。这四个状态里最容易出问题的转换是READY到PLAYING。球停在挡板上的时候玩家点击屏幕球要“发射”出去。发射方向怎么做很多实现是固定45度角往右上发射但这样做的问题在于玩家手还没准备好球的飞行轨迹就是固定的后续全看挡板接得好不好玩家的主动权很低。我在源码里处理方式是在READY状态下挡板移动的同时球也跟着挡板移动保持相对位置不变。这样玩家可以先把挡板挪到自己觉得舒服的位置点击之后球从这个位置出发方向固定为斜上方75度。这个角度并不是45度因为75度更接近垂直方向球的初始轨迹更“高”能给玩家更多反应时间不至于一出场就直奔两侧墙体然后快速下坠。如果你想让玩家控制发射角度可以做触摸点与球位置的连线来确定方向但这个玩法对移动端来说操作门槛有点高普通玩家很容易把球发射到一个很刁钻的角度然后很快就丢了。作为默认方案固定75度是最稳的。状态管理还要考虑声音和特效的触发。我习惯把音效、粒子特效都挂在状态转换的代码里比如撞碎砖块时的得分飘字、球碰到挡板时的“咚”声这样代码结构统一不会出现声音和画面不同步的情况。4. 实操过程从空项目到可玩弹珠游戏4.1 环境准备与项目初始化动手实操之前先把Egret的开发环境搭好。这里我按当前最新稳定版的操作步骤写一遍流程基本固定到Egret官网下载引擎安装包目前主流是5.4.x系列选择带版的安装。安装完成后Egret自带一个可视化工具叫Egret Wing或者命令行工具。如果想走命令行确认egret命令能被系统识别。找一个工作目录执行创建项目的命令egret create BreakoutGame这个命令会生成一个完整的Egret项目模板里面包含src放TypeScript源码resource放资源egretProperties.json项目配置index.html入口页面等结构。打开src目录清掉默认的Main.ts里的内容自己写。之后在egretProperties.json里把entry对应的入口类指向我们自己的Main即可。我用Egret做项目基本都要重写Main模板自带的界面逻辑通常用不上。4.2 主要代码文件的核心内容按照前面模块划分Main.ts负责初始化舞台并创建GameSceneGameScene负责游戏主体生命周期和碰撞逻辑Ball.ts、Paddle.ts、Brick.ts这3个纯对象类只负责自身的数据和渲染。下面是实战中的关键段落。先是Main.ts的核心逻辑其实做得很少就是设置舞台大小把GameScene挂上去class Main extends egret.DisplayObjectContainer { public constructor() { super(); this.addEventListener(egret.Event.ADDED_TO_STAGE, this.onAddToStage, this); } private onAddToStage() { let gameScene new GameScene(); this.addChild(gameScene); } }然后是球的移动与边界反弹放在GameScene的帧循环里private update(deltaTime: number) { if (this.gameState ! GameState.PLAYING) return; this.ball.x this.ball.speedX * deltaTime; this.ball.y this.ball.speedY * deltaTime; // 左右墙壁反弹 if (this.ball.x this.ball.radius) { this.ball.x this.ball.radius; this.ball.speedX Math.abs(this.ball.speedX); } if (this.ball.x this.stage.stageWidth - this.ball.radius) { this.ball.x this.stage.stageWidth - this.ball.radius; this.ball.speedX -Math.abs(this.ball.speedX); } // 上墙反弹 if (this.ball.y this.ball.radius) { this.ball.y this.ball.radius; this.ball.speedY Math.abs(this.ball.speedY); } // 底部丢失判定 if (this.ball.y this.stage.stageHeight) { this.gameOver(); return; } this.checkPaddleCollision(); this.checkBrickCollision(); }这里面有个细节值得注意墙壁反弹时我用的是Math.abs和-Math.abs来强制修正速度方向而不是简单取反。为什么要这么做因为如果球的速度由于某些原因比如上一帧恰好嵌进了墙壁变成了“往墙外跑”的方向取反操作会让球继续往墙外冲穿模。用绝对值强制修正可以保证球永远被弹回场内这是所有碰撞处理里的一个通用技巧——当对象越界时先把对象拉回边界再修正朝向。挡板碰撞的做法是基于相对位置来决定反弹角度。这里重点讲一下这个源码里做了的一个“角度控制”优化如果球从左侧撞到挡板反弹后应该偏向左上方飞行撞到右侧偏向右上方。实现方式是影响球速度的X分量的符号和大小private checkPaddleCollision() { if (!this.checkCircleRectCollision( this.ball.x, this.ball.y, this.ball.radius, this.paddle.x, this.paddle.y, this.paddle.width, this.paddle.height )) return; let hitPos (this.ball.x - this.paddle.x) / this.paddle.width; // 将hitPos从 [0, 1] 映射到 [-1, 1] hitPos hitPos * 2 - 1; let angle hitPos * 60 * Math.PI / 180; let speed Math.sqrt(this.ball.speedX * this.ball.speedX this.ball.speedY * this.ball.speedY); this.ball.speedX speed * Math.sin(angle); this.ball.speedY -speed * Math.cos(angle); }这里的思想是挡板不同位置触发不同的反弹角度——最两侧偏转60度正中垂直向上。这个实现比固定反弹角度的体验好太多。如果只是简单翻转Y轴速度玩家只要站在球的正下方就能无限接球毫无操作空间而角度控制要求玩家“接住球的位置”直接影响下一段球的飞行路线这样才能衍生出进攻策略——打在挡板右侧能让球往右斜飞正好撞掉右边一片砖块。砖块碰撞后除了反弹和移除砖块有一个常用技巧我在源码里也实现了小球撞碎砖块后获得一个极小且随机方向的偏移避免球陷入“在两个砖块之间无限来回弹”的死循环。这个偏移量一般控制在0到0.2之间不会影响整体飞行轨迹但足以打破循环。4.3 一键启动与真机调试的小技巧写完代码后直接在egretProperties.json里的type字段设置game然后用命令行执行egret build egret runegret run会拉起浏览器预览你也可以在可视化工具里面直接点运行按钮。第一次运行如果控制台报错优先检查Main.ts的类名是否和egretProperties.json里的入口配置一致。这个错我遇到过两次每次都是因为改了文件名但没同步改配置文件属于新手最常见的问题之一。调试阶段有个小技巧我认为非常实用在Chrome开发者工具里可以自己手动执行Egret游戏内的指令。因为Egret的TypeScript编译后变成JavaScript全局变量都可从window上访问到。在Console里输入egret.MainContext.instance.stage看舞台对象输入this.ball.x看球的坐标。有一次排查跟帧率相关的问题我就是靠不断在Console里打印当前帧的deltaTime值很快定位到问题根源。真机调试方面Egret提供了一套远程调试方案但我个人建议用一个更朴素的方式egret run -server启动本地服务器之后手机和电脑连同一个WiFi手机浏览器直接访问电脑的局域网IP加端口就能在手机上看效果。这个方案比折腾一堆调试工具快多了还能同时验证触摸事件在移动端浏览器的表现。5. 常见问题与排查技巧实录5.1 碰撞穿模球直接穿过砖块或挡板这是弹珠游戏开发中最常见的问题根源通常有两个球速过快导致一帧内移动的距离超过了砖块的宽度或者碰撞检测的时机不对导致漏判。排查思路是先检查球速。我用MAX_BALL_SPEED限制最大速度为什么不直接取消上限因为小球在高速移动时即使你的碰撞检测逻辑再正确物理世界也是“离散”的采样——比如一帧的deltaTime是0.033秒球速是800像素每秒一帧就会移动26个像素。如果一块砖的宽度是28像素球从砖的左边飞到右边整个过程一帧就完成了如果你只检测球的当前位置是否在砖块的范围内就会完美错过中间“穿过砖块”的过程。除了限速更彻底的做法是“次像素检测”把一帧拆成若干个小步长每个小步长后检测一次碰撞。这个源码里用了一个简化的版本——每帧移动后不仅用球心位置检测还把球心上一帧的位置和这一帧的位置连成一条线段判断这条线段是否与砖块的矩形相交。线段与矩形的相交判定写起来也不算复杂感兴趣的话可以自己研究。不过我实测下来的结论是对于弹珠这种玩法限速小幅随机偏移已经足够用了次像素检测属于锦上添花不是必需。5.2 球在挡板上“抖动”或“卡住”球碰到挡板后如果挡板又马上移动过来“接住”了球就可能出现球在挡板上反复抖动的情况。原因是挡板移动后球的位置变成了挡板内部下一帧碰撞检测再次触发反弹球又弹回去了反复横跳。解决方法是碰撞触发后立刻把球从挡板内部“推出”到挡板表面。我通常在反弹逻辑后面加一句“校正位置”this.ball.y this.paddle.y - this.ball.radius - 1;这个-1是留出一像素的安全间隙。有了这个校正球碰到挡板后马上被重置到挡板上方不会留在挡板内部就不会出现抖动。同理砖块碰撞后也应该做类似的“推出”处理只不过推出方向要根据碰撞方向来定。5.3 不同设备上球速不一致这个问题前面讲过根源是没用deltaTime做速度积分。但还有一个更隐蔽的场景如果游戏的帧率忽高忽低比如因为某些手机性能波动从60帧掉到30帧再回到60帧用deltaTime算出来的速度其实也会有轻微偏差因为大跨度的时间差会让碰撞检测的采样点变稀疏。所以在GameConfig里deltaTime需要做一个最大限制。Egret传入的deltaTime一般是0.016或0.033这种平滑值但如果后台切回来一帧的deltaTime可能高达几秒。此时球会瞬间飞出去好远直接穿墙。我的做法是把deltaTime截断到Math.min(deltaTime, 0.033)超过这个值的按0.033算。这样一个2秒的大卡顿球最多走1帧的距离不至于直接飞出屏幕。卡顿恢复后游戏继续视觉上玩家损失一点点位置但不会因为一次后台切换就莫名其妙地Game Over。5.4 设计与代码中容易踩的其它细节坑还有一个我踩得比较深的坑是egret.TouchEvent.TOUCH_MOVE的监听目标。如果你把监听挂在stage上那么只要有触摸事件不管触摸点在哪个位置挡板都会跟着动。如果挂在挡板对象上会有一个问题挡板本身就是小面积的手指一旦滑出挡板范围触摸事件就不再触发挡板就停在原地了。所以挡板控制一定要挂在stage上而不是自己身上。布局相关的问题也很容易出。设计尺寸是750*1334但实际设备的宽高比五花八门。如果你的砖块区域不是居中布局而是贴顶布局在刘海屏或超长屏上底部会留大量空白看起来很业余。我最后采用的是砖块区域中线对齐的布局方式整个砖块层作为一个容器Sprite放在舞台的水平中线上垂直方向偏上40%的位置。这样无论屏幕多长砖块区域始终在视觉中心附近玩家的视线聚焦不会被破坏。5.5 这个源码后续还能怎么扩展升级最后聊聊扩展的方向。这个弹珠游戏的骨架非常干净给它加玩法非常顺手目前已经验证过的扩展方案有下面几种都可以在现有代码基础上直接改。扩展一道具系统。打碎特殊砖块后掉出道具加速、加宽挡板、多球、穿透、激光等。要在现有架构上实现并不难每种道具放一个数组移动逻辑和球类似只是达到底部就销毁拾取判断用同样的圆形与矩形碰撞检测。扩展二皮肤和关卡编辑。把砖块的生命值和颜色方案放到配置表里设计不同形状的砖块布局比如“S”形、“钻石”形关卡的排列组合瞬间多出几十种。砖块的声明周期从一碰就碎改成需要打N次才碎只需要在Brick里加一个hp字段在碰撞回调里hp--即可。扩展三排行榜和分享。Egret对微信小游戏、抖音小游戏都有发布支持。在游戏结束后把分数写入KV存储再调起分享接口让好友一起挑战分数。这个扩展能直接把游戏的传播属性拉满我前面提过的那位朋友就是靠分享助力机制带来了大量自然流量。做小游戏最怕什么最怕需求变来变去代码撑不住。而这个源码在结构上就已经考虑到了后续的灵活扩展所有核心逻辑都在GameScene里面所有可调配参数都在GameConfig里面想改玩法基本是在“参数之间的组合”而非“重写逻辑”。从这个角度来说用它作为自己第一个Egret实战项目或者面试的作品展示都是很稳妥的选择。本文还有配套的精品资源点击获取
返回列表