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