618 lines
26 KiB
C#
618 lines
26 KiB
C#
#if PRIME_TWEEN_SAFETY_CHECKS && UNITY_ASSERTIONS
|
|
#define SAFETY_CHECKS
|
|
#endif
|
|
#if PRIME_TWEEN_INSPECTOR_DEBUGGING && UNITY_EDITOR
|
|
#define ENABLE_SERIALIZATION
|
|
#endif
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using JetBrains.Annotations;
|
|
using UnityEngine;
|
|
|
|
namespace PrimeTween {
|
|
/// <summary>An ordered group of tweens and callbacks. Tweens in a sequence can run in parallel to one another with <see cref="Group"/> and sequentially with <see cref="Chain"/>.<br/>
|
|
/// To make tweens in a Sequence overlap each other, use <see cref="TweenSettings.startDelay"/> and <see cref="TweenSettings.endDelay"/>.</summary>
|
|
/// <example><code>
|
|
/// Sequence.Create()
|
|
/// .Group(Tween.PositionX(transform, endValue: 10f, duration: 1.5f))
|
|
/// .Group(Tween.Scale(transform, endValue: 2f, duration: 0.5f)) // position and localScale tweens will run in parallel because they are 'grouped'
|
|
/// .Chain(Tween.Rotation(transform, endValue: new Vector3(0f, 0f, 45f), duration: 1f)) // rotation tween is 'chained' so it will start when both previous tweens are finished (after 1.5 seconds)
|
|
/// .ChainCallback(() => Debug.Log("Sequence completed"));
|
|
/// </code></example>
|
|
#if ENABLE_SERIALIZATION
|
|
[Serializable]
|
|
#endif
|
|
public
|
|
#if !ENABLE_SERIALIZATION && UNITY_2020_3_OR_NEWER
|
|
readonly // duration setter produces error in Unity <= 2019.4.40: error CS1604: Cannot assign to 'this' because it is read-only
|
|
#endif
|
|
partial struct Sequence : IEquatable<Sequence> {
|
|
const int emptySequenceTag = -43;
|
|
internal
|
|
#if !ENABLE_SERIALIZATION && UNITY_2020_3_OR_NEWER
|
|
readonly
|
|
#endif
|
|
Tween root;
|
|
internal bool IsCreated => root.IsCreated;
|
|
long id => root.id;
|
|
|
|
/// Sequence is 'alive' when any of its tweens is 'alive'.
|
|
public bool isAlive => root.isAlive;
|
|
|
|
/// Elapsed time of the current cycle.
|
|
public float elapsedTime {
|
|
get => root.elapsedTime;
|
|
set => root.elapsedTime = value;
|
|
}
|
|
|
|
/// The total number of cycles. Returns -1 to indicate infinite number cycles.
|
|
public int cyclesTotal => root.cyclesTotal;
|
|
public int cyclesDone => root.cyclesDone;
|
|
/// The duration of one cycle.
|
|
public float duration {
|
|
get => root.duration;
|
|
private set {
|
|
Assert.IsTrue(isAlive);
|
|
Assert.IsTrue(root.tween.isMainSequenceRoot());
|
|
var rootTween = root.tween;
|
|
Assert.AreEqual(0f, elapsedTimeTotal);
|
|
Assert.IsTrue(value >= rootTween.cycleDuration);
|
|
Assert.IsTrue(value >= rootTween.settings.duration);
|
|
Assert.AreEqual(0f, rootTween.settings.startDelay);
|
|
Assert.AreEqual(0f, rootTween.settings.endDelay);
|
|
rootTween.settings.duration = value;
|
|
rootTween.cycleDuration = value;
|
|
}
|
|
}
|
|
|
|
/// Elapsed time of all cycles.
|
|
public float elapsedTimeTotal {
|
|
get => root.elapsedTimeTotal;
|
|
set => root.elapsedTimeTotal = value;
|
|
}
|
|
|
|
/// <summary>The duration of all cycles. If cycles == -1, returns <see cref="float.PositiveInfinity"/>.</summary>
|
|
public float durationTotal => root.durationTotal;
|
|
|
|
/// Normalized progress of the current cycle expressed in 0..1 range.
|
|
public float progress {
|
|
get => root.progress;
|
|
set => root.progress = value;
|
|
}
|
|
|
|
/// Normalized progress of all cycles expressed in 0..1 range.
|
|
public float progressTotal {
|
|
get => root.progressTotal;
|
|
set => root.progressTotal = value;
|
|
}
|
|
|
|
bool tryManipulate() => root.tryManipulate();
|
|
|
|
bool ValidateCanManipulateSequence() {
|
|
if (!tryManipulate()) {
|
|
return false;
|
|
}
|
|
if (root.elapsedTimeTotal != 0f) {
|
|
Debug.LogError(Constants.animationAlreadyStarted);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public static Sequence Create(int cycles = 1, CycleMode cycleMode = CycleMode.Restart, Ease sequenceEase = Ease.Linear, bool useUnscaledTime = false, UpdateType updateType = default) {
|
|
#if UNITY_EDITOR
|
|
if (Constants.warnNoInstance) {
|
|
return default;
|
|
}
|
|
#endif
|
|
var tween = PrimeTweenManager.fetchTween();
|
|
if (cycleMode == CycleMode.Incremental) {
|
|
Debug.LogError($"Sequence doesn't support CycleMode.Incremental. Parameter {nameof(sequenceEase)} is applied to the sequence's 'timeline', and incrementing the 'timeline' doesn't make sense. For the same reason, {nameof(sequenceEase)} is clamped to [0:1] range.");
|
|
cycleMode = CycleMode.Restart;
|
|
}
|
|
if (sequenceEase == Ease.Custom) {
|
|
Debug.LogError("Sequence doesn't support Ease.Custom.");
|
|
sequenceEase = Ease.Linear;
|
|
}
|
|
if (sequenceEase == Ease.Default) { // todo this is questionable
|
|
sequenceEase = Ease.Linear;
|
|
}
|
|
var settings = new TweenSettings(0f, sequenceEase, cycles, cycleMode, 0f, 0f, useUnscaledTime, updateType);
|
|
tween.Setup(PrimeTweenManager.dummyTarget, ref settings, _ => {}, null, false, TweenType.MainSequence);
|
|
tween.intParam = emptySequenceTag;
|
|
var root = PrimeTweenManager.addTween(tween);
|
|
Assert.IsTrue(root.isAlive);
|
|
return new Sequence(root);
|
|
}
|
|
|
|
public static Sequence Create(Tween firstTween) {
|
|
#if UNITY_EDITOR
|
|
if (Constants.warnNoInstance) {
|
|
return default;
|
|
}
|
|
#endif
|
|
return Create().Group(firstTween);
|
|
}
|
|
|
|
Sequence(Tween rootTween) {
|
|
root = rootTween;
|
|
setSequence(rootTween);
|
|
Assert.IsTrue(isAlive);
|
|
Assert.AreEqual(0f, duration);
|
|
Assert.IsTrue(durationTotal == 0f || float.IsPositiveInfinity(durationTotal));
|
|
}
|
|
|
|
/// <summary>Groups <paramref name="tween"/> with the 'previous' animation in this Sequence.<br/>
|
|
/// The 'previous' animation is the animation used in the preceding Group/Chain/Insert() method call.<br/>
|
|
/// Grouped animations start at the same time and run in parallel.</summary>
|
|
public Sequence Group(Tween tween) {
|
|
if (tryManipulate()) {
|
|
Insert(getLastInSelfOrRoot().tween.waitDelay, tween);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
void addLinkedReference(Tween tween) {
|
|
Tween last;
|
|
if (root.tween.next.IsCreated) {
|
|
last = getLast();
|
|
var lastInSelf = getLastInSelfOrRoot();
|
|
Assert.AreNotEqual(root.id, lastInSelf.id);
|
|
Assert.IsFalse(lastInSelf.tween.nextSibling.IsCreated);
|
|
lastInSelf.tween.nextSibling = tween;
|
|
Assert.IsFalse(tween.tween.prevSibling.IsCreated);
|
|
tween.tween.prevSibling = lastInSelf;
|
|
} else {
|
|
last = root;
|
|
}
|
|
|
|
Assert.IsFalse(last.tween.next.IsCreated);
|
|
Assert.IsFalse(tween.tween.prev.IsCreated);
|
|
last.tween.next = tween;
|
|
tween.tween.prev = last;
|
|
root.tween.intParam = emptySequenceTag - emptySequenceTag; // set to 0 in a way to be able to search the code better
|
|
}
|
|
|
|
Tween getLast() {
|
|
Tween result = default;
|
|
foreach (var current in getAllTweens()) {
|
|
result = current;
|
|
}
|
|
Assert.IsTrue(result.IsCreated);
|
|
Assert.IsFalse(result.tween.next.IsCreated);
|
|
return result;
|
|
}
|
|
|
|
/// <summary>Places <paramref name="tween"/> after all previously added animations in this sequence. Chained animations run sequentially after one another.</summary>
|
|
public Sequence Chain(Tween tween) {
|
|
if (tryManipulate()) {
|
|
Insert(duration, tween);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/// <summary>Places <paramref name="tween"/> inside this Sequence at time <paramref name="atTime"/>, overlapping with other animations.<br/>
|
|
/// The total sequence duration is increased if the inserted <paramref name="tween"/> doesn't fit inside the current sequence duration.</summary>
|
|
public Sequence Insert(float atTime, Tween tween) {
|
|
if (!ValidateCanAdd(tween)) {
|
|
return this;
|
|
}
|
|
if (tween.tween.sequence.IsCreated) {
|
|
Debug.LogError($"{Constants.nestTweenTwiceError} Tween: {tween.tween.GetDescription()}");
|
|
return this;
|
|
}
|
|
setSequence(tween);
|
|
Insert_internal(atTime, tween);
|
|
return this;
|
|
}
|
|
|
|
void Insert_internal(float atTime, Tween other) {
|
|
Assert.AreEqual(0f, other.tween.waitDelay);
|
|
other.tween.waitDelay = atTime;
|
|
duration = Mathf.Max(duration, other.durationWithWaitDelay);
|
|
addLinkedReference(other);
|
|
}
|
|
|
|
/// <summary>Schedules <see cref="callback"/> after all previously added tweens.</summary>
|
|
/// <param name="warnIfTargetDestroyed">https://github.com/KyryloKuzyk/PrimeTween/discussions/4</param>
|
|
public Sequence ChainCallback([NotNull] Action callback, bool warnIfTargetDestroyed = true) {
|
|
if (tryManipulate()) {
|
|
InsertCallback(duration, callback, warnIfTargetDestroyed);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
public Sequence InsertCallback(float atTime, Action callback, bool warnIfTargetDestroyed = true) {
|
|
if (!tryManipulate()) {
|
|
return this;
|
|
}
|
|
var delay = PrimeTweenManager.delayWithoutDurationCheck(PrimeTweenManager.dummyTarget, 0f, false);
|
|
Assert.IsTrue(delay.HasValue);
|
|
delay.Value.tween.OnComplete(callback, warnIfTargetDestroyed);
|
|
return Insert(atTime, delay.Value);
|
|
}
|
|
|
|
/// <summary>Schedules <see cref="callback"/> after all previously added tweens. Passing 'target' allows to write a non-allocating callback.</summary>
|
|
/// <param name="warnIfTargetDestroyed">https://github.com/KyryloKuzyk/PrimeTween/discussions/4</param>
|
|
public Sequence ChainCallback<T>([NotNull] T target, [NotNull] Action<T> callback, bool warnIfTargetDestroyed = true) where T: class {
|
|
if (tryManipulate()) {
|
|
InsertCallback(duration, target, callback, warnIfTargetDestroyed);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
public Sequence InsertCallback<T>(float atTime, [NotNull] T target, Action<T> callback, bool warnIfTargetDestroyed = true) where T: class {
|
|
if (!tryManipulate()) {
|
|
return this;
|
|
}
|
|
var delay = PrimeTweenManager.delayWithoutDurationCheck(target, 0f, false);
|
|
if (!delay.HasValue) {
|
|
return this;
|
|
}
|
|
delay.Value.tween.OnComplete(target, callback, warnIfTargetDestroyed);
|
|
return Insert(atTime, delay.Value);
|
|
}
|
|
|
|
/// <summary>Schedules delay after all previously added tweens.</summary>
|
|
public Sequence ChainDelay(float duration) {
|
|
return Chain(Tween.Delay(duration));
|
|
}
|
|
|
|
Tween getLastInSelfOrRoot() {
|
|
Assert.IsTrue(isAlive);
|
|
var result = root;
|
|
foreach (var current in getSelfChildren()) {
|
|
result = current;
|
|
}
|
|
Assert.IsTrue(result.IsCreated);
|
|
Assert.IsFalse(result.tween.nextSibling.IsCreated);
|
|
return result;
|
|
}
|
|
|
|
void setSequence(Tween handle) {
|
|
Assert.IsTrue(IsCreated);
|
|
Assert.IsTrue(handle.isAlive);
|
|
var tween = handle.tween;
|
|
Assert.IsFalse(tween.sequence.IsCreated);
|
|
tween.sequence = this;
|
|
}
|
|
|
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("ReSharper", "CompareOfFloatsByEqualityOperator")]
|
|
bool ValidateCanAdd(Tween other) {
|
|
if (!ValidateCanManipulateSequence()) {
|
|
return false;
|
|
}
|
|
if (!other.isAlive) {
|
|
Debug.LogError(Constants.addDeadTweenToSequenceError);
|
|
return false;
|
|
}
|
|
var tween = other.tween;
|
|
if (tween.settings.cycles == -1) {
|
|
Debug.LogError(Constants.infiniteTweenInSequenceError);
|
|
return false;
|
|
}
|
|
var rootTween = root.tween;
|
|
if (tween._isPaused && tween._isPaused != rootTween._isPaused) {
|
|
warnIgnoredChildrenSetting(nameof(isPaused));
|
|
}
|
|
if (tween.timeScale != 1f && tween.timeScale != rootTween.timeScale) {
|
|
warnIgnoredChildrenSetting(nameof(timeScale));
|
|
}
|
|
if (tween.settings.useUnscaledTime && tween.settings.useUnscaledTime != rootTween.settings.useUnscaledTime) {
|
|
warnIgnoredChildrenSetting(nameof(TweenSettings.useUnscaledTime));
|
|
}
|
|
if (tween.settings._updateType != _UpdateType.Default && tween.settings._updateType != rootTween.settings._updateType) {
|
|
warnIgnoredChildrenSetting(nameof(TweenSettings.updateType));
|
|
}
|
|
void warnIgnoredChildrenSetting(string settingName) {
|
|
Debug.LogError($"'{settingName}' was ignored after adding child animation to the Sequence. Parent Sequence controls '{settingName}' of all its children animations.\n" +
|
|
"To prevent this error:\n" +
|
|
$"- Use the default value of '{settingName}' in child animation.\n" +
|
|
$"- OR use the same '{settingName}' in child animation.\n\n");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// Stops all tweens in the Sequence, ignoring callbacks.
|
|
public void Stop() {
|
|
if (isAlive && tryManipulate()) {
|
|
Assert.IsTrue(root.tween.isMainSequenceRoot());
|
|
releaseTweens();
|
|
Assert.IsFalse(isAlive);
|
|
}
|
|
}
|
|
|
|
/// <summary>Immediately completes the sequence.<br/>
|
|
/// If the sequence has infinite cycles (cycles == -1), completes only the current cycle. To choose where the sequence should stop (at the 'start' or at the 'end') in the case of infinite cycles, use <see cref="SetRemainingCycles(bool stopAtEndValue)"/> before calling Complete().</summary>
|
|
public void Complete() {
|
|
if (isAlive && tryManipulate()) {
|
|
if (cyclesTotal == -1 || root.tween.settings.cycleMode == CycleMode.Restart) {
|
|
SetRemainingCycles(1);
|
|
} else {
|
|
int cyclesLeft = cyclesTotal - cyclesDone;
|
|
SetRemainingCycles(cyclesLeft % 2 == 1 ? 1 : 2);
|
|
}
|
|
root.isPaused = false;
|
|
Assert.IsTrue(root.tween.isMainSequenceRoot());
|
|
root.tween.updateSequence(float.MaxValue, false, allowSkipChildrenUpdate: false);
|
|
Assert.IsFalse(isAlive);
|
|
}
|
|
}
|
|
|
|
internal void emergencyStop() {
|
|
Assert.IsTrue(isAlive);
|
|
Assert.IsTrue(root.tween.isMainSequenceRoot());
|
|
releaseTweens(t => t.warnOnCompleteIgnored(false));
|
|
}
|
|
|
|
internal void releaseTweens([CanBeNull] Action<ReusableTween> beforeKill = null) {
|
|
var enumerator = getAllTweens();
|
|
enumerator.MoveNext();
|
|
var current = enumerator.Current;
|
|
Assert.IsTrue(current.isAlive);
|
|
while (true) {
|
|
// ReSharper disable once RedundantCast
|
|
Tween? next = enumerator.MoveNext() ? enumerator.Current : (Tween?)null;
|
|
var tween = current.tween;
|
|
Assert.IsTrue(tween._isAlive);
|
|
beforeKill?.Invoke(tween);
|
|
tween.kill();
|
|
Assert.IsFalse(tween._isAlive);
|
|
releaseTween(tween);
|
|
if (!next.HasValue) {
|
|
break;
|
|
}
|
|
current = next.Value;
|
|
}
|
|
Assert.IsFalse(isAlive); // not IsCreated because this may be a local variable in the user's codebase
|
|
}
|
|
|
|
static void releaseTween([NotNull] ReusableTween tween) {
|
|
// Debug.Log($"[{Time.frameCount}] releaseTween {tween.id}");
|
|
Assert.AreNotEqual(0, tween.sequence.root.id);
|
|
tween.next = default;
|
|
tween.prev = default;
|
|
tween.prevSibling = default;
|
|
tween.nextSibling = default;
|
|
tween.sequence = default;
|
|
if (tween.isSequenceRoot()) {
|
|
tween.tweenType = TweenType.None;
|
|
Assert.IsFalse(tween.isSequenceRoot());
|
|
}
|
|
}
|
|
|
|
internal SequenceChildrenEnumerator getAllChildren() {
|
|
var enumerator = getAllTweens();
|
|
var movedNext = enumerator.MoveNext(); // skip self
|
|
Assert.IsTrue(movedNext);
|
|
Assert.AreEqual(root, enumerator.Current);
|
|
return enumerator;
|
|
}
|
|
|
|
/// <summary>Stops the sequence when it reaches the 'end' or returns to the 'start' for the next time.<br/>
|
|
/// For example, if you have an infinite sequence (cycles == -1) with CycleMode.Yoyo/Rewind, and you wish to stop it when it reaches the 'end', then set <see cref="stopAtEndValue"/> to true.
|
|
/// To stop the animation at the 'beginning', set <see cref="stopAtEndValue"/> to false.</summary>
|
|
public void SetRemainingCycles(bool stopAtEndValue) {
|
|
root.SetRemainingCycles(stopAtEndValue);
|
|
}
|
|
|
|
/// <summary>Sets the number of remaining cycles.<br/>
|
|
/// This method modifies the <see cref="cyclesTotal"/> so that the sequence will complete after the number of <see cref="cycles"/>.<br/>
|
|
/// To set the initial number of cycles, use Sequence.Create(cycles: numCycles) instead.<br/><br/>
|
|
/// Setting cycles to -1 will repeat the sequence indefinitely.<br/>
|
|
/// </summary>
|
|
public void SetRemainingCycles(int cycles) {
|
|
root.SetRemainingCycles(cycles);
|
|
}
|
|
|
|
public bool isPaused {
|
|
get => root.isPaused;
|
|
set => root.isPaused = value;
|
|
}
|
|
|
|
internal SequenceDirectEnumerator getSelfChildren(bool isForward = true) => new SequenceDirectEnumerator(this, isForward);
|
|
internal SequenceChildrenEnumerator getAllTweens() => new SequenceChildrenEnumerator(this);
|
|
|
|
public override string ToString() => root.ToString();
|
|
|
|
internal struct SequenceDirectEnumerator {
|
|
readonly Sequence sequence;
|
|
Tween current;
|
|
readonly bool isEmpty;
|
|
readonly bool isForward;
|
|
bool isStarted;
|
|
|
|
internal SequenceDirectEnumerator(Sequence s, bool isForward) {
|
|
Assert.IsTrue(s.isAlive, s.id);
|
|
sequence = s;
|
|
this.isForward = isForward;
|
|
isStarted = false;
|
|
isEmpty = isSequenceEmpty(s);
|
|
if (isEmpty) {
|
|
current = default;
|
|
return;
|
|
}
|
|
current = sequence.root.tween.next;
|
|
Assert.IsTrue(current.IsCreated && current.id != sequence.root.tween.nextSibling.id);
|
|
if (!isForward) {
|
|
while (true) {
|
|
var next = current.tween.nextSibling;
|
|
if (!next.IsCreated) {
|
|
break;
|
|
}
|
|
current = next;
|
|
}
|
|
}
|
|
Assert.IsTrue(current.IsCreated);
|
|
}
|
|
|
|
static bool isSequenceEmpty(Sequence s) {
|
|
// tests: SequenceNestingDifferentSettings(), TestSequenceEnumeratorWithEmptySequences()
|
|
return s.root.tween.intParam == emptySequenceTag;
|
|
}
|
|
|
|
public
|
|
#if UNITY_2020_2_OR_NEWER
|
|
readonly
|
|
#endif
|
|
SequenceDirectEnumerator GetEnumerator() {
|
|
Assert.IsTrue(sequence.isAlive);
|
|
return this;
|
|
}
|
|
|
|
public
|
|
#if UNITY_2020_2_OR_NEWER
|
|
readonly
|
|
#endif
|
|
Tween Current {
|
|
get {
|
|
Assert.IsTrue(sequence.isAlive);
|
|
Assert.IsTrue(current.IsCreated);
|
|
Assert.IsNotNull(current.tween);
|
|
Assert.AreEqual(current.id, current.tween.id);
|
|
Assert.IsTrue(current.tween.sequence.IsCreated);
|
|
return current;
|
|
}
|
|
}
|
|
|
|
public bool MoveNext() {
|
|
if (isEmpty) {
|
|
return false;
|
|
}
|
|
Assert.IsTrue(current.isAlive);
|
|
if (!isStarted) {
|
|
isStarted = true;
|
|
return true;
|
|
}
|
|
current = isForward ? current.tween.nextSibling : current.tween.prevSibling;
|
|
return current.IsCreated;
|
|
}
|
|
}
|
|
|
|
internal struct SequenceChildrenEnumerator {
|
|
readonly Sequence sequence;
|
|
Tween current;
|
|
bool isStarted;
|
|
|
|
internal SequenceChildrenEnumerator(Sequence s) {
|
|
Assert.IsTrue(s.isAlive);
|
|
Assert.IsTrue(s.root.tween.isMainSequenceRoot());
|
|
sequence = s;
|
|
current = default;
|
|
isStarted = false;
|
|
}
|
|
|
|
public
|
|
#if UNITY_2020_2_OR_NEWER
|
|
readonly
|
|
#endif
|
|
SequenceChildrenEnumerator GetEnumerator() {
|
|
Assert.IsTrue(sequence.isAlive);
|
|
return this;
|
|
}
|
|
|
|
public
|
|
#if UNITY_2020_2_OR_NEWER
|
|
readonly
|
|
#endif
|
|
Tween Current {
|
|
get {
|
|
Assert.IsTrue(current.IsCreated);
|
|
Assert.IsNotNull(current.tween);
|
|
Assert.AreEqual(current.id, current.tween.id);
|
|
Assert.IsTrue(current.tween.sequence.IsCreated);
|
|
return current;
|
|
}
|
|
}
|
|
|
|
public bool MoveNext() {
|
|
if (!isStarted) {
|
|
Assert.IsFalse(current.IsCreated);
|
|
current = sequence.root;
|
|
isStarted = true;
|
|
return true;
|
|
}
|
|
Assert.IsTrue(current.isAlive);
|
|
current = current.tween.next;
|
|
return current.IsCreated;
|
|
}
|
|
}
|
|
|
|
/// <summary>Places <paramref name="sequence"/> after all previously added animations in this sequence. Chained animations run sequentially after one another.</summary>
|
|
public Sequence Chain(Sequence sequence) {
|
|
if (tryManipulate()) {
|
|
Insert(duration, sequence);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/// <summary>Groups <paramref name="sequence"/> with the 'previous' animation in this Sequence.<br/>
|
|
/// The 'previous' animation is the animation used in the preceding Group/Chain/Insert() method call.<br/>
|
|
/// Grouped animations start at the same time and run in parallel.</summary>
|
|
public Sequence Group(Sequence sequence) {
|
|
if (tryManipulate()) {
|
|
Insert(getLastInSelfOrRoot().tween.waitDelay, sequence);
|
|
}
|
|
return this;
|
|
}
|
|
|
|
/// <summary>Places <paramref name="sequence"/> inside this Sequence at time <paramref name="atTime"/>, overlapping with other animations.<br/>
|
|
/// The total sequence duration is increased if the inserted <paramref name="sequence"/> doesn't fit inside the current sequence duration.</summary>
|
|
public Sequence Insert(float atTime, Sequence sequence) {
|
|
if (!ValidateCanAdd(sequence.root)) {
|
|
return this;
|
|
}
|
|
|
|
ref var otherTweenType = ref sequence.root.tween.tweenType;
|
|
if (otherTweenType != TweenType.MainSequence) {
|
|
Debug.LogError(Constants.nestSequenceTwiceError);
|
|
return this;
|
|
}
|
|
otherTweenType = TweenType.NestedSequence;
|
|
|
|
Insert_internal(atTime, sequence.root);
|
|
validateSequenceEnumerator();
|
|
return this;
|
|
}
|
|
|
|
/// <summary>Custom timeScale. To smoothly animate timeScale over time, use <see cref="Tween.TweenTimeScale"/> method.</summary>
|
|
public float timeScale {
|
|
get => root.timeScale;
|
|
set => root.timeScale = value;
|
|
}
|
|
|
|
[System.Diagnostics.Conditional("SAFETY_CHECKS")]
|
|
void validateSequenceEnumerator() {
|
|
var buffer = new List<ReusableTween> {
|
|
root.tween
|
|
};
|
|
foreach (var t in getAllTweens()) {
|
|
// Debug.Log($"----- {t}");
|
|
if (t.tween.isSequenceRoot()) {
|
|
foreach (var ch in t.tween.sequence.getSelfChildren()) {
|
|
// Debug.Log(ch);
|
|
buffer.Add(ch.tween);
|
|
}
|
|
}
|
|
}
|
|
if (buffer.Count != buffer.Select(_ => _.id).Distinct().Count()) {
|
|
Debug.LogError($"{root.id}, duplicates in validateSequenceEnumerator():\n{string.Join("\n", buffer)}");
|
|
}
|
|
}
|
|
|
|
public Sequence OnComplete(Action onComplete, bool warnIfTargetDestroyed = true) {
|
|
root.OnComplete(onComplete, warnIfTargetDestroyed);
|
|
return this;
|
|
}
|
|
|
|
public Sequence OnComplete<T>(T target, Action<T> onComplete, bool warnIfTargetDestroyed = true) where T : class {
|
|
root.OnComplete(target, onComplete, warnIfTargetDestroyed);
|
|
return this;
|
|
}
|
|
|
|
public override int GetHashCode() => root.GetHashCode();
|
|
public bool Equals(Sequence other) => root.Equals(other.root);
|
|
}
|
|
}
|