You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
294 lines
6.4 KiB
C#
294 lines
6.4 KiB
C#
using System;
|
|
using UnityEngine;
|
|
using System.Collections.Generic;
|
|
using UnityEasing;
|
|
|
|
namespace Projectiles
|
|
{
|
|
using Random = UnityEngine.Random;
|
|
|
|
[Serializable]
|
|
public class ShakeSetup
|
|
{
|
|
public float Duration = 0.5f;
|
|
public float Magnitude = 0.05f;
|
|
public float Frequency = 10f;
|
|
public float FadeIn = 0.1f;
|
|
public float FadeOut = 0.2f;
|
|
public Ease Ease = Ease.Linear;
|
|
public Vector3 Axis = new(1f, 1f, 1f);
|
|
public EShakeTarget Target = EShakeTarget.Position;
|
|
}
|
|
|
|
public enum EShakeTarget
|
|
{
|
|
None,
|
|
Position,
|
|
Rotation,
|
|
}
|
|
|
|
public enum EShakeForce
|
|
{
|
|
None,
|
|
ReplaceSame,
|
|
Add,
|
|
}
|
|
|
|
public class ShakeEffect : CoreBehaviour
|
|
{
|
|
// PUBLIC MEMBERS
|
|
|
|
public bool IsPlaying => _activeShakes.Count > 0f;
|
|
|
|
// PRIVATE MEMBERS
|
|
|
|
[SerializeField]
|
|
private ShakeSetup _defaultSetup;
|
|
|
|
private List<ShakeData> _activeShakes = new(32);
|
|
|
|
private Vector3 _defaultPosition;
|
|
private Quaternion _defaultRotation;
|
|
|
|
// PUBLIC MEMBERS
|
|
|
|
public void Play(ShakeSetup setup, EShakeForce force = EShakeForce.Add)
|
|
{
|
|
if (setup == null || setup.Target == EShakeTarget.None || setup.Duration <= 0f || setup.Magnitude <= 0f)
|
|
return;
|
|
|
|
if (IsPlaying == false || force == EShakeForce.Add)
|
|
{
|
|
AddShake(setup);
|
|
}
|
|
else if (force == EShakeForce.ReplaceSame)
|
|
{
|
|
for (int i = 0; i < _activeShakes.Count; i++)
|
|
{
|
|
var shake = _activeShakes[i];
|
|
|
|
if (shake.Setup == setup)
|
|
{
|
|
shake.Cooldown = Mathf.Max(setup.Duration - setup.FadeIn, shake.Cooldown);
|
|
return;
|
|
}
|
|
}
|
|
|
|
AddShake(setup);
|
|
}
|
|
}
|
|
|
|
public void Play(EShakeForce force = EShakeForce.Add)
|
|
{
|
|
Play(_defaultSetup, force);
|
|
}
|
|
|
|
public void Stop(ShakeSetup setup, bool immediate = false)
|
|
{
|
|
if (IsPlaying == false)
|
|
return;
|
|
|
|
for (int i = 0; i < _activeShakes.Count; i++)
|
|
{
|
|
var shake = _activeShakes[i];
|
|
|
|
if (shake.Setup != setup)
|
|
continue;
|
|
|
|
if (immediate == true || shake.Setup.FadeOut <= 0f)
|
|
{
|
|
RemoveShake(i);
|
|
return;
|
|
}
|
|
|
|
shake.Cooldown = Mathf.Min(shake.Cooldown, shake.Setup.FadeOut);
|
|
}
|
|
}
|
|
|
|
public void Stop(bool immediate = false)
|
|
{
|
|
if (IsPlaying == false)
|
|
return;
|
|
|
|
for (int i = _activeShakes.Count - 1; i >= 0; i--)
|
|
{
|
|
Stop(_activeShakes[i].Setup, immediate);
|
|
}
|
|
}
|
|
|
|
// MONOBEHAVIOUR
|
|
|
|
protected void Awake()
|
|
{
|
|
_defaultPosition = transform.localPosition;
|
|
_defaultRotation = transform.localRotation;
|
|
}
|
|
|
|
protected void Update()
|
|
{
|
|
if (IsPlaying == false)
|
|
{
|
|
transform.localPosition = _defaultPosition;
|
|
transform.localRotation = _defaultRotation;
|
|
return;
|
|
}
|
|
|
|
var positionOffset = Vector3.zero;
|
|
var rotationOffset = Vector3.zero;
|
|
|
|
for (int i = 0; i < _activeShakes.Count; i++)
|
|
{
|
|
var shake = _activeShakes[i];
|
|
|
|
if (shake.Setup.Target == EShakeTarget.Position)
|
|
{
|
|
positionOffset += shake.GetOffset(Time.deltaTime);
|
|
}
|
|
else
|
|
{
|
|
rotationOffset += shake.GetOffset(Time.deltaTime);
|
|
}
|
|
}
|
|
|
|
transform.localPosition = _defaultPosition + positionOffset;
|
|
transform.localRotation = _defaultRotation * Quaternion.Euler(rotationOffset);
|
|
|
|
for (int i = _activeShakes.Count - 1; i >= 0; i--)
|
|
{
|
|
if (_activeShakes[i].IsFinished == true)
|
|
{
|
|
RemoveShake(i);
|
|
}
|
|
}
|
|
}
|
|
|
|
// PRIVATE METHODS
|
|
|
|
private void AddShake(ShakeSetup setup)
|
|
{
|
|
var shake = Pool.Get<ShakeData>();
|
|
|
|
shake.Reset(setup);
|
|
_activeShakes.Add(shake);
|
|
}
|
|
|
|
private void RemoveShake(int index)
|
|
{
|
|
var shakeData = _activeShakes[index];
|
|
_activeShakes.RemoveAt(index);
|
|
|
|
Pool.Return(shakeData);
|
|
}
|
|
|
|
// HELPERS
|
|
|
|
private class ShakeData
|
|
{
|
|
public ShakeSetup Setup;
|
|
public float Cooldown;
|
|
|
|
public bool IsFinished => Cooldown <= 0f;
|
|
|
|
|
|
[NonSerialized]
|
|
private float _elapsedTime;
|
|
[NonSerialized]
|
|
private float _normalChangeDuration;
|
|
[NonSerialized]
|
|
private float _changeDuration;
|
|
|
|
[NonSerialized]
|
|
private Vector3 _startPosition;
|
|
[NonSerialized]
|
|
private Vector3 _targetPosition;
|
|
[NonSerialized]
|
|
private float _changeCooldown;
|
|
[NonSerialized]
|
|
private Vector3 _lastOffset;
|
|
|
|
private float _changeDurationMultiplier;
|
|
|
|
public void Reset(ShakeSetup setup)
|
|
{
|
|
Setup = setup;
|
|
Cooldown = setup.Duration;
|
|
|
|
_elapsedTime = 0f;
|
|
|
|
_normalChangeDuration = 1f / setup.Frequency;
|
|
_changeDuration = _normalChangeDuration;
|
|
_changeCooldown = 0f;
|
|
|
|
_startPosition = Vector3.zero;
|
|
_targetPosition = Vector3.zero;
|
|
_lastOffset = Vector3.zero;
|
|
}
|
|
|
|
public Vector3 GetOffset(float deltaTime)
|
|
{
|
|
bool isStart = _elapsedTime == 0f;
|
|
bool wasEnd = Cooldown <= _normalChangeDuration * 0.5f;
|
|
|
|
_elapsedTime += deltaTime;
|
|
|
|
Cooldown -= deltaTime;
|
|
_changeCooldown -= deltaTime;
|
|
|
|
bool isEnd = wasEnd == false && Cooldown <= _normalChangeDuration * 0.5f;
|
|
|
|
if (_changeCooldown <= 0f || isEnd == true)
|
|
{
|
|
float magnitudeProgress = 1f;
|
|
|
|
if (Setup.FadeIn > 0f && _elapsedTime < Setup.FadeIn)
|
|
{
|
|
magnitudeProgress = _elapsedTime / Setup.FadeIn;
|
|
}
|
|
else if (Setup.FadeOut > 0f && Cooldown < Setup.FadeOut)
|
|
{
|
|
magnitudeProgress = Cooldown / Setup.FadeOut;
|
|
}
|
|
|
|
float magnitude = Setup.Magnitude * magnitudeProgress;
|
|
|
|
// Recalculate change duration in case the frequency changed
|
|
_normalChangeDuration = 1f / Setup.Frequency;
|
|
|
|
if (isEnd == true)
|
|
{
|
|
_startPosition = _lastOffset;
|
|
_targetPosition = Vector3.zero;
|
|
|
|
_changeDuration = Cooldown + Time.deltaTime;
|
|
_changeCooldown = Cooldown;
|
|
}
|
|
else if (isStart == true)
|
|
{
|
|
_startPosition = Vector3.zero;
|
|
_targetPosition = Vector3.Scale(Random.onUnitSphere, Setup.Axis).normalized * magnitude;
|
|
|
|
// We are covering only half of the shake distance on start
|
|
_changeDuration = _normalChangeDuration * 0.5f;
|
|
_changeCooldown += _changeDuration;
|
|
}
|
|
else
|
|
{
|
|
_startPosition = _targetPosition;
|
|
|
|
var randomRotation = Quaternion.Euler(Random.Range(-60, 60), Random.Range(-60, 60), Random.Range(-60, 60));
|
|
_targetPosition = Vector3.Scale(randomRotation * -_targetPosition, Setup.Axis).normalized * magnitude;
|
|
|
|
_changeDuration = _normalChangeDuration;
|
|
_changeCooldown += _changeDuration;
|
|
}
|
|
}
|
|
|
|
float progress = 1 - _changeCooldown / _changeDuration;
|
|
_lastOffset = Vector3.Lerp(_startPosition, _targetPosition, Setup.Ease.Get(progress));
|
|
|
|
return _lastOffset;
|
|
}
|
|
}
|
|
}
|
|
}
|