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;
///
/// Current noise value.
///
private float _currentNoise;
///
/// Goal to move towards.
///
private float _goal;
#endregion
#region Const.
///
/// How much to multiply distance checks by when moving CurrentNoise towards Goal.
///
private const float DISTANCE_MULTIPLIER = 3f;
///
/// How quickly to move CurrentNoise towards Goal.
///
private const float UPDATE_RATE = 0.5f;
#endregion
///
/// Randomly sets a new goal using variance.
///
private void RandomizeGoal(bool setCurrent)
{
_goal = 1f.Variance(_variance);
if (setCurrent)
_currentNoise = _goal;
}
///
/// Returns the current noise. This is for internal use only.
///
///
/// DeltaTime to use.
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);
}
///
/// Value Multiplier should move towards.
///
private float _multiplierGoal = 1f;
///
/// Current value to multiply by.
///
public float Multiplier { get; private set; } = 1f;
///
/// Rate to move towards multipliers. Use -1f for instant changes.
///
private float _moveRate = -1f;
///
/// True for the rate value to change with distance. This to a degree normalizes changes.
///
private bool _rateUsesDistance = true;
///
/// Sets new values to use.
///
///
///
///
public void SetValues(float multiplier, float moveRate, bool rateUsesDistance)
{
_multiplierGoal = multiplier;
_moveRate = moveRate;
_rateUsesDistance = rateUsesDistance;
}
///
/// Updates the multipliers to move towards their goals. This is for internal use only.
///
///
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.
///
/// Data being used for this instance.
///
public ShakeData Data { get; private set; }
///
///
///
private bool _paused;
///
/// True if this shaker instance is paused.
///
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.
///
/// Seed to generate perlin noise.
///
private float _seed = 0;
///
/// Time passed since the shaker has started.
///
private float _timePassed = 0f;
///
/// Value to multiply roughness by.
///
private ModifierMultiplier _roughnessMultiplier = new ModifierMultiplier();
///
/// Value to multiply magnitude by.
///
private ModifierMultiplier _magnitudeMultiplier = new ModifierMultiplier();
///
/// Time in the magnitude curve when using infinite shake.
///
private float _magnitudeCurveTime = 0f;
///
/// Time in the roughness curve when using infinite shake.
///
private float _roughnessCurveTime = 0f;
///
/// Noise data for magnitude.
///
private NoiseData _magnitudeNoise;
///
/// Noise data for roughness.
///
private NoiseData _roughnessNoise;
///
/// Last offset for instance.
///
private Vector3 _offset;
#endregion
///
/// Creates a Vector3 perlin noise.
///
///
///
///
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;
///
/// Returns a new offset for this instnace.
///
///
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);
}
///
/// Returns a multiplier to use based on if fading in or out.
///
///
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;
}
///
/// Returns a magnitude mulitplier based on the magnitude curve and time passed.
///
///
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);
}
}
///
/// Returns a roughness mulitplier based on the roughness curve and time passed.
///
///
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);
}
}
///
/// Returns if the instance shaker is over.
///
///
public bool ShakerOver()
{
return (DurationOver() && (_offset == Vector3.zero));
}
///
/// Returns true if the total duration has expired, and not unlimited duration.
///
///
private bool DurationOver()
{
if (Data.UnlimitedDuration)
return false;
return (_timePassed >= Data.TotalDuration);
}
///
/// Sets the paused state of this shaker.
///
/// New paused state.
public void SetPaused(bool value)
{
Paused = value;
}
///
/// Stops this instance abruptly.
///
public void Stop()
{
Data.SetTotalDuration(0f);
}
///
/// Fades out this instance. This operation only works if not already fading out.
///
/// Overrides instance Data fade out duration with a new value.
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);
}
}
///
/// Multiplies magnitude values in data by a set amount.
///
/// Value to multiply by. 1f is standard multiplication, which in result would be default values.
/// How quickly per second to move towards new multiplier. Values 0f and lower are instant.
/// True to modify move rate based on distance from multiplier. False to move towards goal using moveRate unmodified.
public void MultiplyMagnitude(float multiplier, float moveRate, bool rateUsesDistance = true)
{
_magnitudeMultiplier.SetValues(multiplier, moveRate, rateUsesDistance);
}
///
/// Multiplies roughness values in data by a set amount.
///
/// Value to multiply by. 1f is standard multiplication, which in result would be default values.
/// How quickly per second to move towards new multiplier. Values 0f and lower are instant.
/// True to modify move rate based on distance from multiplier. False to move towards goal using moveRate unmodified.
public void MultiplyRoughness(float multiplier, float moveRate, bool rateUsesDistance = true)
{
_roughnessMultiplier.SetValues(multiplier, moveRate, rateUsesDistance);
}
}
}