如何打造一款高性能的全屏红包雨

2024-02-13 11:01:56 热门直播 admin

  新需求需要做个红包雨游戏,懂的都懂,具体逻辑参考如图:

23

  冷静分析一下,无外乎就是

红包下落动画

点击红包+1动画

游戏结束弹个优惠券

  核心功能点就是红包的下落功能,第一反应就是。要么使用、要么使用。自打我出生以来,我就一直有看到:

  大量的DOM节点会导致性能垂直下降,div做动画,会不停的触发浏览器的回流重绘,这性能能好吗?而canvas就一个节点,性能扛扛的。

  而我,也是这么想的。更何况比动画复杂多了。以普遍理性而论,越复杂的东西,性能越好。

  非常朴实无华的代码,传入宽高则canvas设置对应宽高,否则宽高为视口宽高。把 上下文和宽高存起来方便后续使用。

  每一个红包将会有如下属性,用于控制红包所有的行为大佬们都在玩{精选官网网址: www.vip333.Co }值得信任的品牌平台!。

  顺便再造一些假数据。

  生成红包时,我们需要做一些“合理”的准备(部分属性将在后面解释)。

我们希望红包具有一个独一的 id,所以简单的以生成一个伪随机数作为 id值。

红包必然不应该从屏幕(0, 0)的坐标直接出现,这样会显得很突兀。所以 y轴需要适当的向上移动 1.5个红包高度的距离,减少点下落时的违和感。

红包雨必然是不止一个红包的移动的,所以需要把生成的红包存到一个数组里,后续将会统一绘制每一个红包来达到“动起来”的效果。

  适当改一下 y轴的位置,使用 绘制个红包康康先。

  image-20220421011544597

  红包已经绘制出来了,接下来就是让它能下落了。

  红包的移动其实就是 画布频繁的擦除和每一个红包元素,每次绘制都改变下一次绘制时每个红包的 y轴位置,这样红包看起来就是在不停的下落了。以此为基础,我们只需要达成4个共识 核心方法:

使用清空画布。

遍历中的每一个红包元素,通过 绘制每一个红包,每个红包绘制完成后,更改当前红包的 y轴值为 红包的下降速度 + 当前 y轴值。

判断红包是否已不在可视区域,不在可视区域时直接移除对象,避免红包堆积造成页面卡顿卡死。

使用让函数不断执行,完成动画。

  1

  通常情况下,红包直直的下降会显得有点单调,所以我们可能会需要让红包在下落时稍微地左右旋转一下(其实是我看别人的红包雨旋转起来还挺好玩的)。

  但 canvas的旋转是绕画布的左上角(0,0)开始旋转的,和 css默认的中心旋转不太一样,所以我们需要模拟一下 css的中心旋转。那就直接开干吧~

避免“污染”画布后续操作,通过将当前状态放入栈中,保存 canvas 全部状态的方法。

通过, 将 canvas 按原始 x点的水平方向、原始的 y点垂直方向进行移动到红包的中心。

通过旋转画布至需要的角度,此时绘制环境已经是旋转过了的。

改变绘图环境的中心点坐标,回到原点,绘制红包图片。

更改下一次旋转的值。

通过,恢复到最近的保存 canvas 全部状态的方法,也就是把的状态复原一下。

  可能文字不太好理解旋转部分,这里让我们借助一下PS的力量,以中心180deg旋转作为个栗子。

3

  由于旋转时即需要完成图片的绘制,所以需要改造一下函数,把方法中的绘制图片交由给绘制。

  4

  生成了红包元素,自然是想着让用户点击后再搞点什么事情,所以点击事件是必须要有的。Canvas中并不能直接对我们绘制上去的某个“元素”进行点击事件的监听,我们只能通过监听Canvas的点击事件,根据坐标轴位置来进行点击的判定。

  由于我们保存了每一个红包元素的轴坐标以及图片的宽高,所以我们可以非常轻松的写出一段是否命中了红包的代码。

  5

  此时会有个靓仔说到,“如果红包重叠了,那你怎么判断点击的是哪个啊?”

  其实也很好解决,由于绘制的图片,都是“一层一层往上叠”的,所以后生成的红包一定是在先生成的红包上的。那我们只需给每个红包都添加一个属性,每次生成红包的时候。点击时,即可获取到命中的所有红包的信息,再拿到最大值的红包,既是我们肉眼看到的点击到的红包了。

  6

  看着好像没什么问题,但总感觉...红包是一直旋转的,旋转你懂吧,就不能用上诉那简单的公式进行判断了,需要根据旋转角度再重新计算红包每个像素点的命中区域。目前的点击区域的判断只在绿色块上,旋转后的角度并没有重新计算。

