-
Notifications
You must be signed in to change notification settings - Fork 0
/
MabuTween.cs
1259 lines (1137 loc) · 44.8 KB
/
MabuTween.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
Copyright(c) 2020 Matthias Bühlmann, Mabulous GmbH. http://www.mabulous.com
All rights reserved.
*/
/* general usage is: Mabu.Tween(what, duration, to, [from], [easingFunction], [loopType], [getterFunction]);
*
* @what defines the value that should be animated. It can can either be a tuple (object, propertyname) or a
* setter function of type SetterFunction<T> that takes one argument of type T (in the range of @from to @to)
* and does something with it.
* @duration is the duration of the tween in seconds.
* @to is the target value to which the value should be animated.
* @from defines the value at which the animation should start. It can take one of three types:
* - a value of type T, in this case that value will be set as the start
* - null, in which case the start value is taken from the value the property will have when the tween is
* started. This only works when @what is an (object, propertyname) tuple, not when it is a
* setter function of type SetterFunction<T>
* - a getter function of type GetterFunction<T>. It will be evaluated when the tween starts to get the start
* value.
* @easingFunction one of many different easing functions defining motion curve. See https://easings.net/
* can be null, in which case sinusoidal ease-in-ease-out is used.
* @loopType defines what happens when the tween reaches the end. Possible Values are
* - LoopType.Dont (the default): doesn't loop, the tween ends when it reaches the to value.
* - LoopType.Loop: repeats the tween forwever, starting at the beginning with each loop
* - LoopType.Reflect: After reaching the end, animates in reverse back to the start and ends then.
* - LoopType.PingPong: Animates continuously forwards, then backwards etc.
* @getterFunction this is usually not required. it is only required when the @what of a subtween is defined using a
* setter function rather than a (object, propertyname) tuple AND the @from is NOT already of type
* GetterFunction<T> but a fixed value of type T AND the subtween is concatenated with other
* subtweens and might animated backwards (this is so the tween can query the value of the animated
* value before the tween starts, so that it can set it back there when animating in reverse).
*
*
* SetterFunction<T> is any function (or lambda) that accepts one argument of type T and retruns void, so you can
* pass any such function as the @what argument to animate the variable.
*
* GetterFunction<T> is any function (or lambda) that has no input arguments and returns a value of type T, so you
* any such function as the @from argument and/or the @getterFunction argument.
*
* Example usage: Let's say you have some function to set the visibility of the menu and you want to Tween it
*
* // Sets the alpha transparency of the Menu to @alpha
* public void SetUIVisiblity(float alpha) { ... }
*
* // Get the current alpha transparency of the menu
* public float GetUIVisibility();
*
* To do so, you need to define a setter function. Since SetUIVisibility already conforms to the setterFunction
* signature, and GetUIVisibility to the getterFunction signature, you can use them directly as delegate
*
* Mabu.Tween(SetUIVisiblity, 0.5f, 0.0f, GetUIVisibility));
*
* This will fade out the menu within 0.5 seconds using a Sinusoidal ease-in-ease-out animation.
*
* Alternatively you could also specify the @from value instead of providing the @getterFuncton:
*
* Mabu.Tween(SetUIVisiblity, 0.5f, 0.0f, 1.0f));
*
* In this case the Menu will first be set to complete opacity (1.0f) if it isn't already, and then
* faded out (0.0f)
*
*
* Another example: Let's say you want to animate an increase in distance between two objects.
* To do so, define a setterFunction (this can be an anonymous lambda function, like in this example):
*
* SetterFunction<float> distanceSetter = (float d) => {
* Vector3 center = (go1.transform.position + go2.transform.position) * 0.5f;
* go1.transform.position = center + (go1.transform.position - center).normalized * d * 0.5f;
* go2.transform.position = center + (go2.transform.position - center).normalized * d * 0.5f;
* }
* float currentDistance = (go1.transform.position - go2.transform.position).magnitude;
* Mabu.Tween(distanceSetter, 2.0f, 10.0f, currentDistance, Easing.Bounce.Out));
*
* This will create a 10 seconds animation that will Tween the two GameObjects move to a distance of 10.0 units from
* each other.
*
*
* On the Type T:
* By default, values of types float, double, Vector2, Vector3, Vector4, Quaternion and Color can be animated, as
* well as any other Value Type that has a static method T Lerp(T a, T b, float t) defined.
*
* However, to animate any Value Type variable can be added by simply defining a suitable Lerp function and setting
* it once using the static SetLerpMethod() function.
*
* For example, to Tween variables of type Rect tweenable, one could subscribe a Lerp function for it in the following
* way:
*
* LerpFunction<Rect> lerpRect = (Rect a, Rect b, float t) => {
* return Rect.MinMaxRect(Mathf.Lerp(a.xMin, b.xMin, t),
* Mathf.Lerp(a.yMin, b.yMin, t),
* Mathf.Lerp(a.xMax, b.xMax, t),
* Mathf.Lerp(a.yMax, b.yMax, t),
* };
* Mabu.SetLerpMethod(typeof(Rect), lerpRect);
*
* After this, variables of type Rect can be animated using Tween(...); and TweenProperty(...);
*
* If you want that Quaternions are interpolated using Slerp instead of Lerp (the default), you can also override
* the interpolation behaviour for them using this method.
*
*
* On Tween Chaining:
* You can chain tweens together using the + or +"= operator. This will return a concatenated tween that first tweens the
* left tween and then the right tween.
* Additionally to Tweens, you can concatenate in this way also YieldInstructions, Coroutines (they should NOT be started
* using StartCoroutine in this case, just concatenate the IEnumerator returned from the coroutine function directly) as
* well as functions and lambdas that take no argument. This allows you to easily define a sequence of animations with
* actions that should happen ac specific points of the overal animation. For example:
*
* // this will first fade in the object 'menu' (provided that it has a property with getter and setter named "opacity"),
* then will toggle the application into fullscreen mode and then fade it out again.
* Mabu.Tween((menu, "opacity"), 1.0f, 1.0f) + () => { Screen.fullScreen = true; } + Mabu.Tween((menu, "opacity"), 1.0f, 0.0f);
*
*/
using System;
using System.Reflection;
using System.Linq;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public static class Mabu
{
public delegate void SetterFunction<T>(T x) where T : struct;
public delegate T GetterFunction<T>() where T : struct;
public delegate T LerpFunction<T>(T a, T b, float t) where T : struct;
public delegate float EasingFunction(float k);
public enum LoopType
{
Dont, // End the tween after playing once.
Loop, // Loops continuously.
Reflect, // Plays one time forward, then one time Reverse, then is over.
PingPong // Plays continuously forward then Reverse.
}
public enum TweenPlayDirection
{
Forward = 1,
Reverse = -1
}
/// <summary>
/// Creates and starts a new Tween.
/// </summary>
/// <param name="setterFunction">
/// This function is executed on each step of the tween and passed the tweened value.
/// </param>
/// <param name="timeInSeconds">
/// The duration of the tween.
/// </param>
/// <param name="to">
/// The value towards which the tweened value is animated.
/// </param>
/// <param name="from">
/// An object that defines the start value of the tween. This can either be a raw value of type T or a function
/// that takes no arguments and returns a value of type T. in the latter case, the function will be called when the
/// tween begins to get the start value.
/// </param>
/// <param name="easingFunction">
/// A function that defines the interpolation of the <param ref="from"/> and <param ref="to"/> values. Use one of the
/// functions in Mabu.Easing or provide your own. See https://easings.net/ for the predefined interpolations. If this
/// parameter is not defined or null is passed, the Sinusodial ease-in-ease-out is used for the tween.
/// </param>
/// <param name="loopType">
/// How the tween is looped.
/// LoopType.Dont (the default), does not loop the tween.
/// LoopType.Loop loops the tween continuously until it is stopped.
/// LoopType.Reflect plays the tween one time regularly and then one time in reverse.
/// LoopType.PingPong play the tween continuously forwards and backwards.
/// </param>
/// <param name="getterFunction">
/// If <param ref="from"/> isn't already a getter function, then a getter function can be specified here to allow the
/// tween to reset the tweened value after playing in reverse to whatever it was before the tween started.
/// </param>
/// <returns>
/// A <see cref="TweenHandle"/> through which the tween can be stopped prematurely or concatenated with other tweens.
/// </returns>
public static TweenHandle Tween<T>(SetterFunction<T> setterFunction, float timeInSeconds, T to, object from,
EasingFunction easingFunction = null,
LoopType loopType = LoopType.Dont,
GetterFunction<T> getterFunction = null) where T : struct
{
return new EnumeratorTween(
new RawLoopableTween<T>(setterFunction, timeInSeconds, to, from, easingFunction, loopType, getterFunction));
}
/// <summary>
/// Creates and starts a new Tween.
/// </summary>
/// <param name="targetProperty">
/// This is a tuple that defines the value that shall be animated. The first member is the object owning the value
/// and the second member is the name of the public property on this object that shall be animated.
/// </param>
/// <param name="timeInSeconds">
/// The duration of the tween.
/// </param>
/// <param name="to">
/// The value towards which the tweened value is animated.
/// </param>
/// <param name="from">
/// An object that defines the start value of the tween. This can be null, in which case the current value of the
/// property is taken as start value or the tween.
/// Otherwise it can either be a raw value of type T or a function that takes no arguments and returns a value of
/// type T. in the latter case, the function will be called when the tween begins to get the start value.
/// </param>
/// <param name="easingFunction">
/// A function that defines the interpolation of the <param ref="from"/> and <param ref="to"/> values. Use one of the
/// functions in Mabu.Easing or provide your own. See https://easings.net/ for the predefined interpolations. If this
/// parameter is not defined or null is passed, the Sinusodial ease-in-ease-out is used for the tween.
/// </param>
/// <param name="loopType">
/// How the tween is looped.
/// LoopType.Dont (the default), does not loop the tween.
/// LoopType.Loop loops the tween continuously until it is stopped.
/// LoopType.Reflect plays the tween one time regularly and then one time in reverse.
/// LoopType.PingPong play the tween continuously forwards and backwards.
/// </param>
/// <returns>
/// A <see cref="TweenHandle"/> through which the tween can be stopped prematurely or concatenated with other tweens.
/// </returns>
public static TweenHandle Tween<U,T>(ValueTuple<U, string> targetProperty, float timeInSeconds, T to,
object from = null, EasingFunction easingFunction = null,
LoopType loopType = LoopType.Dont) where U : class where T : struct
{
return new EnumeratorTween(
new RawLoopableTween<T>(targetProperty, timeInSeconds, to, from, easingFunction, loopType));
}
/// <summary>
/// Creates and starts a new Tween.
/// </summary>
/// <param name="targetProperty">
/// This is an object array with two elements that defines the value that shall be animated. The first element is the
/// object owning the value and the second member is the name of the public property on this object that shall be
/// animated.
/// </param>
/// <param name="timeInSeconds">
/// The duration of the tween.
/// </param>
/// <param name="to">
/// The value towards which the tweened value is animated.
/// </param>
/// <param name="from">
/// An object that defines the start value of the tween. This can be null, in which case the current value of the
/// property is taken as start value or the tween.
/// Otherwise it can either be a raw value of type T or a function that takes no arguments and returns a value of
/// type T. in the latter case, the function will be called when the tween begins to get the start value.
/// </param>
/// <param name="easingFunction">
/// A function that defines the interpolation of the <param ref="from"/> and <param ref="to"/> values. Use one of the
/// functions in Mabu.Easing or provide your own. See https://easings.net/ for the predefined interpolations. If this
/// parameter is not defined or null is passed, the Sinusodial ease-in-ease-out is used for the tween.
/// </param>
/// <param name="loopType">
/// How the tween is looped.
/// LoopType.Dont (the default), does not loop the tween.
/// LoopType.Loop loops the tween continuously until it is stopped.
/// LoopType.Reflect plays the tween one time regularly and then one time in reverse.
/// LoopType.PingPong play the tween continuously forwards and backwards.
/// </param>
/// <returns>
/// A <see cref="TweenHandle"/> through which the tween can be stopped prematurely or concatenated with other tweens.
/// </returns>
public static TweenHandle Tween<T>(object[] targetProperty, float timeInSeconds, T to, object from = null,
EasingFunction easingFunction = null,
LoopType loopType = LoopType.Dont) where T : struct
{
return new EnumeratorTween(
new RawLoopableTween<T>(targetProperty, timeInSeconds, to, from, easingFunction, loopType));
}
/// <summary>
/// A handle representing a tween.
/// </summary>
public abstract class TweenHandle : LoopableEnumerator
{
/// <summary>
/// Stops the original two tweens and creates and starts a concatenated tween.
/// </summary>
public static TweenHandle operator +(TweenHandle first, TweenHandle second)
=> first.Then(second);
/// <summary>
/// Concatenates a Tween and a YieldInstruction.
/// </summary>
public static TweenHandle operator +(TweenHandle first, YieldInstruction second)
=> first.Then(new EnumeratorTween(new PseudoReversableEnumerator(new YieldInstructionEnumerator(second))));
/// <summary>
/// Concatenates a YieldInstruction and a Tween.
/// </summary>
public static TweenHandle operator +(YieldInstruction first, TweenHandle second)
=> new EnumeratorTween(new PseudoReversableEnumerator(new YieldInstructionEnumerator(first))).Then(second);
/// <summary>
/// Concatenates a Tween and a IEnumerator.
/// </summary>
public static TweenHandle operator +(TweenHandle first, IEnumerator second) {
TweenHandle secondTween = (second is TweenHandle)
? second as TweenHandle
: new EnumeratorTween(new PseudoReversableEnumerator(second));
return first.Then(secondTween);
}
/// <summary>
/// Concatenates a IEnumerator and a Tween.
/// </summary>
public static TweenHandle operator +(IEnumerator first, TweenHandle second) {
TweenHandle firstTween = (first is TweenHandle)
? first as TweenHandle
: new EnumeratorTween(new PseudoReversableEnumerator(first));
return firstTween.Then(second);
}
/// <summary>
/// Concatenates a Tween with an Action, executing the Action when the tween ended.
/// </summary>
public static TweenHandle operator +(TweenHandle first, Action second)
=> first.Then(new OneShotTween(second));
/// <summary>
/// Concatenates an Action and a tween, executing the Action beffore the tween starts.
/// </summary>
public static TweenHandle operator +(Action first, TweenHandle second)
=> new OneShotTween(first).Then(second);
/// <summary>
/// Restarts the tween.
/// </summary>
public void Restart()
{
this.Reset();
TweenManager.StartTween(this);
}
/// <summary>
/// Stops the tween prematurely. This method does not have to be called explicitely, since a tween is
/// automatically stopped and discarded when it ends, but can be used to prevent a tween rom playing or to
/// stop it while it plays.
/// </summary>
public void Stop()
{
TweenManager.StopTween(this);
}
/// <summary>
/// Returns true while this Tween is playing, and false after it ended or has otherwise been stoped.
/// </summary>
public bool IsPlaying() {
return TweenManager.IsPlaying(this);
}
protected TweenHandle Then(TweenHandle next)
{
return new ChainedTween(this, next);
}
}
#region CustomLerpMethods
// Define a Lerp method in order to animate other types of variables than the ones supported by default.
public static void SetLerpMethod<T>(LerpFunction<T> lerpMethod) where T : struct
{
Type type = typeof(T);
if (lerpMethod == null)
{
Debug.LogError("LerpMethod is null");
}
lerpMethodDic[type] = lerpMethod;
}
#endregion
#region IMPL
static Mabu()
{
// Register lerpMethods for float and double.
SetLerpMethod((float a, float b, float t) => { return Mathf.Lerp(a, b, t); });
SetLerpMethod((double a, double b, float t) => { return a + (b - a) * Mathf.Clamp01(t); });
}
#region TweenManager
private static Dictionary<Type, Delegate> lerpMethodDic = new Dictionary<Type, Delegate>();
private static class TweenManager
{
private class CoroutineStarter : MonoBehaviour { }
private static GameObject tweenManagerGO;
private static Dictionary<TweenHandle, Coroutine> coroutines = new Dictionary<TweenHandle, Coroutine>();
private static CoroutineStarter coroutineStarter;
static TweenManager()
{
tweenManagerGO = new GameObject("TweenManagerGO");
tweenManagerGO.hideFlags = HideFlags.HideAndDontSave | HideFlags.HideInInspector;
coroutineStarter = tweenManagerGO.AddComponent<CoroutineStarter>();
}
public static void StartTween(TweenHandle tween)
{
if(tween != null) {
if(coroutines.ContainsKey(tween))
StopTween(tween);
coroutines[tween] = coroutineStarter.StartCoroutine(CoroutineWrapper(tween));
}
}
public static void StopTween(TweenHandle tween)
{
if(tween != null) {
Coroutine coroutine;
if(coroutines.TryGetValue(tween, out coroutine)) {
coroutineStarter.StopCoroutine(coroutine);
coroutines.Remove(tween);
}
}
}
public static bool IsPlaying(TweenHandle tween) {
return coroutines.ContainsKey(tween);
}
private static IEnumerator CoroutineWrapper(TweenHandle tween)
{
// StartCoroutine already executes one iteration, therefore
// one initial yield return 0 is inserted so that the actual
// tween does not start yet, so that it still can be removed
// from the manager during this frame without affect the
// object, like setting the 'from' value.
yield return 0;
while(true)
{
bool moved = tween.MoveNext();
if(moved)
{
yield return tween.Current;
}
else
{
StopTween(tween);
yield break;
}
}
}
}
#endregion
#region ReversableEnumerator
public interface IReversableEnumerator : IEnumerator
{
bool MovePrevious();
void Reset(TweenPlayDirection direction);
bool Move(TweenPlayDirection direction);
}
public abstract class ReversableEnumerator : IReversableEnumerator
{
public virtual bool MoveNext() { return Move(TweenPlayDirection.Forward); }
public virtual bool MovePrevious() { return Move(TweenPlayDirection.Reverse); }
public virtual object Current { get { return Inner.Current; } }
public virtual void Reset() { Reset(TweenPlayDirection.Forward); }
// After reset in direction, a Move in the same direction will not return false, while one
// in the opposite direction will.
public abstract void Reset(TweenPlayDirection direction);
public abstract bool Move(TweenPlayDirection direction);
protected IEnumerator Inner { get { return GetInner(); } }
protected abstract IEnumerator GetInner();
}
#endregion
#region LoopableEnumerator
public interface ILoopableEnumerator : IReversableEnumerator
{
LoopType LoopType { get; set; }
}
public abstract class LoopableEnumerator : ReversableEnumerator, ILoopableEnumerator
{
public LoopType LoopType { get; set; } = LoopType.Dont;
private bool loopingBack = false;
public override void Reset(TweenPlayDirection direction)
{
TweenPlayDirection actualDir = direction;
if(LoopType == LoopType.PingPong || LoopType == LoopType.Reflect)
{
loopingBack = actualDir == TweenPlayDirection.Reverse;
actualDir = TweenPlayDirection.Forward;
} else
{
loopingBack = false;
}
Inner.Reset(actualDir);
}
public override bool Move(TweenPlayDirection dir)
{
TweenPlayDirection actualDirection = (TweenPlayDirection)((int)dir * (loopingBack ? -1 : 1));
bool moved = Inner.Move(actualDirection);
if(!moved)
{
switch(LoopType)
{
case LoopType.Loop:
Inner.Reset(actualDirection);
moved = Inner.Move(actualDirection);
break;
case LoopType.Reflect:
if(loopingBack == (dir == TweenPlayDirection.Reverse))
{
goto case LoopType.PingPong;
}
break;
case LoopType.PingPong:
loopingBack = !loopingBack;
actualDirection = (TweenPlayDirection)((int)actualDirection * -1);
// reset here too, so that YieldInstruction Subtweens also run again
Inner.Reset(actualDirection);
moved = Inner.Move(actualDirection);
break;
default:
// ending.
break;
}
}
return moved;
}
protected new IReversableEnumerator Inner { get { return (IReversableEnumerator)GetInner(); } }
}
#endregion
#region Tween
private class PseudoReversableEnumerator : ReversableEnumerator
{
// Wraps a regular IEnumerator and iterating always forward
// independent of direction. While this is incorrect, it's
// the best we can do to with such enumerators.
private IEnumerator inner;
public PseudoReversableEnumerator(IEnumerator other)
{
inner = other;
}
public override bool Move(TweenPlayDirection direction) { return inner.MoveNext(); }
public override void Reset(TweenPlayDirection direction)
{
try {
inner.Reset();
} catch (Exception) {
}
}
protected override IEnumerator GetInner() { return inner; }
}
private class YieldInstructionEnumerator : IEnumerator
{
private YieldInstruction yi;
bool moveNextCalled = false;
public YieldInstructionEnumerator(YieldInstruction instruction)
{
yi = instruction;
}
public bool MoveNext()
{
bool res = !moveNextCalled;
moveNextCalled = true;
return res;
}
public object Current { get { return moveNextCalled ? yi : null; } }
public void Reset() { moveNextCalled = false; }
}
private class OneShotTween : TweenHandle
{
private Action action;
private bool loaded = true;
public OneShotTween(Action action) {
this.action = action;
}
public override bool Move(TweenPlayDirection dir)
{
if(loaded)
{
action.Invoke();
}
loaded = false;
return false;
}
public override void Reset(TweenPlayDirection dir)
{
loaded = true;
}
public override object Current { get { return null; } }
protected override IEnumerator GetInner() { return this; }
}
private class EnumeratorTween : TweenHandle
{
private IReversableEnumerator inner;
public EnumeratorTween(IReversableEnumerator en) {
inner = en;
TweenManager.StartTween(this);
}
protected override IEnumerator GetInner() { return inner; }
}
private class CompoundReversableEnumerator : IReversableEnumerator
{
private IReversableEnumerator current;
private IReversableEnumerator first;
private IReversableEnumerator second;
public CompoundReversableEnumerator(IReversableEnumerator first, IReversableEnumerator second)
{
this.first = first;
current = first;
this.second = second;
}
public virtual bool MoveNext() { return Move(TweenPlayDirection.Forward); }
public virtual bool MovePrevious() { return Move(TweenPlayDirection.Reverse); }
public virtual void Reset() { Reset(TweenPlayDirection.Forward); }
public virtual void Reset(TweenPlayDirection dir)
{
first.Reset(dir);
second.Reset(dir);
if(dir == TweenPlayDirection.Forward)
{
current = first;
}
else
{
current = second;
}
}
public virtual object Current { get { return current.Current; } }
public virtual bool Move(TweenPlayDirection dir)
{
bool moved = current.Move(dir);
if(!moved)
{
if((dir == TweenPlayDirection.Reverse && current == first) ||
(dir == TweenPlayDirection.Forward && current == second))
{
return false;
}
else
{
if(current == first)
{
current = second;
}
else
{
current = first;
}
moved = current.Move(dir);
}
}
return moved;
}
}
private class ChainedTween : TweenHandle
{
private CompoundReversableEnumerator inner;
public ChainedTween(TweenHandle first, TweenHandle second)
{
inner = new CompoundReversableEnumerator(first, second);
TweenManager.StopTween(first);
TweenManager.StopTween(second);
TweenManager.StartTween(this);
}
protected override IEnumerator GetInner() { return inner; }
}
#endregion
#region RawTween
// Helper class to return an empty IEnumerator
private class EmptyEnumerator : IEnumerator
{
public bool MoveNext() { return false; }
public object Current { get { return null; } }
public void Reset() { }
}
public class RawLoopableTween<T> : LoopableEnumerator where T : struct
{
private RawTween<T> inner;
public RawLoopableTween(SetterFunction<T> setterFunction, float timeInSeconds, T to, object from,
EasingFunction easingFunction = null, LoopType loopType = LoopType.Dont,
GetterFunction<T> getterFunction = null)
{
LoopType = loopType;
inner = new RawTween<T>(
setterFunction, timeInSeconds, to, from, easingFunction, TweenPlayDirection.Forward, getterFunction);
}
public RawLoopableTween(object[] targetProperty, float timeInSeconds, T to, object from = null,
EasingFunction easingFunction = null, LoopType loopType = LoopType.Dont)
{
LoopType = loopType;
inner = new RawTween<T>(targetProperty, timeInSeconds, to, from, easingFunction, TweenPlayDirection.Forward);
}
public RawLoopableTween(ValueTuple<object, string> targetProperty, float timeInSeconds, T to, object from = null,
EasingFunction easingFunction = null, LoopType loopType = LoopType.Dont)
{
LoopType = loopType;
inner = new RawTween<T>(targetProperty, timeInSeconds, to, from, easingFunction, TweenPlayDirection.Forward);
}
protected override IEnumerator GetInner() { return inner; }
}
public class RawTween<T> : ReversableEnumerator where T : struct
{
private enum RawTweenState
{
Running,
LeftEnd,
RightEnd,
Resetting
}
private IEnumerator inner;
protected override IEnumerator GetInner() { return inner; }
private RawTweenState state = RawTweenState.LeftEnd;
private TweenPlayDirection resetDirection = TweenPlayDirection.Forward;
private TweenPlayDirection currentPlayDirection = TweenPlayDirection.Forward;
public override bool Move(TweenPlayDirection dir)
{
currentPlayDirection = dir;
if((state == RawTweenState.RightEnd && currentPlayDirection == TweenPlayDirection.Forward) ||
(state == RawTweenState.LeftEnd && currentPlayDirection == TweenPlayDirection.Reverse))
{
return false;
}
return inner.MoveNext();
}
public override void Reset(TweenPlayDirection dir)
{
resetDirection = dir;
state = RawTweenState.Resetting;
}
public RawTween(SetterFunction<T> setterFunction, float timeInSeconds, T to, object from,
EasingFunction easingFunction = null, TweenPlayDirection playDirection = TweenPlayDirection.Forward,
GetterFunction<T> getterFunction = null)
{
inner = RawTweenImpl(setterFunction, timeInSeconds, to, from, easingFunction, playDirection);
}
public RawTween(object[] targetProperty, float timeInSeconds, T to, object from = null,
EasingFunction easingFunction = null, TweenPlayDirection playDirection = TweenPlayDirection.Forward)
{
inner = RawTweenImpl(targetProperty, timeInSeconds, to, from, easingFunction, playDirection);
}
public RawTween(ValueTuple<object, string> targetProperty, float timeInSeconds, T to, object from = null,
EasingFunction easingFunction = null, TweenPlayDirection playDirection = TweenPlayDirection.Forward)
{
inner = RawTweenImpl(targetProperty, timeInSeconds, to, from, easingFunction, playDirection);
}
// Implementation
// Create an empty IEnumerator to return from functions that return IEnumerator but don't yield.
private static IEnumerator YieldBreak = new EmptyEnumerator();
private IEnumerator RawTweenImpl(SetterFunction<T> setterFunction, float timeInSeconds, T to, object from,
EasingFunction easingFunction = null,
TweenPlayDirection playDirection = TweenPlayDirection.Forward,
GetterFunction<T> getterFunction = null)
{
// Perform runtime checks of arguments.
if (setterFunction == null)
{
Debug.LogWarning("setterFunction is null. Will not tween.");
yield break;
}
if(from == null || !(from.GetType().Equals(typeof(T)) || from.GetType().Equals(typeof(GetterFunction<T>))))
{
string warningString;
if(from == null)
{
warningString = "from is null.";
}
else
{
warningString = "from is incorrect type (" + from.GetType().Name + ").";
}
Debug.LogWarning(warningString + "from must be either a value of type " + typeof(T).Name +
" or a getter function of type GetterFunction<" + typeof(T).Name + ">. Will not tween.");
yield break;
}
if((getterFunction != null) && !getterFunction.GetType().Equals(typeof(GetterFunction<T>)))
{
Debug.LogWarning("getterFunction was provided, but it is not of type Func<" + typeof(T).Name +
">. Will not tween.");
yield break;
}
// search in lerpMethodDic for a matching Lerp function.
Delegate del;
lerpMethodDic.TryGetValue(typeof(T), out del);
LerpFunction<T> lerpMethod = del as LerpFunction<T>;
if (lerpMethod == null)
{
// Check if type has a static Lerp function and create a Delegate for it if it has.
MethodInfo lerpMethodInfo = typeof(T).GetMethod("Lerp", BindingFlags.Static | BindingFlags.Public);
if (lerpMethodInfo != null)
{
lerpMethod = Delegate.CreateDelegate(typeof(LerpFunction<T>), lerpMethodInfo) as LerpFunction<T>;
Debug.Assert(lerpMethod != null);
lerpMethodDic[typeof(T)] = lerpMethod;
}
else
{
Debug.LogWarning("Type " + typeof(T).FullName +
" has no static Lerp() function defined. Set a Lerp function of type System.Func<" +
typeof(T).Name + "," + typeof(T).Name + ",float," + typeof(T).Name +
"> using SetLerpFunction().");
yield break;
}
}
// Use sinusoidal easing if no easing function has been provided
if (easingFunction == null)
easingFunction = Easing.Sinusoidal.InOut;
yield return 0; // after this yeld, the first iteration follows
// ResetRelative:
T fromVal;
T originalVal;
if(from.GetType().Equals(typeof(T)))
{
fromVal = (T)from;
}
else if (from.GetType().Equals(typeof(GetterFunction<T>)))
{
fromVal = (from as GetterFunction<T>).Invoke();
}
else
{
fromVal = default(T);
originalVal = default(T);
Debug.Assert(false, "from is of invalid type");
}
if(getterFunction != null)
{
originalVal = getterFunction.Invoke();
} else
{
originalVal = fromVal;
}
Reset:
state = RawTweenState.LeftEnd;
float tweenTime = 0;
float t = 0;
if(resetDirection == TweenPlayDirection.Reverse) {
state = RawTweenState.RightEnd;
tweenTime = timeInSeconds;
t = 1.0f;
}
while (true)
{
tweenTime += (currentPlayDirection == TweenPlayDirection.Forward) ? Time.deltaTime : -Time.deltaTime;
tweenTime = Mathf.Clamp(tweenTime, 0, timeInSeconds);
t = tweenTime / timeInSeconds;
if(state == RawTweenState.Running ||
(state == RawTweenState.LeftEnd && t > 0.0f) ||
(state == RawTweenState.RightEnd && t < 1.0f))
{
float tweenValue = easingFunction(t);
T newValue = lerpMethod(fromVal, to, tweenValue);
setterFunction(newValue);
state = RawTweenState.Running;
}
if(t >= 1.0f)
{
state = RawTweenState.RightEnd;
}
else if(t <= 0.0f)
{
state = RawTweenState.LeftEnd;
setterFunction(originalVal);
}
yield return 0;
if(state == RawTweenState.Resetting)
{
goto Reset;
}
}
}
private IEnumerator RawTweenImpl(object[] targetProperty, float timeInSeconds, T to, object from = null,
EasingFunction easingFunction = null,
TweenPlayDirection playDirection = TweenPlayDirection.Forward)
{
if(targetProperty.Length != 2)
{
Debug.LogWarning("When passing an an object array as targetProperty, the array is expected to have exactly "+
"two elements.");
return YieldBreak;
}
// targetProperty expected to be an obnect with first item the target object and second item the
// targetPropertyName.
object target = targetProperty[0];
if(target == null || !target.GetType().IsValueType)
{
Debug.LogWarning("When passing an object array as targetProperty, the first item must be a reference type "+
"and not null.");
return YieldBreak;
}
string propertyName = targetProperty[1] as string;
if(string.IsNullOrEmpty(propertyName))
{
Debug.LogWarning("When passing an an object array as targetProperty, the second item must be a string " +
"containing the property name.");
return YieldBreak;
}
return RawTweenImpl((target, propertyName), timeInSeconds, to, from, easingFunction, playDirection);
}
// Tween a property on some object.
private IEnumerator RawTweenImpl<U>(ValueTuple<U, string> targetProperty, float timeInSeconds, T to,
object from = null, EasingFunction easingFunction = null,
TweenPlayDirection playDirection = TweenPlayDirection.Forward) where U : class
{
U targetObject = targetProperty.Item1;
string propertyName = targetProperty.Item2;
// Perform runtime checks whether the property can be tweened.
if(targetObject == null)
{
Debug.LogWarning("target is null. Will not tween.");
return YieldBreak;
}
if (!targetObject.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance).Any(
p => p.Name == propertyName))
{
Debug.LogWarning("Object " + targetObject.ToString() + " does not contain a property named " + propertyName +
". Will not tween.");
return YieldBreak;
}
PropertyInfo propInfo = targetObject.GetType().GetProperty(propertyName);
Type propType = propInfo.PropertyType;
if (!propType.Equals(typeof(T)))
{
Debug.LogWarning("Property " + propertyName + " on object " + targetObject.ToString() + " is of type " +
propType.FullName + " but 'to' is of type " + to.GetType().FullName + ". Will not tween.");
return YieldBreak;
}
SetterFunction<T> setterFunction = (T x) => { propInfo.SetValue(targetObject, x); };
GetterFunction<T> getterFunction = () => { return (T)propInfo.GetValue(targetObject); };
if(from == null)
{
from = getterFunction;
}
return RawTweenImpl(setterFunction, timeInSeconds, to, from, easingFunction, playDirection, getterFunction);
}
}
#endregion
#endregion //IMPL
#region Easing
public class Easing
{
/*
The MIT License
Copyright(c) 2010-2012 Tween.js authors.
Easing equations Copyright (c) 2001 Robert Penner http://robertpenner.com/easing/
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal