diff --git a/SharpEngine.Core/Utils/Tween/TweenData.cs b/SharpEngine.Core/Utils/Tween/TweenData.cs
index 5953f1a..c2a4bfa 100644
--- a/SharpEngine.Core/Utils/Tween/TweenData.cs
+++ b/SharpEngine.Core/Utils/Tween/TweenData.cs
@@ -1,84 +1,65 @@
-using SharpEngine.Core.Manager;
using System;
-using System.Linq.Expressions;
-using System.Reflection;
using JetBrains.Annotations;
namespace SharpEngine.Core.Utils.Tween
{
- internal class TweenData
(
- object obj,
- Expression> property,
- TD from,
- TD to,
- float duration,
- bool useFrom = true)
- where TD : notnull
- {
- [UsedImplicitly]
- public object Obj { get; set; } = obj;
+ public abstract class ITweenData
+ {
+ public abstract void Launch();
+ public abstract void Update(float t);
+ }
- [UsedImplicitly]
- public Expression> Property { get; set; } = property;
+ public abstract class TweenData : ITweenData
+ {
+ [UsedImplicitly]
+ public Func Getter { get; set; } = default!;
- [UsedImplicitly]
- public TD From { get; set; } = from;
+ [UsedImplicitly]
+ public Action Setter { get; set; } = default!;
- [UsedImplicitly]
- public TD To { get; set; } = to;
+ [UsedImplicitly]
+ public T From { get; set; } = default!;
- [UsedImplicitly]
- public float Duration { get; set; } = duration;
+ [UsedImplicitly]
+ public T To { get; set; } = default!;
- [UsedImplicitly]
- public TD CurrentValue { get; set; } = default!;
- [UsedImplicitly]
- public TDStep Step { get; set; } = default!;
- [UsedImplicitly]
- public bool UseFrom { get; set; } = useFrom;
+ internal bool UseCurrentValue { get; set; }
- public void Launch()
- {
- CurrentValue = UseFrom ? From : Property.Compile()(Obj);
- Step = CurrentValue switch
- {
- float fFrom when To is float fTo => (TDStep)(object)((fTo - fFrom) / Duration),
- int iFrom when To is int iTo => (TDStep)(object)((iTo - iFrom) / Duration),
- Color cFrom when To is Color cTo => (TDStep)(object)new ColorStep(
- (short)((cTo.R - cFrom.R) / Duration),
- (short)((cTo.G - cFrom.G) / Duration),
- (short)((cTo.B - cFrom.B) / Duration),
- (short)((cTo.A - cFrom.A) / Duration)
- ),
- _ => throw new NotSupportedException("Unsupported type for tweening.")
- };
+ public sealed override void Launch()
+ {
+ if (UseCurrentValue)
+ From = Getter();
+ }
+ }
- DebugManager.Log(LogLevel.Debug, $"Tween Launched: From = {From}, To = {To}, Duration = {Duration}, Step = {Step}");
- }
+ internal sealed class IntTweenData : TweenData
+ {
+ public override void Update(float t)
+ {
+ Setter(t >= 1f ? To : (int)(From + ((double)To - From) * t));
+ }
+ }
- public void Update(float deltaTime)
- {
- if (Duration <= 0)
- {
- ((PropertyInfo)((MemberExpression)Property.Body).Member).SetValue(Obj, To);
- return;
- }
+ internal sealed class FloatTweenData : TweenData
+ {
+ public override void Update(float t)
+ {
+ Setter(t >= 1f ? To : From + (To - From) * t);
+ }
+ }
- CurrentValue = CurrentValue switch
- {
- float cVal when Step is float step => (TD)(object)(cVal + step * deltaTime),
- int cVal when Step is int step => (TD)(object)(cVal + (int)(step * deltaTime)),
- Color cVal when Step is ColorStep step => (TD)(object)new Color(
- (byte)(cVal.R + step.RStep * deltaTime),
- (byte)(cVal.G + step.GStep * deltaTime),
- (byte)(cVal.B + step.BStep * deltaTime),
- (byte)(cVal.A + step.AStep * deltaTime)
- ),
- _ => CurrentValue
- };
-
- ((PropertyInfo)((MemberExpression)Property.Body).Member).SetValue(Obj, CurrentValue);
- Duration -= deltaTime;
- }
- }
+ internal sealed class ColorTweenData : TweenData
+ {
+ public override void Update(float t)
+ {
+ Setter(
+ new Color(
+ (int)(From.R + (To.R - From.R) * t),
+ (int)(From.G + (To.G - From.G) * t),
+ (int)(From.B + (To.B - From.B) * t),
+ (int)(From.A + (To.A - From.A) * t)
+ )
+ );
+ }
+ }
}
diff --git a/SharpEngine.Core/Utils/Tween/TweenStep.cs b/SharpEngine.Core/Utils/Tween/TweenStep.cs
index 2d5d581..8836187 100644
--- a/SharpEngine.Core/Utils/Tween/TweenStep.cs
+++ b/SharpEngine.Core/Utils/Tween/TweenStep.cs
@@ -1,6 +1,7 @@
-using System;
+using System;
using System.Collections.Generic;
using System.Linq.Expressions;
+using System.Reflection;
using JetBrains.Annotations;
namespace SharpEngine.Core.Utils.Tween;
@@ -13,97 +14,316 @@ namespace SharpEngine.Core.Utils.Tween;
/// animated properties.
public class TweenStep
{
- ///
- /// Duration of the step
- ///
- [UsedImplicitly]
- public float CurrentDuration { get; set; }
-
- private readonly List> _floatTweens = [];
- private readonly List> _intTweens = [];
- private readonly List> _colorTweens = [];
-
- ///
- /// Initializes a new instance of the TweenStep class with the specified duration.
- ///
- /// The duration, in seconds, for this tween step. Must be greater than or equal to zero.
- public TweenStep(float duration)
- {
- CurrentDuration = duration;
- }
-
- ///
- /// Add a float tween to the step
- ///
- /// Entity
- /// Property which be modified
- /// Target Value
- /// Duration of the tween
- /// Source Value
- /// Tween Step
- public TweenStep Float(object entity, Expression> property, float to, float duration, float? from = null)
- {
- _floatTweens.Add(new TweenData(entity, property, from ?? 0, to, duration, from is not null));
- return this;
- }
-
- ///
- /// Add an int tween to the step
- ///
- /// Entity
- /// Property which be modified
- /// Target Value
- /// Duration of the tween
- /// Source Value
- /// Tween Steps
- [UsedImplicitly]
- public TweenStep Int(object entity, Expression> property, int to, float duration, int? from = null)
- {
- _intTweens.Add(new TweenData(entity, property, from ?? 0, to, duration, from is not null));
- return this;
- }
-
- ///
- /// Animates a color property of the specified entity from a starting value to a target value over the given
- /// duration.
- ///
- /// The entity whose color property will be animated.
- /// An expression that selects the color property of the entity to animate.
- /// The target color value to animate to.
- /// The duration, in seconds, over which the animation occurs. Must be greater than zero.
- /// The initial color value to animate from. If null, the current value of the property is used.
- /// The current instance, allowing for method chaining.
- [UsedImplicitly]
- public TweenStep Color(object entity, Expression> property, Color to, float duration, Color? from = null)
- {
- _colorTweens.Add(new TweenData(entity, property, from ?? Utils.Color.White, to, duration, from is not null));
- return this;
- }
-
- ///
- /// Update the tween step
- ///
- /// Delta time
- /// If a step ended
- public bool Update(float deltaTime)
- {
- CurrentDuration -= deltaTime;
- foreach (var floatTween in _floatTweens)
- floatTween.Update(deltaTime);
- foreach (var intTween in _intTweens)
- intTween.Update(deltaTime);
- foreach (var colorTween in _colorTweens)
- colorTween.Update(deltaTime);
- return CurrentDuration <= 0f;
- }
-
- internal void Launch()
- {
- foreach (var floatTween in _floatTweens)
- floatTween.Launch();
- foreach (var intTween in _intTweens)
- intTween.Launch();
- foreach (var colorTween in _colorTweens)
- colorTween.Launch();
- }
+ ///
+ /// Duration of the step
+ ///
+ [UsedImplicitly]
+ public float Duration { get; set; }
+
+ ///
+ /// Elapsed time of the step
+ ///
+ [UsedImplicitly]
+ public float Elapsed { get; set; }
+
+ private readonly List<(ITweenData Data, float Duration)> _tweens = [];
+
+ ///
+ /// Initializes a new instance of the TweenStep class with the specified duration.
+ ///
+ /// The duration, in seconds, for this tween step. Must be greater than or equal to zero.
+ public TweenStep(float duration)
+ {
+ Duration = duration;
+ Elapsed = 0;
+ }
+
+ ///
+ /// Add a float tween to the step
+ ///
+ /// Entity
+ /// Property which be modified
+ /// Target Value
+ /// Duration of the tween
+ /// Source Value
+ /// Tween Step
+ public TweenStep Float(
+ T entity,
+ Expression> property,
+ float to,
+ float duration,
+ float? from = null
+ )
+ where T : class
+ {
+ return from.HasValue
+ ? Custom(entity, property, from.Value, to, duration)
+ : Custom(entity, property, to, duration);
+ }
+
+ ///
+ /// Add an int tween to the step
+ ///
+ /// Entity
+ /// Property which be modified
+ /// Target Value
+ /// Duration of the tween
+ ///
+ /// Source Value
+ /// Tween Steps
+ [UsedImplicitly]
+ public TweenStep Int(
+ T entity,
+ Expression> property,
+ int to,
+ float duration,
+ int? from = null
+ )
+ where T : class
+ {
+ return from.HasValue
+ ? Custom(entity, property, from.Value, to, duration)
+ : Custom(entity, property, to, duration);
+ }
+
+ ///
+ /// Animates a color property of the specified entity from a starting value to a target value over the given
+ /// duration.
+ ///
+ /// The entity whose color property will be animated.
+ /// An expression that selects the color property of the entity to animate.
+ /// The target color value to animate to.
+ /// The duration, in seconds, over which the animation occurs. Must be greater than zero.
+ /// The initial color value to animate from. If null, the current value of the property is used.
+ /// The current instance, allowing for method chaining.
+ [UsedImplicitly]
+ public TweenStep Color(
+ T entity,
+ Expression> property,
+ Color to,
+ float duration,
+ Color? from = null
+ )
+ where T : class
+ {
+ return from.HasValue
+ ? Custom(entity, property, from.Value, to, duration)
+ : Custom(entity, property, to, duration);
+ }
+
+ public TweenStep Custom(
+ TEntity entity,
+ Expression> property,
+ T to,
+ float duration
+ )
+ where TEntity : class
+ where TData : TweenData, new()
+ {
+ var compiledGetter = property.Compile();
+ var compiledSetter = CompileSetter(property);
+ Func getter = () => compiledGetter(entity);
+ Action setter = value => compiledSetter(entity, value);
+
+ return Custom(getter, setter, default!, to, duration, useCurrentValue: true);
+ }
+
+ public TweenStep Custom(
+ TEntity entity,
+ Expression> property,
+ T from,
+ T to,
+ float duration
+ )
+ where TEntity : class
+ where TData : TweenData, new()
+ {
+ var compiledGetter = property.Compile();
+ var compiledSetter = CompileSetter(property);
+ Func getter = () => compiledGetter(entity);
+ Action setter = value => compiledSetter(entity, value);
+
+ return Custom(getter, setter, from, to, duration);
+ }
+
+ private TweenStep Custom(
+ Func getter,
+ Action setter,
+ T from,
+ T to,
+ float duration,
+ bool useCurrentValue = false
+ )
+ where TData : TweenData, new()
+ {
+ _tweens.Add(
+ (
+ new TData
+ {
+ Getter = getter,
+ Setter = setter,
+ From = from,
+ To = to,
+ UseCurrentValue = useCurrentValue,
+ },
+ duration
+ )
+ );
+ return this;
+ }
+
+ ///
+ /// Update the tween step
+ ///
+ /// Delta time
+ /// If a step ended
+ public bool Update(float deltaTime)
+ {
+ Elapsed += deltaTime;
+ foreach (var tween in _tweens)
+ {
+ float progress =
+ tween.Duration <= 0 ? 1f : System.Math.Clamp(Elapsed / tween.Duration, 0f, 1f);
+ tween.Data.Update(progress);
+ }
+
+ return Elapsed >= Duration;
+ }
+
+ internal void Launch()
+ {
+ Elapsed = 0;
+ foreach (var tween in _tweens)
+ tween.Data.Launch();
+ }
+
+ private static Action CompileSetter(
+ Expression> expression
+ )
+ where T : class
+ {
+ if (expression.Body is not MemberExpression)
+ throw new ArgumentException(
+ "Expression must be a field or property access.",
+ nameof(expression)
+ );
+
+ // Extract:
+ //
+ // x => x.A.B.C.Value
+ //
+ // into:
+ //
+ // A, B, C, Value
+
+ var members = new List();
+
+ Expression? current = expression.Body;
+
+ while (current is MemberExpression memberExpression)
+ {
+ members.Add(memberExpression.Member);
+ current = memberExpression.Expression;
+ }
+
+ if (current != expression.Parameters[0])
+ throw new ArgumentException(
+ "Expression must originate from the lambda parameter.",
+ nameof(expression)
+ );
+
+ members.Reverse();
+
+ var objParameter = Expression.Parameter(typeof(T), "obj");
+
+ var valueParameter = Expression.Parameter(typeof(TValue), "value");
+
+ List variables = [];
+ List body = [];
+
+ // Structs that will need to be copied back into their parents.
+ var writeBacks =
+ new List<(Expression Parent, MemberInfo Member, ParameterExpression Temp)>();
+
+ Expression owner = objParameter;
+
+ // Walk everything except the final member.
+ for (int i = 0; i < members.Count - 1; i++)
+ {
+ var member = members[i];
+
+ var access = Expression.MakeMemberAccess(owner, member);
+
+ if (access.Type.IsValueType)
+ {
+ /*
+ * Struct:
+ *
+ * obj.Position.X
+ *
+ * becomes roughly:
+ *
+ * var temp = obj.Position;
+ */
+
+ var temp = Expression.Variable(access.Type, $"temp{i}");
+
+ variables.Add(temp);
+
+ body.Add(Expression.Assign(temp, access));
+
+ writeBacks.Add((owner, member, temp));
+
+ owner = temp;
+ }
+ else
+ {
+ // Classes don't need copying.
+ owner = access;
+ }
+ }
+
+ // Final property/field.
+ var leafMember = members[^1];
+
+ EnsureWritable(leafMember);
+
+ var leaf = Expression.MakeMemberAccess(owner, leafMember);
+
+ body.Add(Expression.Assign(leaf, valueParameter));
+
+ for (int i = writeBacks.Count - 1; i >= 0; i--)
+ {
+ var writeBack = writeBacks[i];
+
+ EnsureWritable(writeBack.Member);
+
+ var destination = Expression.MakeMemberAccess(writeBack.Parent, writeBack.Member);
+
+ body.Add(Expression.Assign(destination, writeBack.Temp));
+ }
+
+ var block = Expression.Block(variables, body);
+
+ return Expression.Lambda>(block, objParameter, valueParameter).Compile();
+ }
+
+ private static void EnsureWritable(MemberInfo member)
+ {
+ switch (member)
+ {
+ case PropertyInfo { SetMethod: null } property:
+ throw new ArgumentException($"Property '{property.Name}' is not writable.");
+
+ case FieldInfo { IsInitOnly: true } field:
+ throw new ArgumentException($"Field '{field.Name}' is readonly.");
+
+ case PropertyInfo:
+ case FieldInfo:
+ return;
+
+ default:
+ throw new ArgumentException($"Member '{member.Name}' is not a field or property.");
+ }
+ }
}
diff --git a/Testing/MyScene.cs b/Testing/MyScene.cs
index ff67ff7..ce1dd39 100644
--- a/Testing/MyScene.cs
+++ b/Testing/MyScene.cs
@@ -46,15 +46,16 @@ public override void OpenScene()
{
base.OpenScene();
+ TransformComponent transform = Entities[0].GetComponentAs()!;
Window?.TweenManager.Tweens.Add(new Tween([
new TweenStep(5)
- .Float(Entities[0].GetComponentAs()!, x => ((TransformComponent)x).LocalRotation, 360, 5)
- .Float(Entities[0].GetComponentAs()!.LocalPosition, x => ((Vec2)x).X, 100, 5)
- .Float(Entities[0].GetComponentAs()!.LocalPosition, x => ((Vec2)x).Y, 100, 5),
+ .Float(transform, x => x.LocalRotation, 360, 5)
+ .Float(transform, x => x.LocalPosition.X, 100, 5)
+ .Float(transform, x => x.LocalPosition.Y, 100, 5),
new TweenStep(10)
- .Float(Entities[0].GetComponentAs()!, x => ((TransformComponent)x).LocalRotation, 720, 5)
- .Float(Entities[0].GetComponentAs()!.LocalPosition, x => ((Vec2)x).X, 0, 5)
- .Float(Entities[0].GetComponentAs()!.LocalPosition, x => ((Vec2)x).Y, 0, 5),
+ .Float(transform, x => x.LocalRotation, 720, 5)
+ .Float(transform, x => x.LocalPosition.X, 0, 5)
+ .Float(transform, x => x.LocalPosition.Y, 0, 5),
], () => DebugManager.Log(LogLevel.Info, "FIN DU TWEEN")));
}
-}
+}
\ No newline at end of file
|