在Unity实现屏幕抖动效果

想要实现屏幕抖动效果一般可以通过对摄像机Transform组件中的Position以及Rotate属性进行修改来实现。

我们先基于这个思路进行简单的实现。

using UnityEngine;
public class Shake : MonoBehaviour
#region Properties
[SerializeField]
[Tooltip("相机在三个轴向进行平移的最大位置;")]
Vector3 maximumTranslationShake = Vector3.one * 0.5f;[SerializeField]
[Tooltip("相机在三个方向进行旋转的最大角度;")]
Vector3 maximumAngularShake = Vector3.one * 2;
#endregion
private void Update()
    ShakeTransform();
private void ShakeTransform()
    transform.localPosition = new Vector3(
        Random.Range(-1.0f, 1.0f) * maximumTranslationShake.x, 
        Random.Range(-1.0f, 1.0f) * maximumTranslationShake.y, 
        Random.Range(-1.0f, 1.0f) * maximumTranslationShake.z);
private void ShakeRotate()
    transform.localRotation = Quaternion.Euler(new Vector3(
        Random.Range(-1.0f, 1.0f) * maximumAngularShake.x,
        Random.Range(-1.0f, 1.0f) * maximumAngularShake.x,
        Random.Range(-1.0f, 1.0f) * maximumAngularShake.x));
调用ShakeTransform()
调用ShakeRotate()

在这个脚本中我们通过Random来到达到相机在每帧都有不同的位置,以及旋转。

我们在此可以分析一下这样做的优缺点:

1、若在第一人称项目中使用不同位置的抖动可能会导致摄像机穿入墙体导致穿模“透视”的效果。

2、若同时使不同位置+不同旋转效果其实非常不好! 大家可以自己试一试。

3、使用Random使摄像机的运动杂乱生硬。

接下来我们想让摄像机移动的更平滑,可以使用Unity提供的[Mathf.PerlinNoise]( Mathf-PerlinNoise - Unity 脚本 API )来达到平滑的效果。

柏林噪声是一个非常好用的程序化噪声,大家感兴趣可以去了解了解。

这里直接贴代码了。

using UnityEngine;
public class ShakeableTransform : MonoBehaviour
    #region Properties
    [SerializeField]
    [Tooltip("相机在三个轴向进行平移的最大位置; 默认使用旋转方式达到抖动效果; 此参数无效")]
    Vector3 maximumTranslationShake = Vector3.one * 0.5f;
    [SerializeField]
    [Tooltip("相机在三个方向进行旋转的最大角度; 默认使用旋转方式达到抖动效果; 此参数有效")]
    Vector3 maximumAngularShake = Vector3.one * 2;
    [SerializeField]
    [Tooltip("相机抖动频率")]
    float frequency = 25;
    [SerializeField]
    [Tooltip("相机由抖动状态恢复至平稳状态的速度")]
    float recoverySpeed = 1.5f;
    [SerializeField]
    [Tooltip("相机抖动强度")]
    float traumaExponent = 2;
    //获取随机种子达到每次运行时抖动的效果为随机抖动
    private float seed;
    private float trauma;
    #endregion
    private void Awake()
        seed = UnityEngine.Random.value;
    private void Update()
        if (Input.GetKeyDown(KeyCode.Space))
            InduceStress(1);
        ShakeRotate();
    /// <summary>
    /// 传入stress控制相机抖动程度
    /// 最好将赋值范围规定在 0 - 1之间
    /// 函数本身对传入数据进行了阶段操作Clamp01
    /// </summary>
    /// <param name="stress"></param>
    public void InduceStress(float stress)
        trauma = Mathf.Clamp01(trauma + stress);
    /// <summary>
    /// 通过位移达到抖动效果
    /// </summary>
    private void ShakeTransform()
        float shake = Mathf.Pow(trauma, traumaExponent);
        transform.localPosition = new Vector3(
            maximumTranslationShake.x * (Mathf.PerlinNoise(seed, Time.time * frequency) * 2 - 1),
            maximumTranslationShake.y * (Mathf.PerlinNoise(seed + 1, Time.time * frequency) * 2 - 1),
            maximumTranslationShake.z * (Mathf.PerlinNoise(seed + 2, Time.time * frequency) * 2 - 1)
        ) * shake;
        trauma = Mathf.Clamp01(trauma - recoverySpeed * Time.deltaTime);
    /// <summary>
    /// 通过旋转达到抖动效果
    /// </summary>
    private void ShakeRotate()
        transform.localRotation = Quaternion.Euler(new Vector3(
            maximumAngularShake.x * (Mathf.PerlinNoise(seed + 3, Time.time * frequency) * 2 - 1),
            maximumAngularShake.y * (Mathf.PerlinNoise(seed + 4, Time.time * frequency) * 2 - 1),