Cocos 屏幕震动完整指南:5 种从线性插值到物理模拟的打击感实现方案
2026/9/17 21:44:30 网站建设 项目流程

Cocos 屏幕震动完整指南:5 种从线性插值到物理模拟的打击感实现方案

【免费下载链接】cocos-engineCocos simplifies game creation and distribution with Cocos Creator, a free, open-source, cross-platform game engine. Empowering millions of developers to create high-performance, engaging 2D/3D games and instant web entertainment.项目地址: https://gitcode.com/GitHub_Trending/co/cocos-engine

在 cocos-engine 这类跨平台游戏引擎里做战斗反馈,屏幕震动是成本最低、感知最强的手段之一:一次爆炸、一脚重击,画面抖一下,"打中了"的信号就传给了玩家。本文基于 Cocos 引擎的真实模块(噪声类、刚体组件、游戏时间管理),给出 5 种由浅入深的屏幕震动实现思路,并附上选型对照表和参数经验值,帮助你在打击感与性能之间快速找到平衡点。

原理先行:震动是一条受控的位移曲线,而不是随机抖屏

很多"假震动"的根源是把震动理解成了"每帧给相机加一个随机数"。随机数每帧独立取值,相邻帧之间没有关联,画面会呈现出电击般的跳动,这就是生硬感的来源。

更准确的理解是:震动的本质是一条随时间衰减的位移曲线。想象一根被拨动的音叉:初始振幅最大,之后按固定节奏来回摆动,振幅越来越小,最后归零。好的屏幕震动就是模拟这种"起振—衰减—归零"的过程——用一个强度包络控制"现在该抖多大",再用一个平滑变化的驱动源(插值、噪声或弹簧)决定"这一帧往哪个方向抖"。

这个认知会直接决定你后面所有代码的写法:先算强度,再算方向,两者相乘得到最终位移。

分层实现:从 5 行代码到物理模拟

用线性插值快速实现基础震动

入门方案只做一件事:让强度从触发值平滑降到 0,每帧乘一个随机方向。

import { Vec2 } from 'cc'; class LinearShake { intensity = 0; trigger (power: number) { this.intensity = power; } update (dt: number): Vec2 { const off = new Vec2( (Math.random() * 2 - 1) * this.intensity, (Math.random() * 2 - 1) * this.intensity ); this.intensity += (0 - this.intensity) * Math.min(dt * 6, 1); return off; } }

方向仍是逐帧随机的,所以连续帧之间会有跳变。它适合一次性点击反馈、低配机型的兜底方案,不需要任何额外依赖。

用 ParticleNoise 实现连续噪声震动

Cocos 自带一个 Perlin 风格噪声类 ParticleNoise(源自粒子系统模块),noise(x, y, z, min, max)返回平滑连续的值。把时间当作坐标喂给它,就得到一条天然平滑的曲线:相邻帧的值彼此相关,抖动就有了"惯性"。

import { Vec2 } from 'cc'; import { ParticleNoise } from 'cocos/particle/noise'; class NoiseShake { private noise = new ParticleNoise(); private t = 0; intensity = 0; update (dt: number): Vec2 { this.t += dt; const x = this.noise.noise(this.t * 8, 0, 0, -1, 1); const y = this.noise.noise(0, this.t * 8, 0, -1, 1); this.intensity += (0 - this.intensity) * Math.min(dt * 4, 1); return new Vec2(x * this.intensity, y * this.intensity); } }

时间系数8就是震动频率,改大改小即可换挡。爆炸、地震这类需要"晃得自然"的场景首选它,单次调用开销极低,移动端毫无压力。

物理模拟震动:让刚体替你算衰减

如果你震的对象本来就有刚体(比如被踢飞的箱子、震动的镜头代理节点),可以直接借用物理引擎:施加一个冲击冲量,阻尼负责衰减,完全不用手写衰减逻辑。