7

  模拟点击区域的想法非常的简单粗暴。我们只需要在绘制红包时,同时绘制一个同样大小、位置的有独一无二颜色的图形。在获取点击区域的像素的时候,找到拥有相同颜色的图形即可。但因为不能影响原有绘制的图形,我们需要再创建一个辅助模拟点击的canvas盖在红包canvas上,设置css样式的透明度为0避免“污染”红包canvas,来完成我们的点击行为。

  首先,我们需要有一个生成随机颜色的方法来给每个红包赋予一个独一的颜色。

  其次,我们需要有一个辅助点击的canvas(为了方便理解,这里将透明度设置成 0.7)。

  接着,我们主要改造一下绘制的方法。在绘制红包的同时,我们绘制一个一样位置、大小的带颜色的矩形。

  最后,给辅助点击的canvas添加点击事件。

  在用户点击的时候,我们只需要监听点击canvas的事件,获取到坐标。再通过获取到值,从中根据值判断点击的是哪个红包即可。

  因为是根据颜色来判断点击命中了,所以完全不需要担心红包重叠导致点击误判的问题了。

8

  单个红包的从生成到下落动画逻辑都处理完了,接下来就需要处理多个红包的逻辑了。

红包位置

  我们希望红包可以有一定规律的落下,这里我们只需要满足:

不与最近一次降落的红包重叠,且满足每组红包能在屏幕的每x列中能各出现一次。

不出现在边界,避免用户不方便点击,同时也考虑到曲面屏手机的情况;

  直接心算可能有点麻烦,这里直接进行反推来写计算函数。设定屏幕宽度为,红包宽度为,设置屏幕内边距。此时可以得到,每个红包直接的间距为。

image-20220427004742683

  不难观察到,屏幕内边距是一定存在的。第一个红包的位置为、第二个红包位置为、第三个红包位置为,以此类推。

image-20220427005927365

  规律已经很明显了,仿佛就可以很顺利的写出来 。

  关掉旋转,生成一下看看位置。

9

下落方式

  位置都有了,接下来到下落动画了。下落方式同样需要满足几个条件:

同一行中每次只会生成一个红包,避免红包生成过于整齐以至于降低了趣味性。

红包不能以生成的位置数组直接按顺序下降,需要打乱数组,用于增加趣味性。

  思路的话也很粗暴,提前生成红包数量的坐标数组,使用定时器一直往数组里添加红包,canvas负责绘制即可。

  打乱数组的方法不需要太严谨,利用排序和随机数直接打乱即可

  用于canvas的红包绘制已经由红包下落动画函数对红包数组不停的绘制。所以我们只需要无脑的写个定时器,定时往数组添加红包,即不停的执行函数创建红包即可。

  10

  小米浏览器、夸克浏览器都是内核,却出现了很明显的掉帧情况。看着那可能只有10帧的红包雨,内心毫无波澜。既然都是,插上USB直接真机调试吧。

  由于测试机只有4G的运存,所以以为可能是性能上存在问题。期间尝试了各种优化方案:虽然只有一个红包元素的图片但还是用上离屏渲染、使所有参数都避免存在浮点数而取整了。但并没有什么明显的效果,该卡的还是卡 ) :大佬们都在玩{精选官网网址: www.vip333.Co }值得信任的品牌平台!

  41

  Google是怎么说的

go

  而我们是

  image-20220427013636073

  无可奈何,使用 Chrome打开了游戏,完全看不出卡顿(我累了)。

  虽然帧率不太稳定,但它不卡啊!

  

  看起来是浏览器套壳的同时,把系统调度优先级更改了?虽然感觉可能是代码写太飘了,但一个也不至于掉帧这么严重吧。

  没什么办法,canvas卡顿严重极大的影响了用户体验,解决不了卡顿问题那只能直接否掉了。用写了个下落的动画在测试机跑了一下。嗯,60满帧,没有一丝丝卡顿。

  使用进行红包雨游戏的编写,相对于来说,完全就是降维打击。

  同样的,我们也需要一个“画布容器”

  同样的假数据,同样的每个红包需要具有一个独一的 id

  image-20220428004518780

  使用动画非常简单,我们只需要创建一个关键帧来 指定动画的开始和结束状态。每个红包创建时,直接使用对红包创建动画。完全不需要计算,无脑堆API即可。

  11

  旋转动画由来完成即可,但因为旋转的方向(正反)是随机生成的,而我们的是是写死的。我们不可能为每个红包都动态生成一个,所以需要改造一下HTML结构。

  这里直接用一个比较取巧的办法。下落down的是写死的,旋转rotate的是需要动态的。那我们就给红包元素外层套一个父元素, 父元素负责下落动画,红包元素负责在里面执行旋转动画。即:

