「Unity3D」(7)协程使用3种算法实现CameraShake震屏
来源:互联网 发布:资料整理软件 编辑:程序博客网 时间:2024/06/05 07:43
本文主要讨论CameraShake震屏的实现思路,但不仅限于震屏,震动算法可以震动任意属性,比如Position,Scale,Rotation,Color等等。
思路
震动,就是围绕某个固定点的波动,最后在回归固定点的过程。这个波动的模拟,有千千万万种,这里主要介绍3种,Random(随机),Periodic(周期函数Sin,Cos),PerlinNoise(柏林噪声)。
另外,在视觉预期上,震动过程是一个衰减的过程,所以在波动回归的时候需要加入渐进衰减的模拟,这样就会有更好的效果。
实现
同样,震动的实现,也有很多种,这里主要介绍使用协程的方式。我们首先实现一个协程函数。
public static IEnumerator ShakeRoutine( float magnitude, // 震动幅度 float speed, // 震动速度 float duration, // 震动时间 Func<float> OnGetOriginal, // 获取固定点坐标 Action<float> OnShake, // 获取震动数值 ShakeType shakeType = ShakeType.Smooth, // 震动类型 Action OnComplete = null // 完成回调){ // 震动耗时 var elapsed = 0.0f; // 随机起始点 var random = UnityEngine.Random.Range(-1234.5f, 1234.5f); // 获得固定点 var original = OnGetOriginal(); while (elapsed < duration) { elapsed += Time.deltaTime; var percent = elapsed / duration; // 当前波动 var rps = random + percent * speed; // 波动映射到[-1, 1] float range; switch (shakeType) { case ShakeType.Smooth: range = Mathf.Sin(rps) + Mathf.Cos(rps); break; case ShakeType.PerlinNoise: range = Mathf.PerlinNoise(rps, rps); break; default: range = 0.0f; break; } // 震动总时间的50%后开始衰减 if (percent < 0.5f) { OnShake(range * magnitude + original); } else { // 计算衰减 OnShake(range * magnitude * (2.0f * (1.0f - percent)) + original); } yield return null; } if (OnComplete != null) { // 完成回调 OnComplete(); }}
为了通用性协程构造比较繁琐,如果只针对Camera的震动可以写的很简洁。下面解读一下实现:
OnGetOriginal 为了获得震动的固定点,可以返回PositionX,ScaleY,ColorRed,等等。围绕这个固定点进行震动。
OnShake,每一帧协程计算出震动后数值,用于设置到target对象上。
协程每一帧计算一次震动,直到duration耗尽,回调OnComplete。
range 会根据不同的波动算法,映射到[-1, 1]区间,最后乘以衰减和振幅,就得到了震动后的数值。
Random 波动
代码并没有体现,因为测试发现,random在振幅比较大的时候,效果不太好,但如果振幅很小很小还是不错的。下面给出Random的写法。
// 依然需要映射到[-1, 1]range = UnityEngine.Random.value * 2.0f - 1.0f;
Periodic 波动
这里我使用Mathf.Cos + Mathf.Sin的方式,其实使用Mathf.Cos或Mathf.Sin也是可以的,只不过这里叠加会有加速的效果。周期顾名思义,不会像随机那样无序,单个数值会有震荡的效果,二维数值有转圈的效果。
PerlinNoise 波动
Unity内置实现了二维的PerlinNoise算法,其原理是在一个二维纹理上取值,效果会比Random来的平滑,一般应用于地形,水波纹等自然界元素的模拟。这里体现了一种用法,可以使用不同的策略去得到PerlinNoise的坐标。其坐标是类似Repeat模式的UV坐标。
如何使用
首先利用这个协程函数,构建一个协程执行函数。
public static void Shake( float magnitude, float speed, float duration, Func<float> OnGetOriginal, Action<float> OnShake, ShakeType shakeType = ShakeType.Smooth, Action OnComplete = null){ CoroutineExecutor.StartCoroutineTask(ShakeRoutine(magnitude, speed, duration, OnGetOriginal, OnShake, shakeType, OnComplete));}
这里我使用了协程管理器,也可以继承MonoBehaviour使用自身的协程启动。然后,在看如何使用。
public static void ShakePositionX( this Transform transform, float magnitude, float speed, float duration, ShakeTool.ShakeType shakeType = ShakeTool.ShakeType.Smooth, Action OnComplete = null){ ShakeTool.Shake ( magnitude, speed, duration, () => transform.position.x, (x) => transform.SetPositionX(x), shakeType, OnComplete );}
这里进行了扩展,可以方便的直接使用transform来做ShakePositionX,比如:
transform.ShakePositionX(10.0f, 100f, 1.5f, ShakeTool.ShakeType.Smooth);
更多定制
这里Shake只是震动了一个数值,当然也可以同时震动2或3个或更多。ShakePositionX只是扩展了position x,当然也可以扩展为 xy 或 xyz,或是Scale和Rotation。比如,如果震动XY可以这么写:
public static IEnumerator ShakeRoutine( float magnitude, float speed, float duration, Func<Vector2> OnGetOriginal, Action<Vector2> OnShake, ShakeType shakeType = ShakeType.Smooth, Action OnComplete = null){ var elapsed = 0.0f; var random1 = UnityEngine.Random.Range(-RandomRange, RandomRange); var random2 = UnityEngine.Random.Range(-RandomRange, RandomRange); var original = OnGetOriginal(); while (elapsed < duration) { elapsed += Time.deltaTime; var percent = elapsed / duration; var ps = percent * speed; // map to [-1, 1] float range1; float range2; switch (shakeType) { case ShakeType.Smooth: range1 = Mathf.Sin(random1 + ps); range2 = Mathf.Cos(random2 + ps); break; case ShakeType.PerlinNoise: range1 = Mathf.PerlinNoise(random1 + ps, 0.0f); range2 = Mathf.PerlinNoise(0.0f, random2 + ps); break; default: range1 = 0.0f; range2 = 0.0f; break; } // reduce shake start from 50% duration if (percent < 0.5f) { OnShake(new Vector2(range1 * magnitude, range2 * magnitude) + original); } else { var magDecay = magnitude * (2.0f * (1.0f - percent)); OnShake(new Vector2(range1 * magDecay, range2 * magDecay) + original); } yield return null; } if (OnComplete != null) { OnComplete(); }}
可以看到 OnGetOriginal, OnShake参数由float变成了Vector2,更多参数同理。
public static void ShakePositionXY( this Transform transform, float magnitude, float speed, float duration, ShakeTool.ShakeType shakeType = ShakeTool.ShakeType.Smooth, Action OnComplete = null){ ShakeTool.ShakeV2 ( magnitude, speed, duration, () => (Vector2) transform.position, (v2) => transform.SetPositionXY(v2), shakeType, OnComplete );}
「Shake Shake」
- 「Unity3D」(7)协程使用3种算法实现CameraShake震屏
- 「Unity3D」(6)协程使用IEnumerator的几种方式
- Unity3d 寻路NavMeshAgent使用实现
- Unity3D 使用Mecanim实现连击
- Unity3d协程实现倒数计时
- Unity3D 协程实现贪吃蛇
- Unity3D实现A*寻路算法
- Unity3D 海水多线程渲染算法实现
- Unity3D 海水多线程渲染算法实现
- 在Unity3D中使用uGUI实现3D旋转特效
- Unity3D协程介绍 以及 使用
- Unity3D协程介绍 以及 使用
- Unity3D协程介绍 以及 使用
- Unity3D协程介绍 以及 使用
- Unity3D协程介绍 以及 使用
- 关于Unity3D协程的使用
- Unity3D协程介绍 以及 使用
- Unity3D协程介绍以及使用
- SpringBoot的日志管理
- git-ssh 配置和使用
- [Office] 设置分散对齐
- 每个程序员都该知道的五大定律
- 程序员必须掌握的6种软技能
- 「Unity3D」(7)协程使用3种算法实现CameraShake震屏
- Linux┊理解devfs、sysfs、udev
- Linux下彩色进度条的实现
- [C#基础]c#中的BeginInvoke和EndEndInvoke
- hdoj 1020 Encoding (水题)
- 【4】Kotlin中使用RecyclerView
- cmd使用java -help可以看到关于agent参数
- 196算法的JavaScript实现
- Solr之集群管理Collection-yellowcong