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.
523 lines
14 KiB
C#
523 lines
14 KiB
C#
using System;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using D2D;
|
|
using D2D.Utilities;
|
|
using DG.Tweening;
|
|
using UnityEngine;
|
|
|
|
namespace D2D.Animations
|
|
{
|
|
public enum AnimationPlayMode
|
|
{
|
|
PlayOnEnable,
|
|
// PlayOnVisible,
|
|
PlayByScript
|
|
}
|
|
|
|
public enum AnimationLooping
|
|
{
|
|
None,
|
|
Pong,
|
|
ToTo,
|
|
}
|
|
|
|
public abstract class DAnimation : MonoBehaviour
|
|
{
|
|
#region Variables
|
|
|
|
[Tooltip("Random end point of the animation (from x to y)")]
|
|
[SerializeField] private Vector2 _to = new Vector2(1, 1);
|
|
[SerializeField] private float to = 1;
|
|
|
|
[Tooltip("Random (from x to y) duration of animation")]
|
|
[SerializeField] private Vector2 _duration = new Vector2(.5f, .5f);
|
|
[SerializeField] private float duration = .5f;
|
|
[SerializeField] private DurationData _durationData;
|
|
|
|
[SerializeField] private Vector2 _startDelay;
|
|
[SerializeField] private float startDelay;
|
|
|
|
[SerializeField] private Vector2 _delayBetweenYoyo;
|
|
[SerializeField] private float delayBetweenYoyo;
|
|
|
|
[Tooltip("Delay between animation cycles")]
|
|
[SerializeField] private Vector2 _delayBetweenCycles;
|
|
[SerializeField] private float delayBetweenCycles;
|
|
|
|
[Space(10)]
|
|
|
|
[Tooltip("You can set it to -1 for infinite yoyo animation")]
|
|
[SerializeField] public int _loops = 1;
|
|
|
|
[SerializeField] public bool _isRelative;
|
|
|
|
[Tooltip("Need to kill this GO after animation (not looped) completed?")]
|
|
[SerializeField] public bool _destroyOnFinish;
|
|
|
|
[SerializeField] private Ease _ease = Ease.InOutSine;
|
|
|
|
[SerializeField] public AnimationPlayMode _playMode = AnimationPlayMode.PlayOnEnable;
|
|
|
|
[SerializeField] private DAnimation[] _onComplete;
|
|
[SerializeField] public AnimationCurve curve = null;
|
|
|
|
public event Action Tick;
|
|
public event Action Completed;
|
|
|
|
public bool _isFrom;
|
|
public bool _isLocal;
|
|
|
|
public bool isRandomnessSupported;
|
|
public bool isAdvancedInfoVisible = true;
|
|
public bool isOnCompleteVisible;
|
|
public bool isDurationDataMode;
|
|
public bool _isPlayingInEditor;
|
|
|
|
public bool isTargetVisibleInEditor;
|
|
[ContextMenu("Toggle target visibility")]
|
|
private void ToggleTargetVisibility() => isTargetVisibleInEditor = !isTargetVisibleInEditor;
|
|
|
|
public Vector3 beforePlayPosition;
|
|
public Vector3 beforePlayRotation;
|
|
public Vector3 beforePlayScale;
|
|
|
|
public Ease Ease => _ease;
|
|
|
|
public bool IsPlaying => CurrentTween != null && CurrentTween.IsPlaying();
|
|
|
|
public bool IsJustBorn => _plays == 0 || CurrentTween == null;
|
|
|
|
/// <summary>
|
|
/// Simple recursive algorithm which calculates whole chain duration
|
|
/// (with all _onCompletes).
|
|
/// </summary>
|
|
public float ChainDuration
|
|
{
|
|
get
|
|
{
|
|
return _onComplete.IsNullOrEmpty()
|
|
? CalculatedDuration
|
|
: CalculatedDuration + _onComplete.Max(t => t.ChainDuration);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// For now very stupid recursive algorithm.
|
|
/// For more complex solution it is recommended to find last tween in chain by hand.
|
|
/// </summary>
|
|
public DAnimation LastTweenInChain
|
|
{
|
|
get
|
|
{
|
|
return _onComplete.IsNullOrEmpty()
|
|
? this
|
|
: _onComplete.OrderBy(t => t.CalculatedDuration).First();
|
|
}
|
|
}
|
|
|
|
public bool IsLooped => _loops > 1 || _loops == -1;
|
|
|
|
public bool HasStartDelay => StartDelay != Vector2.zero;
|
|
|
|
public Tween CurrentTween { get; private set; }
|
|
|
|
[HideInInspector] public bool isIncremental;
|
|
|
|
public float CalculatedDuration
|
|
{
|
|
get
|
|
{
|
|
if (_calculatedDuration < 0)
|
|
_calculatedDuration = Duration.RandomFloat();
|
|
|
|
return _calculatedDuration;
|
|
}
|
|
set => _calculatedDuration = value;
|
|
}
|
|
private float _calculatedDuration = -1;
|
|
|
|
private bool _isCustomDuration;
|
|
|
|
protected float CalculatedTo
|
|
{
|
|
get
|
|
{
|
|
if (_calculatedTo < 0)
|
|
_calculatedTo = To.RandomFloat();
|
|
|
|
return _calculatedTo;
|
|
}
|
|
set => _calculatedTo = value;
|
|
}
|
|
private float _calculatedTo = -1;
|
|
|
|
public Transform Target
|
|
{
|
|
get
|
|
{
|
|
if (_target == null)
|
|
Target = transform;
|
|
|
|
return _target;
|
|
}
|
|
private set => _target = value;
|
|
}
|
|
public Transform _target;
|
|
|
|
private Vector2 To
|
|
=> isRandomnessSupported ? _to : new Vector2(to, to);
|
|
|
|
private Vector2 Duration
|
|
{
|
|
get
|
|
{
|
|
if (isDurationDataMode)
|
|
{
|
|
if (_durationData == null)
|
|
throw new Exception("No duration data provided!");
|
|
|
|
return _durationData.Value;
|
|
}
|
|
|
|
return isRandomnessSupported ? _duration : new Vector2(duration, duration);
|
|
}
|
|
}
|
|
|
|
private Vector2 StartDelay =>
|
|
isRandomnessSupported ? _startDelay : new Vector2(startDelay, startDelay);
|
|
|
|
private Vector2 DelayBetweenCycles =>
|
|
isRandomnessSupported ? _delayBetweenCycles : new Vector2(delayBetweenCycles, delayBetweenCycles);
|
|
|
|
private Vector2 DelayBetweenYoyo =>
|
|
isRandomnessSupported ? _delayBetweenYoyo : new Vector2(delayBetweenYoyo, delayBetweenYoyo);
|
|
|
|
// For looped animations
|
|
private Coroutine _loopCoroutine;
|
|
private Coroutine _basePlayAndReplayCoroutine;
|
|
private int _animationLoops;
|
|
private int _plays;
|
|
|
|
#endregion
|
|
|
|
private void OnValidate()
|
|
{
|
|
_plays = 0;
|
|
|
|
SetStaticRecursively(gameObject, false);
|
|
|
|
if (!_onComplete.IsNullOrEmpty())
|
|
_onComplete.ForEach(a => a._playMode = AnimationPlayMode.PlayByScript);
|
|
}
|
|
|
|
private static void SetStaticRecursively(GameObject go, bool isStatic)
|
|
{
|
|
foreach (Transform trans in go.GetComponentsInChildren<Transform>(true))
|
|
{
|
|
trans.gameObject.isStatic = isStatic;
|
|
}
|
|
}
|
|
|
|
private void OnEnable()
|
|
{
|
|
if (_playMode != AnimationPlayMode.PlayOnEnable)
|
|
return;
|
|
|
|
// Set immediately tween back to initial state (0%)
|
|
if (CurrentTween != null)
|
|
{
|
|
CurrentTween.Rewind(false);
|
|
|
|
// Reinitialize everything
|
|
ReInit(true);
|
|
CalculatedDuration = -1;
|
|
CalculatedTo = -1;
|
|
}
|
|
|
|
Play();
|
|
}
|
|
|
|
#region Initializing
|
|
|
|
/// <summary>
|
|
/// Full animation initialize.
|
|
/// </summary>
|
|
public void InitAnimation()
|
|
{
|
|
if (!_isCustomDuration)
|
|
_calculatedDuration = -1;
|
|
|
|
if (CurrentTween != null)
|
|
CurrentTween.onUpdate -= BroadcastTick;
|
|
|
|
CurrentTween = CreateTween();
|
|
|
|
CurrentTween.onUpdate += BroadcastTick;
|
|
|
|
CurrentTween.SetRelative(_isRelative).SetAutoKill(false);
|
|
|
|
if (Ease == Ease.INTERNAL_Custom)
|
|
CurrentTween.SetEase(curve);
|
|
else
|
|
CurrentTween.SetEase(_ease);
|
|
|
|
if (_destroyOnFinish && _loops == 1)
|
|
DestroySelfAfterDelay();
|
|
|
|
if (isIncremental)
|
|
{
|
|
CurrentTween.SetLoops(-1, LoopType.Incremental);
|
|
}
|
|
}
|
|
|
|
private void BroadcastTick()
|
|
{
|
|
Tick?.Invoke();
|
|
}
|
|
|
|
private void DestroySelfAfterDelay()
|
|
{
|
|
Target.gameObject.KillSelf(CalculatedDuration + TweenSettings.Instance.killDelay);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Actual children class tween specific implementation.
|
|
/// </summary>
|
|
protected abstract Tween CreateTween();
|
|
|
|
/// <summary>
|
|
/// Destroys the old animation and creates a new tween with updates parameters.
|
|
/// Mostly resets random parameters.
|
|
/// </summary>
|
|
public DAnimation ReInit(bool killPrevious = false)
|
|
{
|
|
_plays = 0;
|
|
OnReset();
|
|
|
|
if (killPrevious)
|
|
CurrentTween?.Kill();
|
|
|
|
InitAnimation();
|
|
|
|
return this;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Setters
|
|
|
|
public DAnimation SetTarget(Transform newTarget)
|
|
{
|
|
Target = newTarget;
|
|
|
|
OnSetTarget();
|
|
|
|
return this;
|
|
}
|
|
|
|
public DAnimation SetUpdateType(UpdateType type)
|
|
{
|
|
if (CurrentTween == null)
|
|
{
|
|
throw new Exception("Cant set update type for tween if CurrentTween = null");
|
|
}
|
|
|
|
CurrentTween.SetUpdate(type);
|
|
|
|
return this;
|
|
}
|
|
|
|
public DAnimation SetDuration(float d)
|
|
{
|
|
_isCustomDuration = true;
|
|
_calculatedDuration = d;
|
|
return this;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Yeah, we can: https://stackoverflow.com/questions/20078025/can-somehow-a-method-from-base-class-return-child-class
|
|
/// But we want to easily serialize DAnimation in inspector => avoid generics
|
|
/// </summary>
|
|
public virtual DAnimation SetEndPoint(Vector3 endPoint)
|
|
{
|
|
return this;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region Play methods
|
|
|
|
public DAnimation Play()
|
|
{
|
|
if (_basePlayAndReplayCoroutine != null)
|
|
StopCoroutine(_basePlayAndReplayCoroutine);
|
|
|
|
_basePlayAndReplayCoroutine =
|
|
StartCoroutine(BasePlayAndReplay(PlayCurrentTween));
|
|
|
|
return this;
|
|
}
|
|
|
|
private void PlayCurrentTween() => CurrentTween.Play();
|
|
|
|
public DAnimation Play(Vector3 endPoint, Action onComplete = null)
|
|
{
|
|
var a = SetEndPoint(endPoint).ReInit().Play();
|
|
|
|
if (onComplete != null)
|
|
a.Completed += onComplete;
|
|
|
|
return a;
|
|
}
|
|
|
|
public DAnimation Restart()
|
|
{
|
|
if (_basePlayAndReplayCoroutine != null)
|
|
StopCoroutine(_basePlayAndReplayCoroutine);
|
|
|
|
_basePlayAndReplayCoroutine =
|
|
StartCoroutine(BasePlayAndReplay(() => CurrentTween.Restart()));
|
|
|
|
return this;
|
|
}
|
|
|
|
public void KillTo0()
|
|
{
|
|
if (CurrentTween != null)
|
|
{
|
|
CurrentTween.Goto(0, true);
|
|
CurrentTween.Kill();
|
|
CurrentTween = null;
|
|
}
|
|
}
|
|
|
|
public void Kill()
|
|
{
|
|
if (CurrentTween != null)
|
|
{
|
|
CurrentTween.Kill();
|
|
CurrentTween = null;
|
|
}
|
|
}
|
|
|
|
protected virtual void OnLoop() { }
|
|
|
|
private IEnumerator PlayLooped()
|
|
{
|
|
_animationLoops = 0;
|
|
while (_loops == -1 || _animationLoops < _loops)
|
|
{
|
|
CurrentTween.Restart();
|
|
|
|
yield return new WaitForSeconds(CalculatedDuration);
|
|
|
|
yield return new WaitForSeconds(DelayBetweenYoyo.RandomFloat());
|
|
|
|
OnLoop();
|
|
|
|
CurrentTween.PlayBackwards();
|
|
|
|
yield return new WaitForSeconds(CalculatedDuration);
|
|
|
|
_animationLoops += 2;
|
|
|
|
if (_animationLoops == _loops && _destroyOnFinish)
|
|
{
|
|
RestartChainedIfHave();
|
|
InvokeCompletion();
|
|
|
|
DestroySelfAfterDelay();
|
|
}
|
|
|
|
yield return new WaitForSeconds(DelayBetweenCycles.RandomFloat());
|
|
}
|
|
}
|
|
|
|
public DAnimation Stop()
|
|
{
|
|
if (CurrentTween == null)
|
|
return null;
|
|
|
|
if (DelayBetweenCycles.RandomFloat() > 0)
|
|
{
|
|
if (_loopCoroutine != null)
|
|
StopCoroutine(_loopCoroutine);
|
|
}
|
|
else
|
|
{
|
|
CurrentTween.Pause();
|
|
}
|
|
|
|
return this;
|
|
}
|
|
|
|
private IEnumerator BasePlayAndReplay(Action callback)
|
|
{
|
|
bool needDelay = false;
|
|
|
|
if (IsJustBorn)
|
|
{
|
|
OnBeforeFirstPlayAndInit();
|
|
needDelay = true;
|
|
}
|
|
|
|
OnBeforePlayAndInit();
|
|
|
|
if (!Target.gameObject.activeSelf)
|
|
yield break;
|
|
|
|
if (HasStartDelay && needDelay)
|
|
yield return new WaitForSeconds(StartDelay.RandomFloat());
|
|
|
|
if (CurrentTween == null || _isRelative)
|
|
InitAnimation();
|
|
|
|
if (IsJustBorn)
|
|
{
|
|
if (_loops == 1)
|
|
{
|
|
CurrentTween.onComplete += RestartChainedIfHave;
|
|
CurrentTween.onComplete += InvokeCompletion;
|
|
}
|
|
}
|
|
|
|
_plays++;
|
|
|
|
if (IsLooped)
|
|
{
|
|
if (_loopCoroutine != null)
|
|
StopCoroutine(_loopCoroutine);
|
|
|
|
_loopCoroutine = StartCoroutine(PlayLooped());
|
|
}
|
|
else
|
|
{
|
|
callback?.Invoke();
|
|
}
|
|
}
|
|
|
|
private void InvokeCompletion()
|
|
{
|
|
Completed?.Invoke();
|
|
}
|
|
|
|
private void RestartChainedIfHave()
|
|
{
|
|
if (!_onComplete.IsNullOrEmpty())
|
|
_onComplete.ForEach(c => c.Restart());
|
|
}
|
|
|
|
#endregion
|
|
|
|
protected virtual void OnReset() { }
|
|
|
|
protected virtual void OnBeforePlayAndInit() { }
|
|
|
|
protected virtual void OnBeforeFirstPlayAndInit() { }
|
|
|
|
protected virtual void OnSetTarget() { }
|
|
}
|
|
}
|