441 lines
16 KiB
C#
441 lines
16 KiB
C#
using FirstGearGames.Utilities.Maths;
|
|
using UnityEngine;
|
|
|
|
namespace FirstGearGames.SmoothCameraShaker
|
|
{
|
|
public class ShakerInstance
|
|
{
|
|
#region Types.
|
|
private class NoiseData
|
|
{
|
|
public NoiseData(float variance)
|
|
{
|
|
_variance = variance;
|
|
RandomizeGoal(true);
|
|
}
|
|
|
|
#region Private.
|
|
private readonly float _variance;
|
|
/// <summary>
|
|
/// Current noise value.
|
|
/// </summary>
|
|
private float _currentNoise;
|
|
/// <summary>
|
|
/// Goal to move towards.
|
|
/// </summary>
|
|
private float _goal;
|
|
#endregion
|
|
|
|
#region Const.
|
|
/// <summary>
|
|
/// How much to multiply distance checks by when moving CurrentNoise towards Goal.
|
|
/// </summary>
|
|
private const float DISTANCE_MULTIPLIER = 3f;
|
|
/// <summary>
|
|
/// How quickly to move CurrentNoise towards Goal.
|
|
/// </summary>
|
|
private const float UPDATE_RATE = 0.5f;
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Randomly sets a new goal using variance.
|
|
/// </summary>
|
|
private void RandomizeGoal(bool setCurrent)
|
|
{
|
|
_goal = 1f.Variance(_variance);
|
|
if (setCurrent)
|
|
_currentNoise = _goal;
|
|
}
|
|
/// <summary>
|
|
/// Returns the current noise. This is for internal use only.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
/// <param name="deltaTime">DeltaTime to use.</param>
|
|
internal float UpdateCurrentNoise(float deltaTime)
|
|
{
|
|
//No variance.
|
|
if (_variance == 0f)
|
|
return 1f;
|
|
|
|
//At goal, make a new one.
|
|
if (_currentNoise == _goal)
|
|
RandomizeGoal(false);
|
|
|
|
float distance = Mathf.Max(1f, Mathf.Abs(_goal - _currentNoise) * DISTANCE_MULTIPLIER);
|
|
_currentNoise = Mathf.MoveTowards(_currentNoise, _goal, UPDATE_RATE * distance * deltaTime);
|
|
|
|
return _currentNoise;
|
|
}
|
|
}
|
|
|
|
private class ModifierMultiplier
|
|
{
|
|
public ModifierMultiplier() { }
|
|
public ModifierMultiplier(float multiplier, float moveRate, bool rateUsesDistance)
|
|
{
|
|
SetValues(multiplier, moveRate, rateUsesDistance);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Value Multiplier should move towards.
|
|
/// </summary>
|
|
private float _multiplierGoal = 1f;
|
|
/// <summary>
|
|
/// Current value to multiply by.
|
|
/// </summary>
|
|
public float Multiplier { get; private set; } = 1f;
|
|
/// <summary>
|
|
/// Rate to move towards multipliers. Use -1f for instant changes.
|
|
/// </summary>
|
|
private float _moveRate = -1f;
|
|
/// <summary>
|
|
/// True for the rate value to change with distance. This to a degree normalizes changes.
|
|
/// </summary>
|
|
private bool _rateUsesDistance = true;
|
|
|
|
/// <summary>
|
|
/// Sets new values to use.
|
|
/// </summary>
|
|
/// <param name="multiplier"></param>
|
|
/// <param name="moveRate"></param>
|
|
/// <param name="rateUsesDistance"></param>
|
|
public void SetValues(float multiplier, float moveRate, bool rateUsesDistance)
|
|
{
|
|
_multiplierGoal = multiplier;
|
|
_moveRate = moveRate;
|
|
_rateUsesDistance = rateUsesDistance;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Updates the multipliers to move towards their goals. This is for internal use only.
|
|
/// </summary>
|
|
/// <param name="deltaTime"></param>
|
|
internal void Update(float deltaTime)
|
|
{
|
|
//If magnitude isn't at goal yet.
|
|
if (Multiplier != _multiplierGoal)
|
|
{
|
|
//No move rate, move instantly.
|
|
if (_moveRate <= 0f)
|
|
{
|
|
Multiplier = _multiplierGoal;
|
|
}
|
|
else
|
|
{
|
|
float distance = 1f;
|
|
if (_rateUsesDistance)
|
|
distance = Mathf.Max(distance, Mathf.Abs(_multiplierGoal - Multiplier));
|
|
|
|
Multiplier = Mathf.MoveTowards(Multiplier, _multiplierGoal, distance * _moveRate * deltaTime);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
#endregion
|
|
|
|
#region Constructors.
|
|
public ShakerInstance(ShakeData data)
|
|
{
|
|
Data = data;
|
|
|
|
if (data.RandomSeed)
|
|
_seed = Floats.Random01();
|
|
else
|
|
_seed = 0.5f;
|
|
|
|
_magnitudeNoise = new NoiseData(data.MagnitudeNoise);
|
|
_roughnessNoise = new NoiseData(data.RoughnessNoise);
|
|
}
|
|
#endregion
|
|
|
|
#region Public.
|
|
/// <summary>
|
|
/// Data being used for this instance.
|
|
/// </summary>
|
|
public ShakeData Data { get; private set; }
|
|
/// <summary>
|
|
///
|
|
/// </summary>
|
|
private bool _paused;
|
|
/// <summary>
|
|
/// True if this shaker instance is paused.
|
|
/// </summary>
|
|
public bool Paused
|
|
{
|
|
get
|
|
{
|
|
if (_paused)
|
|
return true;
|
|
if (Time.timeScale == 0f && Data.ScaledTime)
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
private set { _paused = value; }
|
|
}
|
|
#endregion
|
|
|
|
#region Private.
|
|
/// <summary>
|
|
/// Seed to generate perlin noise.
|
|
/// </summary>
|
|
private float _seed = 0;
|
|
/// <summary>
|
|
/// Time passed since the shaker has started.
|
|
/// </summary>
|
|
private float _timePassed = 0f;
|
|
/// <summary>
|
|
/// Value to multiply roughness by.
|
|
/// </summary>
|
|
private ModifierMultiplier _roughnessMultiplier = new ModifierMultiplier();
|
|
/// <summary>
|
|
/// Value to multiply magnitude by.
|
|
/// </summary>
|
|
private ModifierMultiplier _magnitudeMultiplier = new ModifierMultiplier();
|
|
/// <summary>
|
|
/// Time in the magnitude curve when using infinite shake.
|
|
/// </summary>
|
|
private float _magnitudeCurveTime = 0f;
|
|
/// <summary>
|
|
/// Time in the roughness curve when using infinite shake.
|
|
/// </summary>
|
|
private float _roughnessCurveTime = 0f;
|
|
/// <summary>
|
|
/// Noise data for magnitude.
|
|
/// </summary>
|
|
private NoiseData _magnitudeNoise;
|
|
/// <summary>
|
|
/// Noise data for roughness.
|
|
/// </summary>
|
|
private NoiseData _roughnessNoise;
|
|
/// <summary>
|
|
/// Last offset for instance.
|
|
/// </summary>
|
|
private Vector3 _offset;
|
|
#endregion
|
|
|
|
/// <summary>
|
|
/// Creates a Vector3 perlin noise.
|
|
/// </summary>
|
|
/// <param name="v"></param>
|
|
/// <param name="seed"></param>
|
|
/// <returns></returns>
|
|
private Vector3 PerlinNoise(float seed)
|
|
{
|
|
return new Vector3(
|
|
Mathf.PerlinNoise(seed, seed) - 0.5f,
|
|
Mathf.PerlinNoise(seed, 0f) - 0.5f,
|
|
Mathf.PerlinNoise(0f, seed) - 0.5f
|
|
);
|
|
}
|
|
|
|
private bool _first = true;
|
|
/// <summary>
|
|
/// Returns a new offset for this instnace.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
internal Vector3 UpdateOffset()
|
|
{
|
|
float deltaTime = (Data.ScaledTime) ? Time.deltaTime : Time.unscaledDeltaTime;
|
|
|
|
if (DurationOver())
|
|
{
|
|
_offset = Vector3.zero;
|
|
}
|
|
else
|
|
{
|
|
//Updates the multipliers which can be set at runtime.
|
|
_magnitudeMultiplier.Update(deltaTime);
|
|
_roughnessMultiplier.Update(deltaTime);
|
|
|
|
//If RandomSeed is used then also use perlin noise, creating a more randomized shake.
|
|
if (Data.RandomSeed)
|
|
_offset = PerlinNoise(_seed);
|
|
//Without a random seed the offset changes predictably.
|
|
else
|
|
_offset = new Vector3(1f, 1f, 1f) * (Mathf.PingPong(_seed, 1f) - 0.5f);
|
|
|
|
_seed += deltaTime * Data.Roughness * ReturnRoughnessCurveMultiplier(deltaTime) * _roughnessMultiplier.Multiplier * _roughnessNoise.UpdateCurrentNoise(deltaTime);
|
|
}
|
|
|
|
//Multiplier from fading which is applied to overall values.
|
|
float fadeMultiplier = ReturnFadeMultiplier();
|
|
//Increase time passed to handle fading and ending shake.
|
|
_timePassed += deltaTime;
|
|
|
|
return _offset * Data.Magnitude * ReturnMagnitudeCurveMultiplier(deltaTime) * _magnitudeMultiplier.Multiplier * fadeMultiplier * _magnitudeNoise.UpdateCurrentNoise(deltaTime);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a multiplier to use based on if fading in or out.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private float ReturnFadeMultiplier()
|
|
{
|
|
//Fading in check.
|
|
if (Data.FadeInDuration > 0f)
|
|
{
|
|
float percent = _timePassed / Data.FadeInDuration;
|
|
//If not fully faded in then return fade in multiplier.
|
|
if (percent < 1f)
|
|
return percent;
|
|
}
|
|
|
|
//Fading out check.
|
|
if (!Data.UnlimitedDuration && Data.FadeOutDuration > 0f)
|
|
{
|
|
float remaining = Data.TotalDuration - _timePassed;
|
|
float percent = remaining / Data.FadeOutDuration;
|
|
if (percent < 1f)
|
|
return percent;
|
|
}
|
|
|
|
//Default multiplier if no fade is occurring.
|
|
return 1f;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns a magnitude mulitplier based on the magnitude curve and time passed.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private float ReturnMagnitudeCurveMultiplier(float deltaTime)
|
|
{
|
|
float curveDuration = Data.RoughnessCurve.keys[Data.RoughnessCurve.length - 1].time;
|
|
|
|
//Unlimited shake, loop curve.
|
|
if (Data.UnlimitedDuration)
|
|
{
|
|
_magnitudeCurveTime += deltaTime;
|
|
if (_magnitudeCurveTime > curveDuration)
|
|
_magnitudeCurveTime = (curveDuration - _magnitudeCurveTime);
|
|
|
|
return Data.MagnitudeCurve.Evaluate(_magnitudeCurveTime);
|
|
}
|
|
//Limited time, use curve normally.
|
|
else
|
|
{
|
|
float percent = _timePassed / Data.TotalDuration;
|
|
return Data.MagnitudeCurve.Evaluate(percent * curveDuration);
|
|
}
|
|
}
|
|
/// <summary>
|
|
/// Returns a roughness mulitplier based on the roughness curve and time passed.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private float ReturnRoughnessCurveMultiplier(float deltaTime)
|
|
{
|
|
float curveDuration = Data.RoughnessCurve.keys[Data.RoughnessCurve.length - 1].time;
|
|
|
|
//Unlimited shake, loop curve.
|
|
if (Data.UnlimitedDuration)
|
|
{
|
|
_roughnessCurveTime += deltaTime;
|
|
if (_roughnessCurveTime > curveDuration)
|
|
_roughnessCurveTime = (curveDuration - _roughnessCurveTime);
|
|
|
|
return Data.RoughnessCurve.Evaluate(_roughnessCurveTime);
|
|
}
|
|
//Limited time, use curve normally.
|
|
else
|
|
{
|
|
float percent = _timePassed / Data.TotalDuration;
|
|
return Data.RoughnessCurve.Evaluate(percent * curveDuration);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns if the instance shaker is over.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
public bool ShakerOver()
|
|
{
|
|
return (DurationOver() && (_offset == Vector3.zero));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Returns true if the total duration has expired, and not unlimited duration.
|
|
/// </summary>
|
|
/// <returns></returns>
|
|
private bool DurationOver()
|
|
{
|
|
if (Data.UnlimitedDuration)
|
|
return false;
|
|
|
|
return (_timePassed >= Data.TotalDuration);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Sets the paused state of this shaker.
|
|
/// </summary>
|
|
/// <param name="value">New paused state.</param>
|
|
public void SetPaused(bool value)
|
|
{
|
|
Paused = value;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stops this instance abruptly.
|
|
/// </summary>
|
|
public void Stop()
|
|
{
|
|
Data.SetTotalDuration(0f);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fades out this instance. This operation only works if not already fading out.
|
|
/// </summary>
|
|
/// <param name="durationOverride">Overrides instance Data fade out duration with a new value.</param>
|
|
public void FadeOut(float? durationOverride = null)
|
|
{
|
|
float fadeDuration = (durationOverride == null) ? Data.FadeOutDuration : durationOverride.Value;
|
|
|
|
bool canChange = false;
|
|
//If unlimited duration then fading out is okay, no checks needed.
|
|
if (Data.UnlimitedDuration)
|
|
{
|
|
canChange = true;
|
|
}
|
|
//Not unlimited total duration, check if already fading out.
|
|
else
|
|
{
|
|
float remaining = Data.TotalDuration - _timePassed;
|
|
/* If remaining is less than new fade
|
|
* duration then let remaining end unmodified.
|
|
* Otherwise snap to new fade out duration.
|
|
* In the future rather than abrutply
|
|
* skipping time fade out will be speed up to match
|
|
* new time. */
|
|
if (remaining > fadeDuration)
|
|
canChange = true;
|
|
}
|
|
|
|
if (canChange)
|
|
{
|
|
Data.SetFadeOutDuration(fadeDuration);
|
|
Data.SetTotalDuration(_timePassed + fadeDuration);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Multiplies magnitude values in data by a set amount.
|
|
/// </summary>
|
|
/// <param name="multiplier">Value to multiply by. 1f is standard multiplication, which in result would be default values.</param>
|
|
/// <param name="moveRate">How quickly per second to move towards new multiplier. Values 0f and lower are instant.</param>
|
|
/// <param name="rateUsesDistance">True to modify move rate based on distance from multiplier. False to move towards goal using moveRate unmodified.</param>
|
|
public void MultiplyMagnitude(float multiplier, float moveRate, bool rateUsesDistance = true)
|
|
{
|
|
_magnitudeMultiplier.SetValues(multiplier, moveRate, rateUsesDistance);
|
|
}
|
|
/// <summary>
|
|
/// Multiplies roughness values in data by a set amount.
|
|
/// </summary>
|
|
/// <param name="multiplier">Value to multiply by. 1f is standard multiplication, which in result would be default values.</param>
|
|
/// <param name="moveRate">How quickly per second to move towards new multiplier. Values 0f and lower are instant.</param>
|
|
/// <param name="rateUsesDistance">True to modify move rate based on distance from multiplier. False to move towards goal using moveRate unmodified.</param>
|
|
public void MultiplyRoughness(float multiplier, float moveRate, bool rateUsesDistance = true)
|
|
{
|
|
_roughnessMultiplier.SetValues(multiplier, moveRate, rateUsesDistance);
|
|
}
|
|
|
|
}
|
|
} |