using FirstGearGames.Utilities.Objects; using System; using System.Collections.Generic; using UnityEngine; namespace FirstGearGames.SmoothCameraShaker { public class CameraShakerHandler : MonoBehaviour { #region Public. /// /// Dispatched after the default Shaker is changed. /// public static event Action OnDefaultShakerChanged; /// /// Dispatched after the default Shaker is changed. Obsolete: use OnDefaultShakerChanged instead. /// [Obsolete("Obsolete: use OnDefaultShakerChanged instead.")] public static event Action OnDefaultCameraShakerChanged { add { OnDefaultShakerChanged += value; } remove { OnDefaultShakerChanged -= value; } } /// /// Dispatched when shaking starts when previously stopped on all Shakers. /// public static event Action OnAllShakingStarted; /// /// Dispatched when shaking ends on all Shakers. /// public static event Action OnAllShakingEnded; /// /// Dispatched every update a shake occurs. This is the total values of InstantiatedShakers. /// public static event Action OnAllShakeUpdate; /// /// Dispatched every fixed update a shake occurs. Contains the shake values from last update of InstantiatedShakers. /// public static event Action OnAllShakeFixedUpdate; /// /// Dispatched when shaking starts on any Shaker. /// public static event Action OnShakingStarted; /// /// Dispatched when shaking ends on any Shaker. /// public static event Action OnShakingEnded; /// /// Dispatched every update a shake occurs on any Shaker. /// public static event Action OnShakeUpdate; /// /// Dispatched every fixed updated a shake occurs on any Shaker. Contains the shake values from last update. /// public static event Action OnShakeFixedUpdate; /// /// Dispatched after a Shaker is added to InstantiatedShakers. /// public static event Action OnShakerInstantiated; /// /// Dispatched after a Shaker is added to InstantiatedShakers. Obsolete: use OnShakerInstantiated instead. /// [Obsolete("Obsolete: use OnShakerInstantiated instead.")] public static event Action OnCameraShakerInstantiated { add { OnShakerInstantiated += value; } remove { OnShakerInstantiated -= value; } } /// /// Dispatched after a Shaker is removed from InstantiatedShakers. /// public static event Action OnShakerDestroyed; /// /// Dispatched after a Shaker is removed from InstantiatedShakers. Obsolete: use OnShakerDestroyed instead. /// [Obsolete("Obsolete: use OnShakerDestroyed instead.")] public static event Action OnCameraShakerDestroyed { add { OnShakerDestroyed += value; } remove { OnShakerDestroyed -= value; } } /// /// All instantiated Shaker scripts. /// public static List InstantiatedShakers = new List(); /// /// All instantiated Shaker scripts. Obsolete: use InstantiatedShakers instead. /// [Obsolete("Obsolete: use InstantiatedShakers instead.")] public static List InstantiatedCameraShakers { get { return InstantiatedShakers; } set { InstantiatedShakers = value; } } /// /// /// z private CameraShaker _defaultCameraShaker; /// /// Current default Shaker. /// public static CameraShaker DefaultCameraShaker { get { if (_instance == null) return null; return _instance._defaultCameraShaker; } private set { if (_instance == null) return; _instance._defaultCameraShaker = value; } } /// /// True if any CameraShaker is currently shaking. /// public static bool Shaking { get { return (_instance._shaking.Count > 0); } } #endregion #region Private. /// /// Collection of CameraShakers which are currently shaking. /// private List _shaking = new List(); /// /// Singleton instance of this script. /// private static CameraShakerHandler _instance; #endregion private void Awake() { //Make sure there is only once instance. if (_instance != null && _instance != this) { if (Debug.isDebugBuild) Debug.LogWarning("Multiple CameraShakerHandler scripts found. This script auto loads itself and does not need to be placed in your scenes."); Destroy(this); return; } } private void Update() { UpdateShakers(); } private void FixedUpdate() { UpdateFixedShakers(); } private void OnDestroy() { DisableAll(); } /// /// Initializes this script for use. Should only be completed once. /// [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.BeforeSceneLoad)] private static void FirstInitialize() { DDOL ddol = DDOL.ReturnDDOL(); GameObject obj = new GameObject(); obj.name = "CameraShakerHandler"; _instance = obj.AddComponent(); _instance.enabled = false; _instance.transform.SetParent(ddol.transform); } /// /// Disables activity on all camera shakers. /// private void DisableAll() { //Disable camera shakers. for (int i = 0; i < InstantiatedShakers.Count; i++) { if (InstantiatedShakers[i] != null) InstantiatedShakers[i].Disable(); } } /// /// Updates Shakers on standard time. /// private void UpdateShakers() { if (_shaking.Count == 0) return; ShakeValues totalCamera = new ShakeValues(); ShakeValues totalCanvases = new ShakeValues(); ShakeValues totalRigidbodies = new ShakeValues(); //True if any shakers are running. bool anyShaking = false; for (int i = 0; i < InstantiatedShakers.Count; i++) { ShakeValues camera; ShakeValues canvases; ShakeValues rigidbodies; if (InstantiatedShakers[i].UpdateShakers(out camera, out canvases, out rigidbodies)) { anyShaking = true; totalCamera.Position += camera.Position; totalCamera.Rotation += camera.Rotation; totalCanvases.Position += canvases.Position; totalCanvases.Rotation += canvases.Rotation; totalRigidbodies.Position += rigidbodies.Position; totalRigidbodies.Rotation += rigidbodies.Rotation; } } //If any where shaking. if (anyShaking) OnAllShakeUpdate?.Invoke(new ShakeUpdate(totalCamera, totalCanvases, totalRigidbodies)); } /// /// Updates Shakers on fixed time. /// private void UpdateFixedShakers() { /* Don't exit if total shaking is 0 * as shaking may have stopped but still * have a fixed shake queued. */ ShakeValues totalCamera = new ShakeValues(); ShakeValues totalCanvases = new ShakeValues(); ShakeValues totalRigidbodies = new ShakeValues(); //True if any shakers are shaking a fixed value. bool anyShaking = false; for (int i = 0; i < InstantiatedShakers.Count; i++) { ShakeValues camera; ShakeValues canvases; ShakeValues rigidbodies; if (InstantiatedShakers[i].UpdateFixedShakers(out camera, out canvases, out rigidbodies)) { anyShaking = true; totalCamera.Position += camera.Position; totalCamera.Rotation += camera.Rotation; totalCanvases.Position += canvases.Position; totalCanvases.Rotation += canvases.Rotation; totalRigidbodies.Position += rigidbodies.Position; totalRigidbodies.Rotation += rigidbodies.Rotation; } } if (anyShaking) OnAllShakeFixedUpdate?.Invoke(new ShakeUpdate(totalCamera, totalCanvases, totalRigidbodies)); } /// /// Returns if an action can be run on the specified Shaker using an All method. /// /// /// /// private static bool CanRunAllOn(CameraShaker shaker, bool includeDisabled) { if (shaker == null) return false; if (!shaker.gameObject.activeInHierarchy && !includeDisabled) return false; return true; } #region Shaker referencing handling. /// /// Adds CameraShaker to shaking. This is for internal use only. /// /// internal static void AddShaking(CameraShaker shaker) { int startCount = _instance._shaking.Count; _instance._shaking.AddUnique(shaker); //Shaking just started. if (startCount == 0 && _instance._shaking.Count > 0) { _instance.enabled = true; OnAllShakingStarted?.Invoke(); } } /// /// Removes CameraShaker from shaking. This is for internal use only. /// /// internal static void RemoveShaking(CameraShaker shaker) { int startCount = _instance._shaking.Count; _instance._shaking.Remove(shaker); //Last shaker was removed. if (startCount > 0 && _instance._shaking.Count == 0) { /* Since this is the last shaker being removed send * zero values. This has to be done before update otherwise these * values would send after the OnAllShakingEnded. */ OnAllShakeUpdate?.Invoke(new ShakeUpdate()); OnAllShakeFixedUpdate?.Invoke(new ShakeUpdate()); OnAllShakingEnded?.Invoke(); _instance.enabled = false; } } /// /// Adds a CameraShaker to the InstantiatedCameraShakers field. This is for internal use only. /// /// internal static void AddInstantiatedShaker(CameraShaker value) { int index = InstantiatedShakers.IndexOf(value); /* If shaker already exist then remove the current entry. * It will then be added to the end. */ if (index != -1) InstantiatedShakers.RemoveAt(index); //First time being added, subscribe to events. else ChangeShakerSubscriptions(value, true); InstantiatedShakers.Add(value); OnShakerInstantiated?.Invoke(value); } /// /// Removes a CameraShaker from the InstantiatedCameraShakers field. This is for internal use only. /// /// internal static void RemoveInstantiatedShaker(CameraShaker value) { ChangeShakerSubscriptions(value, false); InstantiatedShakers.Remove(value); /* If value was the current default shaker then try to change value * to the next most recently enabled shaker. This isn't ideal * but can occur with user error. */ if (value == DefaultCameraShaker && InstantiatedShakers.Count > 0) SetFirstDefault(); OnShakerDestroyed?.Invoke(value); } /// /// Iterates through CameraShakers and sets the most recently active instance as default. /// private static void SetFirstDefault() { for (int i = (InstantiatedShakers.Count - 1); i >= 0; i--) { if (InstantiatedShakers[i] != null && InstantiatedShakers[i].gameObject.activeInHierarchy) { SetDefaultCameraShaker(InstantiatedShakers[i]); return; } } //Fall through. Ideally won't happen but can depending on user setup. SetDefaultCameraShaker(null); } #endregion #region Relaying CameraShaker events. /// /// Changes subscriptions to a camera shaker. /// /// /// private static void ChangeShakerSubscriptions(CameraShaker shaker, bool subscribe) { if (shaker == null) return; if (subscribe) { shaker.OnShakingStarted += Shaker_OnShakingStarted; shaker.OnShakingEnded += Shaker_OnShakingEnded; shaker.OnShakeUpdate += Shaker_OnShakeUpdate; shaker.OnShakeFixedUpdate += Shaker_OnFixedShakeUpdate; } else { shaker.OnShakingStarted -= Shaker_OnShakingStarted; shaker.OnShakingEnded -= Shaker_OnShakingEnded; shaker.OnShakeUpdate -= Shaker_OnShakeUpdate; shaker.OnShakeFixedUpdate -= Shaker_OnFixedShakeUpdate; } } /// /// Received when any instantiated CameraShaker stops shaking. /// /// private static void Shaker_OnShakingEnded(CameraShaker obj) { OnShakingEnded?.Invoke(obj); } /// /// Received when any instantiated CameraShaker starts shaking. /// /// private static void Shaker_OnShakingStarted(CameraShaker obj) { OnShakingStarted?.Invoke(obj); } /// /// Received when any instantiated CameraShaker calls OnShakeUpdate. /// /// /// private static void Shaker_OnShakeUpdate(CameraShaker arg1, ShakeUpdate arg2) { OnShakeUpdate?.Invoke(arg1, arg2); } /// /// Received when any instantiated CameraShaker calls OnFixedShakeUpdate. /// /// /// private static void Shaker_OnFixedShakeUpdate(CameraShaker arg1, ShakeUpdate arg2) { OnShakeFixedUpdate?.Invoke(arg1, arg2); } #endregion #region API. /// /// Copies ShakerInstances from one CameraShaker to another. /// /// CameraShaker copied from. /// CameraShaker copied to. /// True to copy the from cameras current offsets. Both CameraShakers must have the same ShakeTechnique for this to work. public static void CopyShakerInstances(CameraShaker from, CameraShaker to, bool copyOffset = true) { //If neither shaker is null then add instances. if (from != null && to != null) { to.AddShakerInstances(from.ShakerInstances); //Also copy offsets when possible. if (copyOffset && from.ShakeTechnique == to.ShakeTechnique) { /* Use the to camera shake technique. Since they are the same * it really doesn't matter which one I read. */ CameraShaker.ShakeTechniques technique = to.ShakeTechnique; //Matrix. if (technique == CameraShaker.ShakeTechniques.Matrix) { /* Cannot copy the matrix because camera view will remain as last cameras view. * If fixed values are known for from camera then use those. */ if (from.FixedCamera != null) to.SetMatrixOffsets(from.FixedCamera.Position, from.FixedCamera.Rotation); } //LocalSpace. else if (to.ShakeTechnique == CameraShaker.ShakeTechniques.LocalSpace) { to.SetLocalSpaceOffsets(from.transform.localPosition, from.transform.localEulerAngles); } } } } /// /// Sets the DefaultCamereaShaker field. /// /// New CameraShaker to use as default. public static void SetDefaultCameraShaker(CameraShaker value) { CameraShaker old = DefaultCameraShaker; DefaultCameraShaker = value; OnDefaultShakerChanged?.Invoke(new CameraShakerChange(old, value)); } /// /// Sets Scale value on the default CameraShaker. /// /// New scale to use. public static void SetScale(float value) { if (DefaultCameraShaker == null) return; DefaultCameraShaker.SetScale(value); } /// /// Sets the Scale value of InstantiatedCameraShakers. /// /// New scale to use /// True to issue call on disabled CameraShakers as well. public static void SetScaleAll(float value, bool includeDisabled = false) { for (int i = 0; i < InstantiatedShakers.Count; i++) { if (!CanRunAllOn(InstantiatedShakers[i], includeDisabled)) continue; InstantiatedShakers[i].SetScale(value); } } /// /// Shakes the default CameraShaker using data. /// /// ShakeData to use. /// Instance generated using data. public static ShakerInstance Shake(ShakeData data) { if (DefaultCameraShaker == null) return null; return DefaultCameraShaker.Shake(data); } /// /// Shakes the all camera shakers using data. /// /// ShakeData to use. /// True to issue call on disabled CameraShakers as well. /// Instances generated using data. public static List ShakeAll(ShakeData data, bool includeDisabled = false) { List results = new List(); for (int i = 0; i < InstantiatedShakers.Count; i++) { if (!CanRunAllOn(InstantiatedShakers[i], includeDisabled)) continue; results.Add(InstantiatedShakers[i].Shake(data)); } return results; } /// /// Sets the paused state of all shaker instances on the default CameraShaker. /// /// New pause state. public static void SetPaused(bool value) { if (DefaultCameraShaker == null) return; DefaultCameraShaker.SetPaused(value); } /// /// Sets the paused state of all shaker instances on the all CameraShakers. /// /// New pause state. /// True to issue call on disabled CameraShakers as well. public static void SetPausedAll(bool value, bool includeDisabled = false) { for (int i = 0; i < InstantiatedShakers.Count; i++) { if (!CanRunAllOn(InstantiatedShakers[i], includeDisabled)) continue; InstantiatedShakers[i].SetPaused(value); } } /// /// Abruptly stops all instances on the default CameraShaker. /// public static void Stop() { if (DefaultCameraShaker == null) return; DefaultCameraShaker.Stop(); } /// /// Abruptly stops all instances on InstantiatedCameraShakers. /// /// True to issue call on disabled CameraShakers as well. public static void StopAll(bool includeDisabled = false) { for (int i = 0; i < InstantiatedShakers.Count; i++) { if (!CanRunAllOn(InstantiatedShakers[i], includeDisabled)) continue; InstantiatedShakers[i].Stop(); } } /// /// Fades out all instances on the default CameraShaker. This operation only works on instances not already fading out. /// /// Overrides instance fade out duration with a new value. public static void FadeOut(float? durationOverride = null) { if (DefaultCameraShaker == null) return; DefaultCameraShaker.FadeOut(durationOverride); } /// /// Fades out all instances on all CameraShakers. This operation only works on instances not already fading out. /// /// Overrides instance fade out duration with a new value. /// True to issue call on disabled CameraShakers as well. public static void FadeOutAll(float? durationOverride = null, bool includeDisabled = false) { for (int i = 0; i < InstantiatedShakers.Count; i++) { if (!CanRunAllOn(InstantiatedShakers[i], includeDisabled)) continue; InstantiatedShakers[i].FadeOut(durationOverride); } } /// /// Multiplies magnitude values for all instances on the defaut camera shaker. /// /// 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 movdRate unmodified. public void MultiplyMagnitude(float multiplier, float moveRate, bool rateUsesDistance) { if (DefaultCameraShaker == null) return; DefaultCameraShaker.MultiplyMagnitude(multiplier, moveRate, rateUsesDistance); } /// /// Multiplies magnitude values for all instances on all camera shakers. /// /// 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 movdRate unmodified. /// True to issue call on disabled CameraShakers as well. public void MultiplyMagnitudeAll(float multiplier, float moveRate, bool rateUsesDistance, bool includeDisabled = false) { for (int i = 0; i < InstantiatedShakers.Count; i++) { if (!CanRunAllOn(InstantiatedShakers[i], includeDisabled)) continue; InstantiatedShakers[i].MultiplyMagnitude(multiplier, moveRate, rateUsesDistance); } } /// /// Multiplies roughness values for all instances on the default camera shaker. /// /// 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 movdRate unmodified. public void MultiplyRoughness(float multiplier, float moveRate, bool rateUsesDistance) { if (DefaultCameraShaker == null) return; DefaultCameraShaker.MultiplyRoughness(multiplier, moveRate, rateUsesDistance); } /// /// Multiplies roughness values for all instances on all camera shakers. /// /// 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 movdRate unmodified. /// True to issue call on disabled CameraShakers as well. public void MultiplyRoughnessAll(float multiplier, float moveRate, bool rateUsesDistance, bool includeDisabled = false) { for (int i = 0; i < InstantiatedShakers.Count; i++) { if (!CanRunAllOn(InstantiatedShakers[i], includeDisabled)) continue; InstantiatedShakers[i].MultiplyRoughness(multiplier, moveRate, rateUsesDistance); } } #endregion } }