using UnityEngine;
using System.Collections;
using System.Collections.Generic;
namespace Com.LuisPedroFonseca.ProCamera2D
{
#if UNITY_5_3_OR_NEWER
[HelpURLAttribute("http://www.procamera2d.com/user-guide/extension-shake/")]
#endif
public class ProCamera2DShake : BasePC2D
{
public static string ExtensionName = "Shake";
static ProCamera2DShake _instance;
public static ProCamera2DShake Instance
{
get
{
if (Equals(_instance, null))
{
_instance = FindObjectOfType(typeof(ProCamera2DShake)) as ProCamera2DShake;
if (Equals(_instance, null))
throw new UnityException("ProCamera2D does not have a Shake extension.");
}
return _instance;
}
}
/// Property to know if there's a ProCamera2DShake present
public static bool Exists { get { return _instance != null; } }
public System.Action OnShakeCompleted;
public List ShakePresets = new List();
public List ConstantShakePresets = new List();
public ConstantShakePreset StartConstantShakePreset;
/// Used internally by the editor
public ConstantShakePreset CurrentConstantShakePreset;
Transform _shakeParent;
List _applyInfluencesCoroutines = new List();
List _shakeTimedCoroutines = new List();
Coroutine _shakeCoroutine;
Vector3 _shakeVelocity;
List _shakePositions = new List();
Quaternion _rotationTarget;
Quaternion _originalRotation;
float _rotationTime;
float _rotationVelocity;
List _influences = new List();
Vector3 _influencesSum = Vector3.zero;
Vector3[] _constantShakePositions;
Vector3 _constantShakePosition;
bool _isConstantShaking;
override protected void Awake()
{
base.Awake();
_instance = this;
if (ProCamera2D.transform.parent != null)
{
_shakeParent = new GameObject("ProCamera2DShakeContainer").transform;
_shakeParent.parent = ProCamera2D.transform.parent;
_shakeParent.localPosition = Vector3.zero;
ProCamera2D.transform.parent = _shakeParent;
}
else
{
_shakeParent = ProCamera2D.transform.parent = new GameObject("ProCamera2DShakeContainer").transform;
}
_originalRotation = _transform.localRotation;
}
void Start()
{
if (StartConstantShakePreset != null)
ConstantShake(StartConstantShakePreset);
}
void Update()
{
_influencesSum = Vector3.zero;
if (_influences.Count > 0)
{
_influencesSum = Utils.GetVectorsSum(_influences);
_influences.Clear();
_shakeParent.localPosition = _influencesSum;
}
}
/// Shakes the camera with the given values.
/// The duration of the shake
/// The shake strength on each axis
/// Indicates how much will the shake vibrate
/// Indicates how much random the shake will be
/// The initial angle of the shake. Use -1 if you want it to be random.
/// The maximum rotation the camera can reach during shake
/// How smooth the shake should be, 0 being instant
/// If true, the shake will occur even if the timeScale is 0
public void Shake(
float duration,
Vector2 strength,
int vibrato = 10,
float randomness = .1f,
float initialAngle = -1f,
Vector3 rotation = default(Vector3),
float smoothness = .1f,
bool ignoreTimeScale = false)
{
if (!enabled)
return;
vibrato++;
if (vibrato < 2)
vibrato = 2;
// Calculate steps durations
float[] durations = new float[vibrato];
float sum = 0;
for (int i = 0; i < vibrato; ++i)
{
float iterationPerc = (i + 1) / (float)vibrato;
float tDuration = duration * iterationPerc;
sum += tDuration;
durations[i] = tDuration;
}
float tDurationMultiplier = duration / sum;
for (int i = 0; i < vibrato; ++i)
durations[i] = durations[i] * tDurationMultiplier;
float shakeMagnitude = strength.magnitude;
float magnitudeDecay = shakeMagnitude / vibrato;
float ang = initialAngle != -1f ? initialAngle : Random.Range(0f, 360f);
var positions = new Vector2[vibrato];
positions[vibrato - 1] = Vector2.zero;
var rotations = new Quaternion[vibrato];
rotations[vibrato - 1] = _originalRotation;
var rotationQtn = Quaternion.Euler(rotation);
for (int i = 0; i < vibrato - 1; ++i)
{
// Position
if (i > 0)
ang = ang - 180 + Random.Range(-90, 90) * randomness;
Quaternion rndQuaternion = Quaternion.AngleAxis(Random.Range(-90, 90) * randomness, Vector3.up);
float radians = ang * Mathf.Deg2Rad;
var dir = new Vector3(shakeMagnitude * Mathf.Cos(radians), shakeMagnitude * Mathf.Sin(radians), 0);
Vector2 position = rndQuaternion * dir;
position.x = Vector2.ClampMagnitude(position, strength.x).x;
position.y = Vector2.ClampMagnitude(position, strength.y).y;
positions[i] = position;
shakeMagnitude -= magnitudeDecay;
strength = Vector2.ClampMagnitude(strength, shakeMagnitude);
// Rotation
var sign = i % 2 == 0 ? 1 : -1;
var percent = (float)i / (vibrato - 1);
rotations[i] = sign == 1 ? Quaternion.Lerp(rotationQtn, Quaternion.identity, percent) * _originalRotation : Quaternion.Inverse(Quaternion.Lerp(rotationQtn, Quaternion.identity, percent)) * _originalRotation;
}
_applyInfluencesCoroutines.Add(ApplyShakesTimed(positions, rotations, durations, smoothness, ignoreTimeScale));
}
/// Shakes the camera using the values defined on the provided preset
/// The index of the preset
public void Shake(int presetIndex)
{
if (presetIndex <= ShakePresets.Count - 1)
{
Shake(
ShakePresets[presetIndex].Duration,
ShakePresets[presetIndex].Strength,
ShakePresets[presetIndex].Vibrato,
ShakePresets[presetIndex].Randomness,
ShakePresets[presetIndex].UseRandomInitialAngle ? -1 : ShakePresets[presetIndex].InitialAngle,
ShakePresets[presetIndex].Rotation,
ShakePresets[presetIndex].Smoothness,
ShakePresets[presetIndex].IgnoreTimeScale);
}
else
{
Debug.LogWarning("Could not find a shake preset with the index: " + presetIndex);
}
}
/// Shakes the camera using the values defined on the provided preset
/// The name of the preset
public void Shake(string presetName)
{
for (int i = 0; i < ShakePresets.Count; i++)
{
if (ShakePresets[i].name == presetName)
{
Shake(i);
return;
}
}
Debug.LogWarning("Could not find a shake preset with the name: " + presetName);
}
/// Shakes the camera using the values defined on the provided preset
/// The shake preset
public void Shake(ShakePreset preset)
{
Shake(preset.Duration,
preset.Strength,
preset.Vibrato,
preset.Randomness,
preset.UseRandomInitialAngle ? -1 : preset.InitialAngle,
preset.Rotation,
preset.Smoothness,
preset.IgnoreTimeScale);
}
/// Stops all current shakes
public void StopShaking()
{
for (int i = 0; i < _applyInfluencesCoroutines.Count; i++)
{
StopCoroutine(_applyInfluencesCoroutines[i]);
}
for (int i = 0; i < _shakeTimedCoroutines.Count; i++)
{
StopCoroutine(_shakeTimedCoroutines[i]);
}
if (_shakeCoroutine != null)
{
StopCoroutine(_shakeCoroutine);
_shakeCoroutine = null;
}
_shakePositions.Clear();
_shakeVelocity = Vector3.zero;
ShakeCompleted();
}
///
/// Constantly shakes the camera until it's explicitly told to stop
///
/// The preset that contains all the constant shake parameters
public void ConstantShake(ConstantShakePreset preset)
{
if (CurrentConstantShakePreset != null)
StopConstantShaking(0);
CurrentConstantShakePreset = preset;
_isConstantShaking = true;
_constantShakePositions = new Vector3[preset.Layers.Count];
for (int i = 0; i < preset.Layers.Count; i++)
{
StartCoroutine(CalculateConstantShakePosition(
i,
preset.Layers[i].Frequency.x,
preset.Layers[i].Frequency.y,
preset.Layers[i].AmplitudeHorizontal,
preset.Layers[i].AmplitudeVertical,
preset.Layers[i].AmplitudeDepth));
}
StartCoroutine(ConstantShakeRoutine(preset.Intensity));
}
///
/// Constantly shakes the camera until it's explicitly told to stop
///
/// The name of the preset. It must be part of the ConstantShakePresets list.
public void ConstantShake(string presetName)
{
for (int i = 0; i < ConstantShakePresets.Count; i++)
{
if (ConstantShakePresets[i].name == presetName)
{
ConstantShake(ConstantShakePresets[i]);
return;
}
}
Debug.LogWarning("Could not find a ConstantShakePreset with the name: " + presetName + ". Remember you need to add it to the ConstantShakePresets list first.");
}
///
/// Constantly shakes the camera until it's explicitly told to stop
///
/// The index of the preset. It must be part of the ConstantShakePresets list.
public void ConstantShake(int presetIndex)
{
if (presetIndex <= ConstantShakePresets.Count - 1)
{
ConstantShake(ConstantShakePresets[presetIndex]);
}
else
{
Debug.LogWarning("Could not find a ConstantShakePreset with the index: " + presetIndex + ". Remember you need to add it to the ConstantShakePresets list first.");
}
}
///
/// Stops constant shakes
///
/// How long it takes to stop the constant shake
public void StopConstantShaking(float duration = .3f)
{
CurrentConstantShakePreset = null;
_isConstantShaking = false;
if (duration > 0f)
StartCoroutine(StopConstantShakeRoutine(duration));
else
{
StopAllCoroutines();
_constantShakePosition = Vector3.zero;
_influences.Clear();
_influences.Add(_constantShakePosition);
}
}
/// Apply the given influences to the camera during the corresponding durations.
/// An array of the vectors representing the shakes to be applied
/// An array of the rotations to be applied
/// An array with the durations of the influences to be applied
/// How smooth the shake should be, 0 being instant
/// If true, the shake will occur even if the timeScale is 0
public Coroutine ApplyShakesTimed(
Vector2[] shakes,
Vector3[] rotations,
float[] durations,
float smoothness = .1f,
bool ignoreTimeScale = false)
{
if (!enabled)
return null;
var rotationsQtn = new Quaternion[rotations.Length];
for (int i = 0; i < rotations.Length; i++)
rotationsQtn[i] = Quaternion.Euler(rotations[i]) * _originalRotation;
return ApplyShakesTimed(shakes, rotationsQtn, durations);
}
/// Apply the given influence to the camera during this frame, while ignoring all camera boundaries
/// The vector representing the influence to be applied
public void ApplyInfluenceIgnoringBoundaries(Vector2 influence)
{
if (Time.deltaTime < .0001f || float.IsNaN(influence.x) || float.IsNaN(influence.y))
return;
_influences.Add(VectorHV(influence.x, influence.y));
}
Coroutine ApplyShakesTimed(
Vector2[] shakes,
Quaternion[] rotations,
float[] durations,
float smoothness = .1f,
bool ignoreTimeScale = false)
{
var coroutine = StartCoroutine(ApplyShakesTimedRoutine(shakes, rotations, durations, ignoreTimeScale));
if (_shakeCoroutine == null)
_shakeCoroutine = StartCoroutine(ShakeRoutine(smoothness, ignoreTimeScale));
return coroutine;
}
IEnumerator ShakeRoutine(float smoothness, bool ignoreTimeScale = false)
{
while (_shakePositions.Count > 0 ||
Vector3.Distance(_shakeParent.localPosition, _influencesSum) > .01f ||
Quaternion.Angle(_transform.localRotation, _originalRotation) > .01f)
{
var newShakePosition = Utils.GetVectorsSum(_shakePositions) + _influencesSum;
var newShakePositionSmoothed = Vector3.zero;
if (ignoreTimeScale)
newShakePositionSmoothed = Vector3.SmoothDamp(_shakeParent.localPosition, newShakePosition, ref _shakeVelocity, smoothness, float.MaxValue, Time.unscaledDeltaTime);
else if (ProCamera2D.DeltaTime > 0f)
newShakePositionSmoothed = Vector3.SmoothDamp(_shakeParent.localPosition, newShakePosition, ref _shakeVelocity, smoothness);
_shakeParent.localPosition = newShakePositionSmoothed;
_shakePositions.Clear();
if (ignoreTimeScale)
_rotationTime = Mathf.SmoothDamp(_rotationTime, 1f, ref _rotationVelocity, smoothness, float.MaxValue, ProCamera2D.UpdateType == UpdateType.LateUpdate ? Time.unscaledDeltaTime : Time.fixedUnscaledDeltaTime);
else if (ProCamera2D.DeltaTime > 0)
_rotationTime = Mathf.SmoothDamp(_rotationTime, 1f, ref _rotationVelocity, smoothness, float.MaxValue, ProCamera2D.DeltaTime);
var rotationTargetSmoothed = Quaternion.Slerp(_transform.localRotation, _rotationTarget, _rotationTime);
_transform.localRotation = rotationTargetSmoothed;
_rotationTarget = _originalRotation;
yield return ProCamera2D.GetYield();
}
ShakeCompleted();
}
void ShakeCompleted()
{
_shakeParent.localPosition = _influencesSum;
_transform.localRotation = _originalRotation;
_shakeCoroutine = null;
if (OnShakeCompleted != null)
OnShakeCompleted();
}
IEnumerator ApplyShakesTimedRoutine(IList shakes, IList rotations, float[] durations, bool ignoreTimeScale = false)
{
_shakeTimedCoroutines = new List();
var count = -1;
while (count < durations.Length - 1)
{
count++;
var duration = durations[count];
var coroutine = StartCoroutine(ApplyShakeTimedRoutine(shakes[count], rotations[count], duration, ignoreTimeScale));
_shakeTimedCoroutines.Add(coroutine);
yield return coroutine;
}
}
IEnumerator ApplyShakeTimedRoutine(Vector2 shake, Quaternion rotation, float duration, bool ignoreTimeScale = false)
{
_rotationTime = 0;
_rotationVelocity = 0;
while (duration > 0)
{
if (ignoreTimeScale)
{
if(ProCamera2D.UpdateType == UpdateType.LateUpdate)
duration -= Time.unscaledDeltaTime;
else if(ProCamera2D.UpdateType == UpdateType.FixedUpdate)
duration -= Time.fixedUnscaledDeltaTime;
}
else
duration -= ProCamera2D.DeltaTime;
_shakePositions.Add(VectorHV(shake.x, shake.y));
_rotationTarget = rotation;
yield return ProCamera2D.GetYield();
}
}
IEnumerator StopConstantShakeRoutine(float duration)
{
var velocity = Vector3.zero;
_influences.Clear();
while (duration >= 0)
{
duration -= ProCamera2D.DeltaTime;
_constantShakePosition = Vector3.SmoothDamp(_constantShakePosition, Vector3.zero, ref velocity, duration, float.MaxValue);
_influences.Add(_constantShakePosition);
yield return ProCamera2D.GetYield();
}
}
IEnumerator CalculateConstantShakePosition(int index, float frequencyMin, float frequencyMax, float amplitudeX, float amplitudeY, float amplitudeZ)
{
while (_isConstantShaking)
{
var randomFrequency = Random.Range(frequencyMin, frequencyMax);
var unitSphere = Random.insideUnitSphere;
var randomAmplitudeX = unitSphere.x * amplitudeX;
var randomAmplitudeY = unitSphere.y * amplitudeY;
var randomAmplitudeZ = unitSphere.z * amplitudeZ;
if(index < _constantShakePositions.Length)
_constantShakePositions[index] = VectorHVD(randomAmplitudeX, randomAmplitudeY, randomAmplitudeZ);
//Debug.DrawLine(transform.localPosition, transform.localPosition + VectorHVD(randomAmplitudeX, randomAmplitudeY, randomAmplitudeZ), Color.green, randomFrequency);
if(ProCamera2D.IgnoreTimeScale)
yield return new WaitForSecondsRealtime(randomFrequency);
else
yield return new WaitForSeconds(randomFrequency);
}
}
IEnumerator ConstantShakeRoutine(float intensity)
{
while (_isConstantShaking)
{
if (ProCamera2D.DeltaTime > 0)
{
var result = Utils.GetVectorsSum(_constantShakePositions) / _constantShakePositions.Length;
_constantShakePosition.Set(Utils.SmoothApproach(_constantShakePosition.x, result.x, result.x, intensity, ProCamera2D.DeltaTime),
Utils.SmoothApproach(_constantShakePosition.y, result.y, result.y, intensity, ProCamera2D.DeltaTime),
Utils.SmoothApproach(_constantShakePosition.z, result.z, result.z, intensity, ProCamera2D.DeltaTime));
_influences.Add(_constantShakePosition);
}
yield return ProCamera2D.GetYield();
}
}
#if UNITY_EDITOR
override protected void DrawGizmos()
{
base.DrawGizmos();
var cameraDimensions = Utils.GetScreenSizeInWorldCoords(ProCamera2D.GameCamera, Mathf.Abs(Vector3D(transform.localPosition)));
if (Application.isPlaying && _shakeParent.localPosition != Vector3.zero)
{
Gizmos.color = EditorPrefsX.GetColor(PrefsData.ShakeInfluenceColorKey, PrefsData.ShakeInfluenceColorValue);
Utils.DrawArrowForGizmo(ProCamera2D.TargetsMidPoint, _shakeParent.localPosition, .04f * cameraDimensions.y);
}
}
#endif
}
}