diff options
author | chai <215380520@qq.com> | 2024-06-03 10:15:45 +0800 |
---|---|---|
committer | chai <215380520@qq.com> | 2024-06-03 10:15:45 +0800 |
commit | acea7b2e728787a0d83bbf83c8c1f042d2c32e7e (patch) | |
tree | 0bfec05c1ca2d71be2c337bcd110a0421f19318b /Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening | |
parent | 88febcb02bf127d961c6471d9e846c0e1315f5c3 (diff) |
+ plugins project
Diffstat (limited to 'Plugins/MonoGame.Extended/source/MonoGame.Extended.Tweening')
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"); + } + } +} |