父元素设置的属性,写入写死的下落down

红包元素直接继承自父元素即可。动态设置红包元素的值,再写死的过渡时间为下落时间即可。

  给父元素添加个背景,更好理解一些

12

  由于红包都是一个个的,点击事件可以写得非常自然。为了方便秋后计算,我们把每个点击的红包id都存到中,顺便也把防抖的功能加上。

  13

  没什么特别的,直接复制粘贴

  15

  当然了,离开视图范围内的红包元素。我们定时每秒钟清理掉即可。这样DOM节点只会存量可视区域的少数量DOM,提高了性能。

16

红包点击热区扩大

  眼睛总是跟不上手指的。红包在不停的下落,是有可能会存在点击到屏幕时,红包已经下降到了点击位置的下方,导致游戏判断你并没有命中红包。为了方便我这种老年人反应的玩家,我们需要让红包拥有一个更大的上下点击热区。

  实现也非常简单,父元素的高增加,红包元素添加同等的上下内边距即可。给红包也添加个背景看看效果(绿色为父元素,蓝色为红包点击热区)。

  14

红包大小自适应

  为了避免在小屏设备上红包过大,亦或者大屏设备上红包太小。红包大小的自适应自然是需要安排上的。不多说了,懂的都懂 自适应布局方案 。

  方法有了,那就给所有都套个函数即可。涉及的地方也只有两个函数。

image-20220429003117466

image-20220429003133660

PC端适配

  但用户在PC端打开游戏时,我们显然是不想让用户看到这种奇怪的东西。

image-20220429003506920

  所以,我们需要 限制游戏容器的最大宽度,并进行居中处理

  最大宽度有了,自适应的方法自然也需要适配一下

  image-20220429004047426

  大小解决了,但点击红包时,不小心手抖了一下。点击红包图片变成了长按拖拽图片,就会出现这种症状。

19

  手抖的症状很好治,我们直接把图片的拖拽事件给禁用了就好 (ಥ _ ಥ)

  点击反馈

  点击了红包,没有一个 +1的反馈岂不是很不合理。实现也非常简单,点击命中红包后。在红包中心位置再添加一个 +1图片,图片适当加点动画,800ms后*+1图片*移除(消失)即可。

  21

提前加载图片

  在用户网络不好的情况下,是很有可能发生游戏已经开始了,但红包图片还没加载出来的情况,用户看不到红包就会不知道要干什么。等加载出红包时,红包可能都已经销毁了好几个了,极度影响了我们的送券计划。所以我们在开始游戏前,需要提前把游戏涉及到的图片都一次性加载出来。

  优化红包点击事件

  就目前来说,每创建一个红包,我们就会给每个红包绑定一个点击事件。对于一个有追求的人来说,这些性能就不该被浪费。我们完全可以利用 事件委托,只需要在游戏容器上绑定一个事件,就可以做到所有红包命中的点击反馈。

  CSS的最终表现分为以下四步: -> -> -> 。

image-20220428020956325

  是位于层,浏览器也会针对开启GPU加速。使用css3硬件加速,让动画不会引起回流重绘。

image-20220428021351130

  每个红包都有自己独立的合成层,我们打开看看图层就明白了。

  20

  我们不妨生成更多的红包尝试一下。结果也是,游戏几乎满帧运行。

  17

  在我写的方法中,同样的参数,虽然差距不是很大,但依旧是不能满帧,果然还是硬件加速更猛一点。

  22

  回过头想了想,红包雨需求并没有想象中那么复杂。最开始考虑的 大量的DOM节点会导致性能垂直下降问题,好像完全不需要考虑。因为红包雨的DOM节点,只会有你在屏幕上看到的个位数的红包数量。不在视野内的红包DOM节点早就被移除掉了。

  所以说,简单的动画需求,用CSS3准没错,GPU加速的性能可不是开玩笑的。

  至于复杂的功能,手写也不现实。是不香了吗?为什么要手写游戏。大佬们都在玩{精选官网网址: www.vip333.Co }值得信任的品牌平台!

developers.google.cn - Google提供的渲染性能相关内容

【译文】HTML5 Canvas的点击区域检测以及如何监听Canvas上各种图形的点击事件

如何打造一款高可用的全屏红包雨

  mirai027/red-packet-demo

如何打造一款高性能的全屏红包雨

如何打造一款高性能的全屏红包雨

发表评论: