查看原文
其他

全网都在用这个表情,你知道它是怎么实现的吗?

如果你是微信表情的“资深”用户,那么这个表情你一定不陌生,如果配合使用会起到意想不到的效果。
话说 2023 年春节微信上线了两款新的表情:兔年新春表情、王者新表情。用户在微信聊天中输入“兔年快乐”、“新年快乐”、“恭喜发财”就会出现一些兔子飞奔而过,而输入“王者荣耀”、“排位”、“上分”、“变鹿”、“王者摇好运”就会出现王者荣耀的瑶变神鹿的表情,还有机会掉落福袋参与抽奖。

兔年表情和王者荣耀表情是由Cocos制作而成

火爆全网,不少用户玩得不亦乐乎。这种微信表情称之为全屏表情。自从微信2021年上线“炸弹”、“烟花”、“爆竹”、“庆祝”等表情后,几乎每年微信都会更新上线一些有趣的表情。

我突发奇想,问问 ChatGPT "微信全屏表情是用什么实现的",结果得到的答案是

what ?!ChatGPT 竟然说微信全屏表情是帧动画实现的,还反驳我。看来大家对我们使用的技术还是有一些误解,因此有必要写篇文章分享一下。

从产品需求说起

这事还得从产品需求说起,当时产品提出全屏表情需求的表述是这样的:

“想象聊天界面是一个现实世界,里面的聊天气泡就像现实世界的物体,比如炸弹抛在空中,就像真实世界的一个炸弹爆炸,聊天气泡这些物体会对此有物理反应,炸裂了,炸掉落了或炸黑了之类的。聊天气泡掉落过程应该像有重力感应一样,有掉落加速度。掉到地上应该有物体碰撞反应。”

聊天气泡是物体?还要聊天气泡对表情做出物理反应?! 这脑洞可以,我们从来没从这个角度来看待聊天界面。这是一个非常有趣的需求,小伙伴们瞬间来了兴趣。

这样的需求如果用 iOS/Android 的基础 UI 控件肯定实现不了如此炸裂的效果,那能不能用普通表情雨那样,用帧动画来实现呢?

为什么不能用帧动画

产品定位是想要“还原现实世界”,让用户有更真实的体验。比如“炸弹”表情,希望是从用户发出的消息气泡往聊天中间形成抛物线,而消息气泡位置是不确定的,那不是要实时计算?爆炸效果还希望形状、颜色、大小、位置都是随机组合的,每次触发的特效样式都不一样。这样特效可以无限叠加,用户们可以刷屏,比如,大家一起放满屏的“烟花”,这就很有趣了。

炸弹抛物线需要根据情况实时计算以求效果真实生动

那如果用帧动画实现能不能实现一定范围内的随机?

我们以“烟花”为例,烟花颜色取范围内随机、形状大中小、烟花密集、烟花扩散方式、烟花是否带尾巴、用户背景为亮或为暗。总计 72 种组合方式。

如下图中,每个文件都代表着一种烟花模式,部分参数取值还是某个区间,所以最终效果会远比72种要多,特别是粒子系统。

假设用帧动画来实现这些烟花效果,每种组合形成 5 秒 30fps 1024 的帧动画,那么 72 种组合的话,共需要 600M 左右的资源,这些资源如果从后台下载就会使用大量用户流量,如果是本地预生成,也会占大量用户手机磁盘空间。在内存消耗上,也是同理,由于“刷屏”的需要,多种“烟花”同时在屏播放,帧动画需要同时加载多份资源。帧动画还无法解决表情的强互动、强实时方面的需求。

总体来说,这个需求场景下,帧动画并不适用。

为什么选择“游戏引擎”

而不是其他渲染方案

那为什么选择使用“游戏引擎”而不是其他渲染方案呢?

原因有很多,其中有一个比较大的是:还原效果。由于我们的美术同学非常熟悉使用游戏引擎制作特效,他可以将设计稿通过游戏引擎已有的成熟工具链及功能,进行快速制作,在保证效果还原的同时,也保证了短时间周期内的可交付。最终,我们选择游戏引擎来实现最终效果,并最终实现了我们项目初始定下的关于效果的目标:

