Files
BABA_YAGA/Assets/FirstGearGames/SmoothCameraShaker/Scripts/Shakables/ShakableRigidbody2D.cs
2026-05-18 21:22:34 +07:00

362 lines
14 KiB
C#

using FirstGearGames.Utilities.Maths;
using FirstGearGames.Utilities.Objects;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Serialization;
namespace FirstGearGames.SmoothCameraShaker
{
public class ShakableRigidbody2D : ShakableBase
{
#region Types.
/// <summary>
/// Data about how to update rigidbodies with shakes.
/// </summary>
private class RigidbodyData
{
public RigidbodyData(Rigidbody2D rb)
{
Rigidbody = rb;
}
/// <summary>
/// Rigidbody on this object.
/// </summary>
public readonly Rigidbody2D Rigidbody;
/// <summary>
/// Direction to multiply position by at random intervals.
/// </summary>
public float RandomPositionMultiplier { get; private set; } = 1f;
/// <summary>
/// Sets the value for RandomPositionMultiplier.
/// </summary>
/// <param name="value"></param>
public void SetRandomPositionMultiplier(float value)
{
RandomPositionMultiplier = value;
}
/// <summary>
/// Direction to multiply rotation by at random intervals.
/// </summary>
public float RandomRotationMultiplier { get; private set; } = 1f;
/// <summary>
/// Sets the value for RandomRotationMultiplier.
/// </summary>
/// <param name="value"></param>
public void SetRandomRotationMultiplier(float value)
{
RandomRotationMultiplier = value;
}
/// <summary>
/// Next time to update randomMultipliers.
/// </summary>
public float NextRandomizeTime { get; private set; } = -1f;
/// <summary>
/// Sets the value for NextRandomizeTime.
/// </summary>
/// <param name="value"></param>
public void SetNextRandomizeTime(float value)
{
NextRandomizeTime = value;
}
/// <summary>
/// Positional values last time shake offsets were received.
/// </summary>
public Vector2 LastPositional { get; private set; } = Vector2.zero;
/// <summary>
/// Sets the value for LastPositional.
/// </summary>
/// <param name="value"></param>
public void SetLastPositional(Vector2 value)
{
LastPositional = value;
}
/// <summary>
/// Rotational values last time shake offsets were received.
/// </summary>
public float LastRotational { get; private set; } = 0f;
/// <summary>
/// Sets the value for LastRotational.
/// </summary>
/// <param name="value"></param>
public void SetLastRotational(float value)
{
LastRotational = value;
}
}
#endregion
#region Serialized.
/// <summary>
/// Multiplier to apply towards position.
/// </summary>
[Tooltip("Multiplier to apply towards position.")]
[Space(10f)]
[SerializeField]
[FormerlySerializedAs("_positionMultiplier")]
private float _positionalMultiplier = 1f;
/// <summary>
/// Multipplier to apply towards rotation.
/// </summary>
[Tooltip("Multipplier to apply towards rotation.")]
[SerializeField]
[FormerlySerializedAs("_rotationMultiplier")]
private float _rotationalMultiplier = 1f;
/// <summary>
/// Only shake when in view of a camera.
/// </summary>
[Tooltip("Only shake when in view of a camera.")]
[Space(10f)]
[SerializeField]
private bool _requireInView = true;
/// <summary>
/// True to find rigidbodies in children too. This allows you to use one ShakableRigidbody on the parent if all children rigidbodies should shake as well.
/// </summary>
[Tooltip("True to find rigidbodies in children too. This allows you to use one ShakableRigidbody on the parent if all children rigidbodies should shake as well.")]
[SerializeField]
private bool _includeChildren = false;
/// <summary>
/// True to ignore the transform this component resides, and only shake children.
/// </summary>
[Tooltip("True to ignore the transform this component resides, and only shake children.")]
[SerializeField]
private bool _ignoreSelf = false;
/// <summary>
/// True to also find inactive children.
/// </summary>
[Tooltip("True to also find inactive children.")]
[SerializeField]
private bool _includeInactive = false;
/// <summary>
/// True to convert forces to local space before applying.
/// </summary>
[Tooltip("True to convert forces to local space before applying.")]
[Space(10f)]
[SerializeField]
private bool _localizeShake = false;
/// <summary>
/// True to randomly change force direction. Best used with bulk objects so they do not all shake the same direction.
/// </summary>
[Tooltip("True to randomly change force direction. Best used with bulk objects so they do not all shake the same direction.")]
[SerializeField]
private bool _randomizeDirections = true;
#endregion
#region Private.
/// <summary>
/// Data about each rigidbody to shake.
/// </summary>
private RigidbodyData[] _rbData;
/// <summary>
/// True if currently in view.
/// </summary>
private bool _inView = false;
/// <summary>
/// ObjectShaker used for this object. May be null if not using ObjectShaker type.
/// </summary>
private ObjectShaker _objectShaker = null;
#endregion
private void Awake()
{
FirstInitialize();
}
private void OnEnable()
{
if (_requireInView && _inView)
ChangeSubscription(true);
else if (!_requireInView)
ChangeSubscription(true);
}
private void OnDisable()
{
if (_requireInView && _inView)
ChangeSubscription(false);
else if (!_requireInView)
ChangeSubscription(false);
}
/// <summary>
/// Initializes this script for use. Should only be completed once.
/// </summary>
private void FirstInitialize()
{
//If using ObjectShaker type.
if (base.ShakerType == ShakerTypes.ObjectShaker)
{
_objectShaker = GetComponentInParent<ObjectShaker>();
if (_objectShaker == null)
{
Debug.LogError("ObjectShaker could not be found on or above object " + gameObject.name + ". Shakable will be destroyed.", this);
DestroyImmediate(this);
return;
}
}
//If not including children.
if (!_includeChildren)
{
Rigidbody2D rb = GetComponent<Rigidbody2D>();
if (rb == null)
{
Debug.LogWarning("Rigidbody is empty on " + gameObject.name + ". Shakable will not function.", this);
DestroyImmediate(this);
return;
}
_rbData = new RigidbodyData[1];
_rbData[0] = new RigidbodyData(rb);
}
//Include children.
else
{
List<Rigidbody2D> rbs = new List<Rigidbody2D>();
Transforms.GetComponentsInChildren(transform, rbs, !_ignoreSelf, _includeInactive);
if (rbs.Count == 0)
{
Debug.LogWarning("No rigidbodies exist on parent or children of " + gameObject.name + ". Shakable will not function.", this);
DestroyImmediate(this);
return;
}
_rbData = new RigidbodyData[rbs.Count];
for (int i = 0; i < rbs.Count; i++)
_rbData[i] = new RigidbodyData(rbs[i]);
}
/* Try to find a renderer. One is required on this object for OnBecameVisible and OnBecameInvisible
* to call. If a renderer doesn't exist then add a low cost renderer. */
if (_requireInView)
{
if (GetComponent<Renderer>() == null)
{
if (Debug.isDebugBuild)
Debug.Log("Renderer not found on object. Adding renderer so RequireInView works properly. Added renderer may be smaller than actual object, and sometimes may not be detected as in view. To resolve add your own renderer to the object this script resides.", this);
SpriteRenderer r = gameObject.AddComponent<SpriteRenderer>();
r.sprite = null;
}
}
}
#region OnShakeUpdates.
/// <summary>
/// Received every fixed update a shake occurs. Contains the shake values from last update.
/// </summary>
private void CameraShakerHandler_OnShakeFixedUpdate(ShakeUpdate obj)
{
ShakeUpdateOccurred(obj);
}
/// <summary>
/// Received every fixed update a shake occurs. Contains the shake values from last update.
/// </summary>
private void ObjectShaker_OnShakeFixedUpdate(ObjectShaker arg1, ShakeUpdate arg2)
{
ShakeUpdateOccurred(arg2);
}
/// <summary>
/// Called when a shake update occurs, wether it be from CameraShaker or ObjectShaker.
/// </summary>
/// <param name="obj"></param>
private void ShakeUpdateOccurred(ShakeUpdate obj)
{
if (!_inView && _requireInView)
return;
for (int i = 0; i < _rbData.Length; i++)
{
#if UNITY_EDITOR
//Rigidbody can go null when exiting playmode, and camerashaker may try to send one last update when exiting play mode.
if (_rbData[i].Rigidbody == null)
return;
#endif
//Check if random multipliers should be updated.
CheckRandomizeRandomers(_rbData[i]);
//Calculate new offsets.
Vector2 newPos = obj.Objects.Position * _rbData[i].RandomPositionMultiplier * _positionalMultiplier;
float newRot = obj.Objects.Rotation.z * _rbData[i].RandomRotationMultiplier * _rotationalMultiplier;
//If to localize force.
if (_localizeShake)
newPos = _rbData[i].Rigidbody.transform.TransformDirection(newPos);
//Apply force.
_rbData[i].Rigidbody.AddForce(newPos - _rbData[i].LastPositional, ForceMode2D.Impulse);
_rbData[i].Rigidbody.AddTorque(newRot - _rbData[i].LastRotational, ForceMode2D.Impulse);
//Set last values.
_rbData[i].SetLastPositional(newPos);
_rbData[i].SetLastRotational(newRot);
}
}
#endregion
/// <summary>
/// Updates random multipliers if they are in need. What a fun name.
/// </summary>
private void CheckRandomizeRandomers(RigidbodyData data)
{
if (!_randomizeDirections)
return;
//Becomes true if enough time has passed to make new random multipliers.
bool newRandomize = (Time.time > data.NextRandomizeTime);
if (newRandomize)
data.SetNextRandomizeTime(Time.time + Random.Range(3f, 7f));
/* If new random multipliers or velocity is zero then randomize multipliers. */
if (newRandomize || data.Rigidbody.linearVelocity == Vector2.zero)
data.SetRandomPositionMultiplier(Floats.RandomlyFlip(data.RandomPositionMultiplier));
if (newRandomize || data.Rigidbody.angularVelocity == 0f)
data.SetRandomRotationMultiplier(Floats.RandomlyFlip(data.RandomRotationMultiplier));
}
/// <summary>
/// Received when visible in any camera.
/// </summary>
private void OnBecameVisible()
{
_inView = true;
if (_requireInView)
ChangeSubscription(true);
}
/// <summary>
/// Received when no longer visible in any camera.
/// </summary>
private void OnBecameInvisible()
{
_inView = false;
if (_requireInView)
ChangeSubscription(false);
}
/// <summary>
/// Changes the subscription to the camera shaker.
/// </summary>
/// <param name="subscribe"></param>
private void ChangeSubscription(bool subscribe)
{
//CameraShaker type.
if (base.ShakerType == ShakerTypes.CameraShaker)
{
if (subscribe)
CameraShakerHandler.OnAllShakeFixedUpdate += CameraShakerHandler_OnShakeFixedUpdate;
else
CameraShakerHandler.OnAllShakeFixedUpdate -= CameraShakerHandler_OnShakeFixedUpdate;
}
//ObjectShaker type.
else if (base.ShakerType == ShakerTypes.ObjectShaker)
{
if (_objectShaker != null)
{
if (subscribe)
_objectShaker.OnShakeFixedUpdate += ObjectShaker_OnShakeFixedUpdate;
else
_objectShaker.OnShakeFixedUpdate -= ObjectShaker_OnShakeFixedUpdate;
}
}
}
}
}