summaryrefslogtreecommitdiff
path: root/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening
diff options
context:
space:
mode:
Diffstat (limited to 'Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening')
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/ColorTween.cs15
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/EasingFunctions.cs120
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/LinearOperations.cs21
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/LinearTween.cs23
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/MonoGame.Extended.Tweening.csproj12
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/Tween.cs184
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/TweenFieldMember.cs44
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/TweenMember.cs37
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/TweenPropertyMember.cs43
-rw-r--r--Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/Tweener.cs133
10 files changed, 632 insertions, 0 deletions
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/ColorTween.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/ColorTween.cs
new file mode 100644
index 0000000..2e98e5d
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/ColorTween.cs
@@ -0,0 +1,15 @@
+using Microsoft.Xna.Framework;
+
+namespace MonoGame.Extended.Tweening;
+
+public class ColorTween: Tween<Color>
+{
+ internal ColorTween(object target, float duration, float delay, TweenMember<Color> member, Color endValue) : base(target, duration, delay, member, endValue)
+ {
+ }
+
+ protected override void Interpolate(float n)
+ {
+ Member.Value = Color.Lerp(_startValue, _endValue, n);
+ }
+}
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/EasingFunctions.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/EasingFunctions.cs
new file mode 100644
index 0000000..f93a5ff
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/EasingFunctions.cs
@@ -0,0 +1,120 @@
+using System;
+using Microsoft.Xna.Framework;
+
+namespace MonoGame.Extended.Tweening
+{
+ public static class EasingFunctions
+ {
+ public static float Linear(float value) => value;
+
+ public static float CubicIn(float value) => Power.In(value, 3);
+ public static float CubicOut(float value) => Power.Out(value, 3);
+ public static float CubicInOut(float value) => Power.InOut(value, 3);
+
+ public static float QuadraticIn(float value) => Power.In(value, 2);
+ public static float QuadraticOut(float value) => Power.Out(value, 2);
+ public static float QuadraticInOut(float value) => Power.InOut(value, 2);
+
+ public static float QuarticIn(float value) => Power.In(value, 4);
+ public static float QuarticOut(float value) => Power.Out(value, 4);
+ public static float QuarticInOut(float value) => Power.InOut(value, 4);
+
+ public static float QuinticIn(float value) => Power.In(value, 5);
+ public static float QuinticOut(float value) => Power.Out(value, 5);
+ public static float QuinticInOut(float value) => Power.InOut(value, 5);
+
+ public static float SineIn(float value) => (float) Math.Sin(value*MathHelper.PiOver2 - MathHelper.PiOver2) + 1;
+ public static float SineOut(float value) => (float) Math.Sin(value*MathHelper.PiOver2);
+ public static float SineInOut(float value) => (float) (Math.Sin(value*MathHelper.Pi - MathHelper.PiOver2) + 1)/2;
+
+ public static float ExponentialIn(float value) => (float) Math.Pow(2, 10*(value - 1));
+ public static float ExponentialOut(float value) => Out(value, ExponentialIn);
+ public static float ExponentialInOut(float value) => InOut(value, ExponentialIn);
+
+ public static float CircleIn(float value) => (float) -(Math.Sqrt(1 - value * value) - 1);
+ public static float CircleOut(float value) => (float) Math.Sqrt(1 - (value - 1) * (value - 1));
+ public static float CircleInOut(float value) => (float) (value <= .5 ? (Math.Sqrt(1 - value * value * 4) - 1) / -2 : (Math.Sqrt(1 - (value * 2 - 2) * (value * 2 - 2)) + 1) / 2);
+
+ public static float ElasticIn(float value)
+ {
+ const int oscillations = 1;
+ const float springiness = 3f;
+ var e = (Math.Exp(springiness*value) - 1)/(Math.Exp(springiness) - 1);
+ return (float) (e*Math.Sin((MathHelper.PiOver2 + MathHelper.TwoPi*oscillations)*value));
+ }
+
+ public static float ElasticOut(float value) => Out(value, ElasticIn);
+ public static float ElasticInOut(float value) => InOut(value, ElasticIn);
+
+ public static float BackIn(float value)
+ {
+ const float amplitude = 1f;
+ return (float) (Math.Pow(value, 3) - value*amplitude*Math.Sin(value*MathHelper.Pi));
+ }
+
+ public static float BackOut(float value) => Out(value, BackIn);
+ public static float BackInOut(float value) => InOut(value, BackIn);
+
+ public static float BounceOut(float value) => Out(value, BounceIn);
+ public static float BounceInOut(float value) => InOut(value, BounceIn);
+
+ public static float BounceIn(float value)
+ {
+ const float bounceConst1 = 2.75f;
+ var bounceConst2 = (float) Math.Pow(bounceConst1, 2);
+
+ value = 1 - value; //flip x-axis
+
+ if (value < 1/bounceConst1) // big bounce
+ return 1f - bounceConst2*value*value;
+
+ if (value < 2/bounceConst1)
+ return 1 - (float) (bounceConst2*Math.Pow(value - 1.5f/bounceConst1, 2) + .75);
+
+ if (value < 2.5/bounceConst1)
+ return 1 - (float) (bounceConst2*Math.Pow(value - 2.25f/bounceConst1, 2) + .9375);
+
+ //small bounce
+ return 1f - (float) (bounceConst2*Math.Pow(value - 2.625f/bounceConst1, 2) + .984375);
+ }
+
+
+ private static float Out(float value, Func<float, float> function)
+ {
+ return 1 - function(1 - value);
+ }
+
+ private static float InOut(float value, Func<float, float> function)
+ {
+ if (value < 0.5f)
+ return 0.5f*function(value*2);
+
+ return 1f - 0.5f*function(2 - value*2);
+ }
+
+ private static class Power
+ {
+ public static float In(double value, int power)
+ {
+ return (float) Math.Pow(value, power);
+ }
+
+ public static float Out(double value, int power)
+ {
+ var sign = power%2 == 0 ? -1 : 1;
+ return (float) (sign*(Math.Pow(value - 1, power) + sign));
+ }
+
+ public static float InOut(double s, int power)
+ {
+ s *= 2;
+
+ if (s < 1)
+ return In(s, power)/2;
+
+ var sign = power%2 == 0 ? -1 : 1;
+ return (float) (sign/2.0*(Math.Pow(s - 2, power) + sign*2));
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/LinearOperations.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/LinearOperations.cs
new file mode 100644
index 0000000..12a03a8
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/LinearOperations.cs
@@ -0,0 +1,21 @@
+using System;
+using System.Linq.Expressions;
+
+namespace MonoGame.Extended.Tweening;
+
+public class LinearOperations<T>
+{
+ static LinearOperations()
+ {
+ var a = Expression.Parameter(typeof(T));
+ var b = Expression.Parameter(typeof(T));
+ var c = Expression.Parameter(typeof(float));
+ Add = Expression.Lambda<Func<T, T, T>>(Expression.Add(a, b), a, b).Compile();
+ Subtract = Expression.Lambda<Func<T, T, T>>(Expression.Subtract(a, b), a, b).Compile();
+ Multiply = Expression.Lambda<Func<T, float, T>>(Expression.Multiply(a, c), a, c).Compile();
+ }
+
+ public static Func<T, T, T> Add { get; }
+ public static Func<T, T, T> Subtract { get; }
+ public static Func<T, float, T> Multiply { get; }
+}
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/LinearTween.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/LinearTween.cs
new file mode 100644
index 0000000..8469a13
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/LinearTween.cs
@@ -0,0 +1,23 @@
+namespace MonoGame.Extended.Tweening;
+
+public class LinearTween<T>: Tween<T>
+ where T: struct
+{
+ private T _range;
+
+ internal LinearTween(object target, float duration, float delay, TweenMember<T> member, T endValue) : base(target, duration, delay, member, endValue)
+ {
+ }
+
+ protected override void Initialize()
+ {
+ base.Initialize();
+ _range = LinearOperations<T>.Subtract(_endValue, _startValue);
+ }
+
+ protected override void Interpolate(float n)
+ {
+ var value = LinearOperations<T>.Add(_startValue, LinearOperations<T>.Multiply(_range, n));
+ Member.Value = value;
+ }
+}
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/MonoGame.Extended.Tweening.csproj b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/MonoGame.Extended.Tweening.csproj
new file mode 100644
index 0000000..8395e42
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/MonoGame.Extended.Tweening.csproj
@@ -0,0 +1,12 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+ <PropertyGroup>
+ <Description>A tweening system to make MonoGame more awesome.</Description>
+ <PackageTags>monogame animations tweening</PackageTags>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <ProjectReference Include="..\MonoGame.Extended\MonoGame.Extended.csproj" />
+ </ItemGroup>
+
+</Project>
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/Tween.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/Tween.cs
new file mode 100644
index 0000000..ad29251
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/Tween.cs
@@ -0,0 +1,184 @@
+using System;
+using Microsoft.Xna.Framework;
+
+namespace MonoGame.Extended.Tweening
+{
+ public abstract class Tween<T> : Tween
+ where T : struct
+ {
+ internal Tween(object target, float duration, float delay, TweenMember<T> member, T endValue)
+ : base(target, duration, delay)
+ {
+ Member = member;
+ _endValue = endValue;
+ }
+
+ public TweenMember<T> Member { get; }
+ public override string MemberName => Member.Name;
+
+ protected T _startValue;
+ protected T _endValue;
+
+ protected override void Initialize()
+ {
+ _startValue = Member.Value;
+ }
+
+ protected override void Swap()
+ {
+ _endValue = _startValue;
+ Initialize();
+ }
+ }
+
+ public abstract class Tween
+ {
+ internal Tween(object target, float duration, float delay)
+ {
+ Target = target;
+ Duration = duration;
+ Delay = delay;
+ IsAlive = true;
+
+ _remainingDelay = delay;
+ }
+
+ public object Target { get; }
+ public abstract string MemberName { get; }
+ public float Duration { get; }
+ public float Delay { get; }
+ public bool IsPaused { get; set; }
+ public bool IsRepeating => _remainingRepeats != 0;
+ public bool IsRepeatingForever => _remainingRepeats < 0;
+ public bool IsAutoReverse { get; private set; }
+ public bool IsAlive { get; private set; }
+ public bool IsComplete { get; private set; }
+ public float TimeRemaining => Duration - _elapsedDuration;
+ public float Completion => MathHelper.Clamp(_completion, 0, 1);
+
+ private Func<float, float> _easingFunction;
+ private bool _isInitialized;
+ private float _completion;
+ private float _elapsedDuration;
+ private float _remainingDelay;
+ private float _repeatDelay;
+ private int _remainingRepeats;
+ private Action<Tween> _onBegin;
+ private Action<Tween> _onEnd;
+
+ public Tween Easing(Func<float, float> easingFunction) { _easingFunction = easingFunction; return this; }
+ public Tween OnBegin(Action<Tween> action) { _onBegin = action; return this; }
+ public Tween OnEnd(Action<Tween> action) { _onEnd = action; return this; }
+ public Tween Pause() { IsPaused = true; return this; }
+ public Tween Resume() { IsPaused = false; return this; }
+
+ public Tween Repeat(int count, float repeatDelay = 0f)
+ {
+ _remainingRepeats = count;
+ _repeatDelay = repeatDelay;
+ return this;
+ }
+
+ public Tween RepeatForever(float repeatDelay = 0f)
+ {
+ _remainingRepeats = -1;
+ _repeatDelay = repeatDelay;
+ return this;
+ }
+
+ public Tween AutoReverse()
+ {
+ if (_remainingRepeats == 0)
+ _remainingRepeats = 1;
+
+ IsAutoReverse = true;
+ return this;
+ }
+
+ protected abstract void Initialize();
+ protected abstract void Interpolate(float n);
+ protected abstract void Swap();
+
+ public void Cancel()
+ {
+ _remainingRepeats = 0;
+ IsAlive = false;
+ }
+
+ public void CancelAndComplete()
+ {
+ if (IsAlive)
+ {
+ _completion = 1;
+
+ Interpolate(1);
+ IsComplete = true;
+ _onEnd?.Invoke(this);
+ }
+
+ Cancel();
+ }
+
+ public void Update(float elapsedSeconds)
+ {
+ if(IsPaused || !IsAlive)
+ return;
+
+ if (_remainingDelay > 0)
+ {
+ _remainingDelay -= elapsedSeconds;
+
+ if (_remainingDelay > 0)
+ return;
+ }
+
+ if (!_isInitialized)
+ {
+ _isInitialized = true;
+ Initialize();
+ _onBegin?.Invoke(this);
+ }
+
+ if (IsComplete)
+ {
+ IsComplete = false;
+ _elapsedDuration = 0;
+ _onBegin?.Invoke(this);
+
+ if (IsAutoReverse)
+ Swap();
+ }
+
+ _elapsedDuration += elapsedSeconds;
+
+ var n = _completion = _elapsedDuration / Duration;
+
+ if (_easingFunction != null)
+ n = _easingFunction(n);
+
+ if (_elapsedDuration >= Duration)
+ {
+ if (_remainingRepeats != 0)
+ {
+ if(_remainingRepeats > 0)
+ _remainingRepeats--;
+
+ _remainingDelay = _repeatDelay;
+ }
+ else if (_remainingRepeats == 0)
+ {
+ IsAlive = false;
+ }
+
+ n = _completion = 1;
+ IsComplete = true;
+ }
+
+ Interpolate(n);
+
+ if (IsComplete)
+ _onEnd?.Invoke(this);
+ }
+
+ }
+}
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/TweenFieldMember.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/TweenFieldMember.cs
new file mode 100644
index 0000000..da2f75d
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/TweenFieldMember.cs
@@ -0,0 +1,44 @@
+using System;
+using System.Diagnostics;
+using System.Linq.Expressions;
+using System.Reflection;
+
+namespace MonoGame.Extended.Tweening
+{
+ public sealed class TweenFieldMember<T> : TweenMember<T>
+ where T : struct
+ {
+ private readonly FieldInfo _fieldInfo;
+
+ public TweenFieldMember(object target, FieldInfo fieldInfo)
+ : base(target, CompileGetMethod(fieldInfo), CompileSetMethod(fieldInfo))
+ {
+ _fieldInfo = fieldInfo;
+ }
+
+ private static Func<object, object> CompileGetMethod(FieldInfo fieldInfo)
+ {
+ var self = Expression.Parameter(typeof(object));
+ var instance = Expression.Convert(self, fieldInfo.DeclaringType);
+ var field = Expression.Field(instance, fieldInfo);
+ var convert = Expression.TypeAs(field, typeof(object));
+
+ return Expression.Lambda<Func<object, object>>(convert, self).Compile();
+ }
+
+ private static Action<object, object> CompileSetMethod(FieldInfo fieldInfo)
+ {
+ Debug.Assert(fieldInfo.DeclaringType != null);
+
+ var self = Expression.Parameter(typeof(object));
+ var value = Expression.Parameter(typeof(object));
+ var fieldExp = Expression.Field(Expression.Convert(self, fieldInfo.DeclaringType), fieldInfo);
+ var assignExp = Expression.Assign(fieldExp, Expression.Convert(value, fieldInfo.FieldType));
+
+ return Expression.Lambda<Action<object, object>>(assignExp, self, value).Compile();
+ }
+
+ public override Type Type => _fieldInfo.FieldType;
+ public override string Name => _fieldInfo.Name;
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/TweenMember.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/TweenMember.cs
new file mode 100644
index 0000000..adcee59
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/TweenMember.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Linq.Expressions;
+
+namespace MonoGame.Extended.Tweening
+{
+ public abstract class TweenMember
+ {
+ protected TweenMember(object target)
+ {
+ Target = target;
+ }
+
+ public object Target { get; }
+ public abstract Type Type { get; }
+ public abstract string Name { get; }
+ }
+
+ public abstract class TweenMember<T> : TweenMember
+ where T : struct
+ {
+ protected TweenMember(object target, Func<object, object> getMethod, Action<object, object> setMethod)
+ : base(target)
+ {
+ _getMethod = getMethod;
+ _setMethod = setMethod;
+ }
+
+ private readonly Func<object, object> _getMethod;
+ private readonly Action<object, object> _setMethod;
+
+ public T Value
+ {
+ get { return (T) _getMethod(Target); }
+ set { _setMethod(Target, value); }
+ }
+ }
+}
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/TweenPropertyMember.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/TweenPropertyMember.cs
new file mode 100644
index 0000000..7b1db71
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/TweenPropertyMember.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Diagnostics;
+using System.Linq.Expressions;
+using System.Reflection;
+
+namespace MonoGame.Extended.Tweening
+{
+ public sealed class TweenPropertyMember<T> : TweenMember<T>
+ where T : struct
+ {
+ private readonly PropertyInfo _propertyInfo;
+
+ public TweenPropertyMember(object target, PropertyInfo propertyInfo)
+ : base(target, CompileGetMethod(propertyInfo), CompileSetMethod(propertyInfo))
+ {
+ _propertyInfo = propertyInfo;
+ }
+
+ public override Type Type => _propertyInfo.PropertyType;
+ public override string Name => _propertyInfo.Name;
+
+ private static Func<object, object> CompileGetMethod(PropertyInfo propertyInfo)
+ {
+ var param = Expression.Parameter(typeof(object));
+ var instance = Expression.Convert(param, propertyInfo.DeclaringType);
+ var convert = Expression.TypeAs(Expression.Property(instance, propertyInfo), typeof(object));
+ return Expression.Lambda<Func<object, object>>(convert, param).Compile();
+ }
+
+ private static Action<object, object> CompileSetMethod(PropertyInfo propertyInfo)
+ {
+ Debug.Assert(propertyInfo.DeclaringType != null);
+
+ var param = Expression.Parameter(typeof(object));
+ var argument = Expression.Parameter(typeof(object));
+ var expression = Expression.Convert(param, propertyInfo.DeclaringType);
+ var methodInfo = propertyInfo.SetMethod;
+ var arguments = Expression.Convert(argument, propertyInfo.PropertyType);
+ var setterCall = Expression.Call(expression, methodInfo, arguments);
+ return Expression.Lambda<Action<object, object>>(setterCall, param, argument).Compile();
+ }
+ }
+} \ No newline at end of file
diff --git a/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/Tweener.cs b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/Tweener.cs
new file mode 100644
index 0000000..61b55f9
--- /dev/null
+++ b/Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening/Tweener.cs
@@ -0,0 +1,133 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Linq.Expressions;
+using System.Reflection;
+using Microsoft.Xna.Framework;
+
+namespace MonoGame.Extended.Tweening
+{
+ public class Tweener : IDisposable
+ {
+ public Tweener()
+ {
+ }
+
+ public void Dispose()
+ {
+ CancelAll();
+ _activeTweens.Clear();
+ _memberCache.Clear();
+ }
+
+ public long AllocationCount { get; private set; }
+
+ private readonly List<Tween> _activeTweens = new List<Tween>();
+
+ public Tween<TMember> TweenTo<TTarget, TMember>(TTarget target, Expression<Func<TTarget, TMember>> expression, TMember toValue, float duration, float delay = 0f)
+ where TTarget : class
+ where TMember : struct
+ {
+ switch (toValue)
+ {
+ case Color toValueColor:
+ return (Tween<TMember>)(object)TweenTo<TTarget, Color, ColorTween>(target, expression as Expression<Func<TTarget, Color>>, toValueColor, duration, delay);
+ default:
+ return TweenTo<TTarget, TMember, LinearTween<TMember>>(target, expression, toValue, duration, delay);
+ }
+
+ }
+
+ public Tween<TMember> TweenTo<TTarget, TMember, TTween>(TTarget target, Expression<Func<TTarget, TMember>> expression, TMember toValue, float duration, float delay = 0f)
+ where TTarget : class
+ where TMember : struct
+ where TTween : Tween<TMember>
+ {
+ var memberExpression = (MemberExpression)expression.Body;
+ var memberInfo = memberExpression.Member;
+ var member = GetMember<TMember>(target, memberInfo.Name);
+ var activeTween = FindTween(target, member.Name);
+
+ activeTween?.Cancel();
+
+ AllocationCount++;
+ var tween = (TTween)Activator.CreateInstance(typeof(TTween),
+ BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, null,
+ new object[]{target, duration, delay, member, toValue}, null);
+ _activeTweens.Add(tween);
+ return tween;
+ }
+
+ public void Update(float elapsedSeconds)
+ {
+ for (var i = _activeTweens.Count - 1; i >= 0; i--)
+ {
+ var tween = _activeTweens[i];
+
+ tween.Update(elapsedSeconds);
+
+ if (!tween.IsAlive)
+ _activeTweens.RemoveAt(i);
+ }
+ }
+
+ public Tween FindTween(object target, string memberName)
+ {
+ return _activeTweens.FirstOrDefault(t => t.Target == target && t.MemberName == memberName);
+ }
+
+ public void CancelAll()
+ {
+ foreach (var tween in _activeTweens)
+ tween.Cancel();
+ }
+
+ public void CancelAndCompleteAll()
+ {
+ foreach (var tween in _activeTweens)
+ tween.CancelAndComplete();
+ }
+
+ private struct TweenMemberKey
+ {
+#pragma warning disable 414
+ public object Target;
+ public string MemberName;
+#pragma warning restore 414
+ }
+
+ private readonly Dictionary<TweenMemberKey, TweenMember> _memberCache = new Dictionary<TweenMemberKey, TweenMember>();
+
+ private TweenMember<T> GetMember<T>(object target, string memberName)
+ where T : struct
+ {
+ var key = new TweenMemberKey { Target = target, MemberName = memberName };
+
+ if (_memberCache.TryGetValue(key, out var member))
+ return (TweenMember<T>) member;
+
+ member = CreateMember<T>(target, memberName);
+ _memberCache.Add(key, member);
+ return (TweenMember<T>) member;
+ }
+
+ private TweenMember<T> CreateMember<T>(object target, string memberName)
+ where T : struct
+ {
+ AllocationCount++;
+
+ var type = target.GetType();
+ var property = type.GetTypeInfo().GetProperty(memberName);
+
+ if (property != null)
+ return new TweenPropertyMember<T>(target, property);
+
+ var field = type.GetTypeInfo().GetField(memberName);
+
+ if (field != null)
+ return new TweenFieldMember<T>(target, field);
+
+ throw new InvalidOperationException($"'{memberName}' is not a property or field of the target");
+ }
+ }
+}