设计“所见即所得”:美术直接导出的游戏资源,前端添加逻辑导出为游戏工程,可以直接运行。

低资源占用:独立进程,在主进程只有3MB左右的内存占用,对于聊天功能的影响较小。

拓展性:Web端可以运行图灵完备的JavaScript和WebAssembly等,配合客户端 JSAPI 可以实现丰富的互动效果,给了后续产品场景极强的想象力。

低实现成本:客户端只需要做简单的管理,真正的效果实现直接由游戏引擎搞定,保证及时上线。

低学习成本:在我们的场景下,或许是0学习成本,因为美术同学本身非常熟悉游戏引擎的使用。

代码复用:由于逻辑代码都在前端,因此只要双端都能支持js+wasm,就能保证双端效果一致且代码可以直接复用。

使用引擎进行逻辑的实现以及通信框架的搭建后,美术同学在游戏引擎中制作的特效prefab,可以完美地在客户端中还原出来,同时达到了产品与设计上的预期,并且对后续修改与迭代非常友好。当然,在搭建这个框架的过程中,并非一帆风顺,开发的过程中几乎全程都是写出部分功能并自测后,在转向开发其他功能的过程中,就发现之前写法的很多问题,然后就得开始边实现新功能边重构旧功能。所以,总体的技术选型如下图:

多说一个选择题:用游戏引擎是采用原生实现还是前端实现呢?很显然,原生实现需要在微信内集成一个游戏引擎,对于微信包体积增量比较大,比较难以接受。所以,我们采用了前端实现的方案。

我们具体是怎么实现的

利用浏览器对JavaScript、WebAssembly代码的动态执行能力,将从游戏引擎导出的经过特殊处理的WebGL工程,通过JS框架层进行通信桥接,并通过开发工具打包上传,最终在客户端解包,运行并渲染在WebView上。该WebView在进入微信会话后,会被添加到当前的ViewTree中,并处于最顶层,禁用交互且透明。当用户在客户端发送特定表情后,通过WeixinJSBridge将其该消息通知到前端公共库中预处理,再层层传递到前端JS框架层,JS胶水,最终到达WebAssembly,达到调用游戏引擎中的方法,动态实例化特效Prefab的效果。整体框架如下图:

这种需求在游戏领域很常见,粒子特效、实时、随机都是成熟的解决方案了。比如用游戏引擎的粒子系统相比之下就更容易实现烟花表情的随机特效,从发出烟花开始的拖尾轨迹使用Trails模块实现,烟花主体就可以使用如Noise、Color Over Lifetime等模块实现,烟花的泛光效果可以Bloom实现,烟花炸开后的闪烁效果则可以使用SubEmitter模块实现。

炸弹爆炸、烟花的播放是可以用游戏引擎实现的,但是,关于聊天消息气泡的拟物化就难了。“难道要把聊天消息气泡用游戏引擎实现?”这个方案很快就被我们拍死了,微信的聊天消息类型和展现样式太多了,里面的逻辑也非常复杂。

我们最终的解决方案就是全屏表情在聊天表面用游戏引擎实现播放,聊天消息气泡的效果用原生实现,如果要物理跌落效果,可以游戏引擎控制气泡位置来实现。如下图

只增加了644K的代码包

基本实现方案已经有了,但想要最终上线,微信包体积的增量一定要控制住:

  1. 由于表情只会使用到游戏引擎里很少的模块,所以需要精减一下,只包含必要的模块即可。

  2. 移除场景中默认的天空盒资源,微信全屏表情不需要天空盒。

  3. 使用压缩纹理,满足美术要求的情况下,让资源更小。

等等。

最终我们只需要增加644K的代码包体积,满足上线要求。

引擎技术的更多可能

在微信的聊天,追求的是轻量高效,对于引擎的需求相对保守,而小程序由于天生的开放性,会为引擎技术带来更多的可能。

小程序的开发者可以在微信中,使用引擎能力,创建更加生动逼真的“视觉奇观”,为用户提供更加身临其境的体验。我们会持续在引擎技术上进行探索,在 “轻量高效” 与 “逼真生动” 之间,探索引擎技术的边界。

—END—


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存