mirror of
https://github.com/Cardidi/dotween-upm-fork.git
synced 2025-12-21 01:36:05 +08:00
501 lines
16 KiB
C#
501 lines
16 KiB
C#
using UnityEngine;
|
|
using System.Collections;
|
|
using System.Collections.Generic;
|
|
|
|
|
|
/// <summary>
|
|
/// base class for TweenChains and TweenFlows
|
|
/// </summary>
|
|
public class AbstractGoTweenCollection : AbstractGoTween
|
|
{
|
|
protected List<TweenFlowItem> _tweenFlows = new List<TweenFlowItem>();
|
|
|
|
|
|
/// <summary>
|
|
/// data class that wraps an AbstractTween and its start time for the timeline
|
|
/// </summary>
|
|
protected class TweenFlowItem
|
|
{
|
|
public float startTime;
|
|
public float endTime { get { return startTime + duration; } }
|
|
public float duration;
|
|
public AbstractGoTween tween;
|
|
|
|
|
|
public TweenFlowItem( float startTime, AbstractGoTween tween )
|
|
{
|
|
this.tween = tween;
|
|
this.startTime = startTime;
|
|
this.duration = tween.totalDuration;
|
|
}
|
|
|
|
|
|
public TweenFlowItem( float startTime, float duration )
|
|
{
|
|
this.duration = duration;
|
|
this.startTime = startTime;
|
|
}
|
|
}
|
|
|
|
|
|
public AbstractGoTweenCollection( GoTweenCollectionConfig config )
|
|
{
|
|
// allow events by default
|
|
allowEvents = true;
|
|
|
|
// setup callback bools
|
|
_didInit = false;
|
|
_didBegin = false;
|
|
|
|
// flag the onIterationStart event to fire.
|
|
// as long as goTo is not called on this tween, the onIterationStart event will fire
|
|
_fireIterationStart = true;
|
|
|
|
// copy the TweenConfig info over
|
|
id = config.id;
|
|
loopType = config.loopType;
|
|
iterations = config.iterations;
|
|
updateType = config.propertyUpdateType;
|
|
timeScale = 1;
|
|
state = GoTweenState.Paused;
|
|
|
|
_onInit = config.onInitHandler;
|
|
_onBegin = config.onBeginHandler;
|
|
_onIterationStart = config.onIterationStartHandler;
|
|
_onUpdate = config.onUpdateHandler;
|
|
_onIterationEnd = config.onIterationEndHandler;
|
|
_onComplete = config.onCompleteHandler;
|
|
|
|
Go.addTween( this );
|
|
}
|
|
|
|
|
|
#region AbstractTween overrides
|
|
|
|
/// <summary>
|
|
/// returns a list of all Tweens with the given target in the collection
|
|
/// technically, this should be marked as internal
|
|
/// </summary>
|
|
public List<GoTween> tweensWithTarget( object target )
|
|
{
|
|
List<GoTween> list = new List<GoTween>();
|
|
|
|
foreach( var flowItem in _tweenFlows )
|
|
{
|
|
// skip TweenFlowItems with no target
|
|
if( flowItem.tween == null )
|
|
continue;
|
|
|
|
// check Tweens first
|
|
var tween = flowItem.tween as GoTween;
|
|
if( tween != null && tween.target == target )
|
|
list.Add( tween );
|
|
|
|
// check for TweenCollections
|
|
if( tween == null )
|
|
{
|
|
var tweenCollection = flowItem.tween as AbstractGoTweenCollection;
|
|
if( tweenCollection != null )
|
|
{
|
|
var tweensInCollection = tweenCollection.tweensWithTarget( target );
|
|
if( tweensInCollection.Count > 0 )
|
|
list.AddRange( tweensInCollection );
|
|
}
|
|
}
|
|
}
|
|
|
|
return list;
|
|
}
|
|
|
|
|
|
public override bool removeTweenProperty( AbstractTweenProperty property )
|
|
{
|
|
foreach( var flowItem in _tweenFlows )
|
|
{
|
|
// skip delay items which have no tween
|
|
if( flowItem.tween == null )
|
|
continue;
|
|
|
|
if( flowItem.tween.removeTweenProperty( property ) )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
public override bool containsTweenProperty( AbstractTweenProperty property )
|
|
{
|
|
foreach( var flowItem in _tweenFlows )
|
|
{
|
|
// skip delay items which have no tween
|
|
if( flowItem.tween == null )
|
|
continue;
|
|
|
|
if( flowItem.tween.containsTweenProperty( property ) )
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
public override List<AbstractTweenProperty> allTweenProperties()
|
|
{
|
|
var propList = new List<AbstractTweenProperty>();
|
|
|
|
foreach( var flowItem in _tweenFlows )
|
|
{
|
|
// skip delay items which have no tween
|
|
if( flowItem.tween == null )
|
|
continue;
|
|
|
|
propList.AddRange( flowItem.tween.allTweenProperties() );
|
|
}
|
|
|
|
return propList;
|
|
}
|
|
|
|
|
|
/// <summary>
|
|
/// we are always considered valid because our constructor adds us to Go and we start paused
|
|
/// </summary>
|
|
public override bool isValid()
|
|
{
|
|
return true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// resumes playback
|
|
/// </summary>
|
|
public override void play()
|
|
{
|
|
base.play();
|
|
|
|
foreach( var flowItem in _tweenFlows )
|
|
{
|
|
if( flowItem.tween != null )
|
|
flowItem.tween.play();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// pauses playback
|
|
/// </summary>
|
|
public override void pause()
|
|
{
|
|
base.pause();
|
|
|
|
foreach( var flowItem in _tweenFlows )
|
|
{
|
|
if( flowItem.tween != null )
|
|
flowItem.tween.pause();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// tick method. if it returns true it indicates the tween is complete
|
|
/// </summary>
|
|
public override bool update( float deltaTime )
|
|
{
|
|
if ( !_didInit )
|
|
onInit();
|
|
|
|
if ( !_didBegin )
|
|
onBegin();
|
|
|
|
if ( _fireIterationStart )
|
|
onIterationStart();
|
|
|
|
// update the timeline and state.
|
|
base.update( deltaTime );
|
|
|
|
// get the proper elapsedTime if we're doing a PingPong
|
|
var convertedElapsedTime = _isLoopingBackOnPingPong ? duration - _elapsedTime : _elapsedTime;
|
|
|
|
// used for iterating over flowItems below.
|
|
TweenFlowItem flowItem = null;
|
|
|
|
// if we iterated last frame and this flow restarts from the beginning, we now need to reset all
|
|
// of the flowItem tweens to either the beginning or the end of their respective timelines
|
|
// we also want to do this in the _opposite_ way that we would normally iterate on them
|
|
// as the start value of a later flowItem may alter a property of an earlier flowItem.
|
|
if ( _didIterateLastFrame && loopType == GoLoopType.RestartFromBeginning )
|
|
{
|
|
if ( isReversed || _isLoopingBackOnPingPong )
|
|
{
|
|
for ( int i = 0; i < _tweenFlows.Count; ++i )
|
|
{
|
|
flowItem = _tweenFlows[i];
|
|
|
|
if ( flowItem.tween == null )
|
|
continue;
|
|
|
|
var cacheAllow = flowItem.tween.allowEvents;
|
|
flowItem.tween.allowEvents = false;
|
|
flowItem.tween.restart();
|
|
flowItem.tween.allowEvents = cacheAllow;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for ( int i = _tweenFlows.Count - 1; i >= 0; --i )
|
|
{
|
|
flowItem = _tweenFlows[i];
|
|
|
|
if ( flowItem.tween == null )
|
|
continue;
|
|
|
|
var cacheAllow = flowItem.tween.allowEvents;
|
|
flowItem.tween.allowEvents = false;
|
|
flowItem.tween.restart();
|
|
flowItem.tween.allowEvents = cacheAllow;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if ( ( isReversed && !_isLoopingBackOnPingPong ) || ( !isReversed && _isLoopingBackOnPingPong ) )
|
|
{
|
|
// if we are moving the tween in reverse, we should be iterating over the flowItems in reverse
|
|
// to help properties behave a bit better.
|
|
for ( var i = _tweenFlows.Count - 1; i >= 0; --i )
|
|
{
|
|
flowItem = _tweenFlows[i];
|
|
|
|
if ( flowItem.tween == null )
|
|
continue;
|
|
|
|
// if there's been an iteration this frame and we're not done yet, we want to make sure
|
|
// this tween is set to play in the right direction, and isn't set to complete/paused.
|
|
if ( _didIterateLastFrame && state != GoTweenState.Complete )
|
|
{
|
|
if ( !flowItem.tween.isReversed )
|
|
flowItem.tween.reverse();
|
|
|
|
flowItem.tween.play();
|
|
}
|
|
|
|
if ( flowItem.tween.state == GoTweenState.Running && flowItem.endTime >= convertedElapsedTime )
|
|
{
|
|
var convertedDeltaTime = Mathf.Abs( convertedElapsedTime - flowItem.startTime - flowItem.tween.totalElapsedTime );
|
|
flowItem.tween.update( convertedDeltaTime );
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
for ( int i = 0; i < _tweenFlows.Count; ++i )
|
|
{
|
|
flowItem = _tweenFlows[i];
|
|
|
|
if ( flowItem.tween == null )
|
|
continue;
|
|
|
|
// if there's been an iteration this frame and we're not done yet, we want to make sure
|
|
// this tween is set to play in the right direction, and isn't set to complete/paused.
|
|
if ( _didIterateLastFrame && state != GoTweenState.Complete )
|
|
{
|
|
if ( flowItem.tween.isReversed )
|
|
flowItem.tween.reverse();
|
|
|
|
flowItem.tween.play();
|
|
}
|
|
|
|
if ( flowItem.tween.state == GoTweenState.Running && flowItem.startTime <= convertedElapsedTime )
|
|
{
|
|
var convertedDeltaTime = convertedElapsedTime - flowItem.startTime - flowItem.tween.totalElapsedTime;
|
|
flowItem.tween.update( convertedDeltaTime );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
onUpdate();
|
|
|
|
if ( _fireIterationEnd )
|
|
onIterationEnd();
|
|
|
|
if ( state == GoTweenState.Complete )
|
|
{
|
|
onComplete();
|
|
|
|
return true; // true if complete
|
|
}
|
|
|
|
return false; // false if not complete
|
|
}
|
|
|
|
/// <summary>
|
|
/// reverses playback. if going forward it will be going backward after this and vice versa.
|
|
/// </summary>
|
|
public override void reverse()
|
|
{
|
|
base.reverse();
|
|
|
|
var convertedElapsedTime = _isLoopingBackOnPingPong ? duration - _elapsedTime : _elapsedTime;
|
|
|
|
foreach ( var flowItem in _tweenFlows )
|
|
{
|
|
if ( flowItem.tween == null )
|
|
continue;
|
|
|
|
if ( isReversed != flowItem.tween.isReversed )
|
|
flowItem.tween.reverse();
|
|
|
|
flowItem.tween.pause();
|
|
|
|
// we selectively mark tweens for play if they will be played immediately or in the future.
|
|
// update() will filter out more tweens that should not be played yet.
|
|
if ( isReversed || _isLoopingBackOnPingPong )
|
|
{
|
|
if ( flowItem.startTime <= convertedElapsedTime )
|
|
flowItem.tween.play();
|
|
}
|
|
else
|
|
{
|
|
if ( flowItem.endTime >= convertedElapsedTime )
|
|
flowItem.tween.play();
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// goes to the specified time clamping it from 0 to the total duration of the tween. if the tween is
|
|
/// not playing it will be force updated to the time specified.
|
|
/// </summary>
|
|
public override void goTo( float time, bool skipDelay = true )
|
|
{
|
|
time = Mathf.Clamp( time, 0f, totalDuration );
|
|
|
|
// provide an early out for calling goTo on the same time multiple times.
|
|
if ( time == _totalElapsedTime )
|
|
return;
|
|
|
|
// we don't simply call base.goTo because that would force an update within AbstractGoTweenCollection,
|
|
// which forces an update on all the tweenFlowItems without putting them in the right position.
|
|
// it's also possible that people will move around a tween via the goTo method, so we want to
|
|
// try to make that as efficient as possible.
|
|
|
|
// if we are doing a goTo at the "start" of the timeline, based on the isReversed variable,
|
|
// allow the onBegin and onIterationStart callback to fire again.
|
|
// we only allow the onIterationStart event callback to fire at the start of the timeline,
|
|
// as doing a goTo(x) where x % duration == 0 will trigger the onIterationEnd before we
|
|
// go to the start.
|
|
if ( ( isReversed && time == totalDuration ) || ( !isReversed && time == 0f ) )
|
|
{
|
|
_didBegin = false;
|
|
_fireIterationStart = true;
|
|
}
|
|
else
|
|
{
|
|
_didBegin = true;
|
|
_fireIterationStart = false;
|
|
}
|
|
|
|
// since we're doing a goTo, we want to stop this tween from remembering that it iterated.
|
|
// this could cause issues if you caused the tween to complete an iteration and then goTo somewhere
|
|
// else while still paused.
|
|
_didIterateThisFrame = false;
|
|
|
|
// force a time and completedIterations before we update
|
|
_totalElapsedTime = time;
|
|
_completedIterations = isReversed ? Mathf.CeilToInt( _totalElapsedTime / duration ) : Mathf.FloorToInt( _totalElapsedTime / duration );
|
|
|
|
// we don't want to use the Collection update function, because we don't have all of our
|
|
// child tweenFlowItems setup properly. this will properly setup our iterations,
|
|
// totalElapsedTime, and other useful information.
|
|
base.update( 0 );
|
|
|
|
var convertedElapsedTime = _isLoopingBackOnPingPong ? duration - _elapsedTime : _elapsedTime;
|
|
|
|
// we always want to process items in the future of this tween from last to first.
|
|
// and items that have already occured from first to last.
|
|
TweenFlowItem flowItem = null;
|
|
if ( isReversed || _isLoopingBackOnPingPong )
|
|
{
|
|
// flowItems in the future of the timeline
|
|
for ( int i = 0; i < _tweenFlows.Count; ++i )
|
|
{
|
|
flowItem = _tweenFlows[i];
|
|
|
|
if ( flowItem == null )
|
|
continue;
|
|
|
|
if ( flowItem.endTime >= convertedElapsedTime )
|
|
break;
|
|
|
|
changeTimeForFlowItem( flowItem, convertedElapsedTime );
|
|
}
|
|
|
|
// flowItems in the past & current part of the timeline
|
|
for ( int i = _tweenFlows.Count - 1; i >= 0; --i )
|
|
{
|
|
flowItem = _tweenFlows[i];
|
|
|
|
if ( flowItem == null )
|
|
continue;
|
|
|
|
if ( flowItem.endTime < convertedElapsedTime )
|
|
break;
|
|
|
|
changeTimeForFlowItem( flowItem, convertedElapsedTime );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// flowItems in the future of the timeline
|
|
for ( int i = _tweenFlows.Count - 1; i >= 0; --i )
|
|
{
|
|
flowItem = _tweenFlows[i];
|
|
|
|
if ( flowItem == null )
|
|
continue;
|
|
|
|
if ( flowItem.startTime <= convertedElapsedTime )
|
|
break;
|
|
|
|
changeTimeForFlowItem( flowItem, convertedElapsedTime );
|
|
}
|
|
|
|
// flowItems in the past & current part of the timeline
|
|
for ( int i = 0; i < _tweenFlows.Count; ++i )
|
|
{
|
|
flowItem = _tweenFlows[i];
|
|
|
|
if ( flowItem == null )
|
|
continue;
|
|
|
|
if ( flowItem.startTime > convertedElapsedTime )
|
|
break;
|
|
|
|
changeTimeForFlowItem( flowItem, convertedElapsedTime );
|
|
}
|
|
}
|
|
}
|
|
|
|
private void changeTimeForFlowItem( TweenFlowItem flowItem, float time )
|
|
{
|
|
if ( flowItem == null || flowItem.tween == null )
|
|
return;
|
|
|
|
if ( flowItem.tween.isReversed != ( isReversed || _isLoopingBackOnPingPong ) )
|
|
flowItem.tween.reverse();
|
|
|
|
var convertedTime = Mathf.Clamp( time - flowItem.startTime, 0f, flowItem.endTime );
|
|
|
|
if ( flowItem.startTime <= time && flowItem.endTime >= time )
|
|
{
|
|
flowItem.tween.goToAndPlay( convertedTime );
|
|
}
|
|
else
|
|
{
|
|
flowItem.tween.goTo( convertedTime );
|
|
flowItem.tween.pause();
|
|
}
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|