import { Vec3 } from 'cc'; import type { RigidBody } from 'cocos/physics/framework/components/rigid-body'; function impulseShake (body: RigidBody, dir: Vec3, power: number) { const force = new Vec3(dir.x, dir.y, dir.z).multiply(power); body.applyForce(force); body.linearDamping = 0.85; // 控制衰减速度,值越大停得越快 }

参考 RigidBody 组件源码 中applyForce的签名,还可传入相对作用点制造扭转。只推荐给"震动的对象本来就参与物理模拟"的场景,纯为抖个屏幕而引入刚体,开销明显偏高。

多频叠加:做出"主震 + 余震"的层次感

单一频率的噪声听起来像"嗡嗡",叠加多个频率就有了层次:低频给大范围晃动,高频给细碎的毛刺感,很像真实地震中先晃后颤的过程。

import { Vec2 } from 'cc'; import { ParticleNoise } from 'cocos/particle/noise'; function multiFreqOffset (n: ParticleNoise, t: number, amp: number): Vec2 { const main = n.noise(t * 2, 0, 0, -1, 1); // 低频:大晃动 const sub = n.noise(t * 10, 0, 0, -1, 1); // 中频:节奏感 const fine = n.noise(t * 28, 0, 0, -1, 1); // 高频:细碎毛刺 const v = (0.6 * main + 0.3 * sub + 0.1 * fine) * amp; return new Vec2(v, -v * 0.8); }

权重0.6 / 0.3 / 0.1是偏保守的起点,先保证低频主导。剧情演出、大 BOSS 登场这类需要"有呼吸感"的镜头用它;成本只是多了两次噪声求值,仍属轻量级。

选型对比:一张表看清开销与适用场景

方案计算开销效果质感适用场景设备建议
线性插值最低生硬,有跳变点击反馈、低配兜底全设备
单频噪声平滑自然爆炸、通用冲击全设备
多频叠加中低层次丰富剧情演出、大型事件中端及以上
物理模拟较高真实联动对象本就参与物理中高端,低端慎用
实时动态源(多源混合+池化)取决于源数量可控性最强弹幕、连续打击做好对象池后全设备可用

一句话结论:默认选单频噪声,需要层次感升级多频,涉及刚体才考虑物理方案。

调试与参数经验

让震动在不同设备上表现一致 ⏱️

震动曲线一旦和帧率耦合(比如"每帧衰减 5%"),高刷屏上就会衰减得比低帧率设备快一倍。正确做法是让所有时间推进都乘以真实帧间隔dt(参考 Game 时间管理 的deltaTime),而不是每帧写死一个比例。触发时刻的对齐可以用game.totalTime取统一时间基准。

多震动源冲突如何处理 🔀

子弹连续命中时,如果每个源都各自覆盖相机位移,后触发的会直接"吃掉"先前的效果。推荐一个统一的震动管理器:新震动画进列表,每帧累加所有源的位移,强度低于阈值的回收进对象池,避免高频创建对象。

import { Vec2 } from 'cc'; import { LinearShake } from './linear-shake'; // 上文基础震动类 class ShakeManager { private free: LinearShake[] = []; active: LinearShake[] = []; add (power: number) { const s = this.free.pop() ?? new LinearShake(); s.trigger(power); this.active.push(s); } update (dt: number): Vec2 { const off = new Vec2(); for (const s of this.active) off.add(s.update(dt)); for (let i = this.active.length - 1; i >= 0; i--) if (this.active[i].intensity < 0.01) this.free.push(this.active.splice(i, 1)[0]); return off; } }

同方向多源叠加时记得设一个总强度上限(clamp),防止五个源同时触发把画面抖出屏幕外。

强度与衰减的取值区间(经验值)

以"位移单位 = 世界单位"为量纲,下面这组区间在多数项目里可以直接作为调参起点:

  • 爆炸/重击:初始强度 3.5~5.0,包络衰减较快(2~3 秒内归零);
  • UI 点击反馈:初始强度 0.8~1.2,衰减要干脆,避免"余味太长"显得廉价;
  • 剧情长镜头:初始强度 1.5~2.5,衰减放慢,配低频噪声更贴合叙事节奏。

频率上同理:time * 2左右是低频(大地震),time * 8~10是中频(通用),time * 24~32是高频(细碎)。先定包络(多久停),再定频率(怎么晃),顺序反了很难收敛。

进阶话题 🚀

方向性震动:3D 场景里常只想让画面在 XY 平面晃、保持纵深稳定。做法是把噪声映射到 X、Y 两个正交轴(如noise(t*2.3, 0, 0)noise(0, t*1.8, 0)),第三个轴固定为 0,天然得到 2.5D 效果。

距离衰减:震源到相机的距离越远,强度越低。取1 / (1 + k * distance)这类平滑衰减即可,近处爆炸才值得"满震"。

帧率自适应:除了用dt驱动外,还可在帧耗时超阈值时自动降低高频分量权重——高频毛刺对感知贡献小,砍掉它几乎无感,却能省下一次噪声求值。

收尾:五条可执行清单

  1. 新项目默认用单频噪声震动,把线性插值版留给低端机降级分支。
  2. 所有时间推进一律乘dt,杜绝"每帧写死衰减比例"这类帧率耦合写法。
  3. 多源震动走统一管理器 + 对象池,并给总位移设上限。
  4. 调参顺序固定为:先包络(强度与衰减时长)后频率(时间系数),用经验区间做起点而非从零猜。
  5. 把相机位移逻辑抽成独立组件,方便在真机(尤其中低端安卓机)上逐方案 A/B 观察表现。

【免费下载链接】cocos-engineCocos simplifies game creation and distribution with Cocos Creator, a free, open-source, cross-platform game engine. Empowering millions of developers to create high-performance, engaging 2D/3D games and instant web entertainment.项目地址: https://gitcode.com/GitHub_Trending/co/cocos-engine

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询