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); } } }