From 61625a4add92622db81b43df690f39852677bd9c Mon Sep 17 00:00:00 2001 From: Sage Vaillancourt Date: Thu, 24 Apr 2025 14:34:25 -0400 Subject: [PATCH] Add a simple EasingSystem --- MonoGameBlank2dStartKit.sln.DotSettings.user | 1 + .../Components.cs | 58 ++++++++++++- .../MonoGameBlank2dStartKitGame.cs | 6 +- .../MonoGameBlank2dStartKit.Core/Scenarios.cs | 12 ++- .../Systems/EasingSystem.cs | 87 +++++++++++++++++++ .../Systems/RotationSystem.cs | 10 ++- 6 files changed, 164 insertions(+), 10 deletions(-) create mode 100644 MonoGameBlank2dStartKit/MonoGameBlank2dStartKit.Core/Systems/EasingSystem.cs diff --git a/MonoGameBlank2dStartKit.sln.DotSettings.user b/MonoGameBlank2dStartKit.sln.DotSettings.user index 8082b2f..36ea3c3 100644 --- a/MonoGameBlank2dStartKit.sln.DotSettings.user +++ b/MonoGameBlank2dStartKit.sln.DotSettings.user @@ -1,5 +1,6 @@  ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded diff --git a/MonoGameBlank2dStartKit/MonoGameBlank2dStartKit.Core/Components.cs b/MonoGameBlank2dStartKit/MonoGameBlank2dStartKit.Core/Components.cs index 6053278..8ab281f 100644 --- a/MonoGameBlank2dStartKit/MonoGameBlank2dStartKit.Core/Components.cs +++ b/MonoGameBlank2dStartKit/MonoGameBlank2dStartKit.Core/Components.cs @@ -2,6 +2,7 @@ using System; using System.Numerics; using Microsoft.Xna.Framework.Graphics; using MonoGameBlank2dStartKit.Core.Content; +using MonoGameBlank2dStartKit.Core.Utils; namespace MonoGameBlank2dStartKit.Core; @@ -19,6 +20,9 @@ public readonly record struct Mass(int Value); public readonly record struct RotationSpeed(float Speed, float Amount = float.MaxValue, float Drag = 0) { + public RotationSpeed(float speed, TimeSpan timeSpan) : this(speed, speed * timeSpan.SecFloat()) + { + } } public readonly record struct Rotation(float Value, Vector2 Origin) @@ -31,4 +35,56 @@ public readonly record struct Rotation(float Value, Vector2 Origin) } } -public readonly record struct Image(ImageId ImageId); \ No newline at end of file +public readonly record struct Image(ImageId ImageId); + +public interface IMovement +{ + Position Start { get; init; } + Position Target { get; init; } + float DurationSec { get; init; } + float ProgressSec { get; init; } +} + +public readonly record struct MoveLinearly( + Position Start, + Position Target, + float DurationSec, + float ProgressSec = 0f +) : IMovement; + +public readonly record struct CubicEaseIn( + Position Start, + Position Target, + float DurationSec, + float ProgressSec = 0f +) : IMovement; + +public readonly record struct CubicEaseOut( + Position Start, + Position Target, + float DurationSec, + float ProgressSec = 0f +) : IMovement; + +public readonly record struct CubicEaseInOut( + Position Start, + Position Target, + float DurationSec, + float ProgressSec = 0f +) : IMovement; + +public readonly record struct QuinticEaseIn( + Position Start, + Position Target, + float DurationSec, + float ProgressSec = 0f +) : IMovement; + +public readonly record struct QuinticEaseOut( + Position Start, + Position Target, + float DurationSec, + float ProgressSec = 0f +) : IMovement; + +public readonly record struct AddEntityOnStop(); \ No newline at end of file diff --git a/MonoGameBlank2dStartKit/MonoGameBlank2dStartKit.Core/MonoGameBlank2dStartKitGame.cs b/MonoGameBlank2dStartKit/MonoGameBlank2dStartKit.Core/MonoGameBlank2dStartKitGame.cs index 575baa5..ce4af25 100644 --- a/MonoGameBlank2dStartKit/MonoGameBlank2dStartKit.Core/MonoGameBlank2dStartKitGame.cs +++ b/MonoGameBlank2dStartKit/MonoGameBlank2dStartKit.Core/MonoGameBlank2dStartKitGame.cs @@ -8,7 +8,6 @@ using Microsoft.Xna.Framework.Input; using MonoGameBlank2dStartKit.Core.Content; using MonoGameBlank2dStartKit.Core.Systems; using MoonTools.ECS; -using static System.Net.Mime.MediaTypeNames; namespace MonoGameBlank2dStartKit.Core { @@ -21,6 +20,7 @@ namespace MonoGameBlank2dStartKit.Core private TextureDrawSystem TextureDrawSystem { get; set; } private VelocitySystem VelocitySystem { get; set; } private RotationSystem RotationSystem { get; set; } + private EasingSystem EasingSystem { get; set; } // Resources for drawing. private readonly GraphicsDeviceManager graphicsDeviceManager; @@ -57,7 +57,7 @@ namespace MonoGameBlank2dStartKit.Core // Configure screen orientations. graphicsDeviceManager.SupportedOrientations = DisplayOrientation.LandscapeLeft | DisplayOrientation.LandscapeRight; - + World = new World(); } @@ -94,6 +94,7 @@ namespace MonoGameBlank2dStartKit.Core TextureDrawSystem = new TextureDrawSystem(World, _assets, _spriteBatch); VelocitySystem = new VelocitySystem(World); RotationSystem = new RotationSystem(World); + EasingSystem = new EasingSystem(World); Scenarios.Basic(World, _assets); base.LoadContent(); } @@ -114,6 +115,7 @@ namespace MonoGameBlank2dStartKit.Core var elapsedGameTime = gameTime.ElapsedGameTime; VelocitySystem.Update(elapsedGameTime); RotationSystem.Update(elapsedGameTime); + EasingSystem.Update(elapsedGameTime); base.Update(gameTime); } diff --git a/MonoGameBlank2dStartKit/MonoGameBlank2dStartKit.Core/Scenarios.cs b/MonoGameBlank2dStartKit/MonoGameBlank2dStartKit.Core/Scenarios.cs index 58ce55a..52d1adc 100644 --- a/MonoGameBlank2dStartKit/MonoGameBlank2dStartKit.Core/Scenarios.cs +++ b/MonoGameBlank2dStartKit/MonoGameBlank2dStartKit.Core/Scenarios.cs @@ -1,3 +1,4 @@ +using System; using System.Numerics; using MonoGameBlank2dStartKit.Core.Content; using MoonTools.ECS; @@ -9,10 +10,15 @@ public static class Scenarios public static void Basic(World world, Assets assets) { var ball = world.CreateEntity(); - world.Set(ball, new Position(new Vector2(10, 10))); world.Set(ball, new Image(ImageId.Ball)); - world.Set(ball, new Velocity(40f, 40f)); + world.Set(ball, new MoveLinearly + { + Start = new Position(new Vector2(10, 10)), + Target = new Position(500, 300), + DurationSec = 1.5f, + }); + // world.Set(ball, new Velocity(40f, 40f)); world.Set(ball, new Rotation(0, assets.GetTexture2D(ImageId.Ball))); - world.Set(ball, new RotationSpeed(3f, Drag: 0.03f)); + world.Set(ball, new RotationSpeed(3f, TimeSpan.FromSeconds(1.5))); } } \ No newline at end of file diff --git a/MonoGameBlank2dStartKit/MonoGameBlank2dStartKit.Core/Systems/EasingSystem.cs b/MonoGameBlank2dStartKit/MonoGameBlank2dStartKit.Core/Systems/EasingSystem.cs new file mode 100644 index 0000000..87bfbbd --- /dev/null +++ b/MonoGameBlank2dStartKit/MonoGameBlank2dStartKit.Core/Systems/EasingSystem.cs @@ -0,0 +1,87 @@ +using System; +using MonoGameBlank2dStartKit.Core.Utils; +using MoonTools.ECS; + +namespace MonoGameBlank2dStartKit.Core.Systems; + +public class EasingSystem : MoonTools.ECS.System +{ + private record EasingFilter(Filter Filter, Action, float> ProcessEntities); + + private readonly EasingFilter[] _easingFilters; + + public EasingSystem(World world) : base(world) + { + _easingFilters = + [ + BuildEasingFilter(p => p), + BuildEasingFilter(p => p * p * p), + BuildEasingFilter(p => + { + float f = p - 1; + return (f * f * f) + 1; + }), + BuildEasingFilter(p => + { + if (p < 0.5) + { + return 4 * p * p * p; + } + + float f = 2 * p - 2; + return 0.5f * f * f * f + 1; + }), + BuildEasingFilter(p => p * p * p * p * p), + BuildEasingFilter(p => + { + float f = p - 1; + return (f * f * f * f * f) + 1; + }), + ]; + } + + private EasingFilter BuildEasingFilter(Func easingFunc) where T : unmanaged, IMovement + { + var filter = FilterBuilder.Include().Build(); + return new EasingFilter(filter, (entities, deltaSec) => + { + foreach (var entity in entities) + { + var currentMovement = World.Get(entity); + ApplyMovement(deltaSec, entity, currentMovement, easingFunc); + } + }); + } + + public override void Update(TimeSpan delta) + { + float deltaSec = delta.SecFloat(); + + foreach (var easingFilter in _easingFilters) + { + easingFilter.ProcessEntities(easingFilter.Filter.Entities, deltaSec); + } + } + + private void ApplyMovement( + float deltaSec, + Entity entity, + T currentMovement, + Func easingFunction) where T : unmanaged, IMovement + { + float newProgressSec = currentMovement.ProgressSec + deltaSec; + float percentComplete = newProgressSec / currentMovement.DurationSec; + if (percentComplete >= 1) + { + World.Set(entity, currentMovement.Target); + World.Remove(entity); + } + else + { + var diff = currentMovement.Target.Vector2 - currentMovement.Start.Vector2; + var newPos = new Position((currentMovement.Start.Vector2) + (diff * easingFunction(percentComplete))); + World.Set(entity, newPos); + World.Set(entity, currentMovement with { ProgressSec = newProgressSec }); + } + } +} \ No newline at end of file diff --git a/MonoGameBlank2dStartKit/MonoGameBlank2dStartKit.Core/Systems/RotationSystem.cs b/MonoGameBlank2dStartKit/MonoGameBlank2dStartKit.Core/Systems/RotationSystem.cs index 43fc585..257c518 100644 --- a/MonoGameBlank2dStartKit/MonoGameBlank2dStartKit.Core/Systems/RotationSystem.cs +++ b/MonoGameBlank2dStartKit/MonoGameBlank2dStartKit.Core/Systems/RotationSystem.cs @@ -17,15 +17,17 @@ public class RotationSystem : MoonTools.ECS.System public override void Update(TimeSpan delta) { + float deltaSec = delta.SecFloat(); foreach (var entity in _filter.Entities) { var currentRotation = World.Get(entity); var rotationSpeed = World.Get(entity); - float change = rotationSpeed.Speed * delta.SecFloat(); + float change = rotationSpeed.Speed * deltaSec; World.Set(entity, currentRotation with { Value = currentRotation.Value + change }); - var newSpeed = rotationSpeed.Speed - (rotationSpeed.Drag * delta.SecFloat()); - if (Math.Sign(newSpeed) != Math.Sign(rotationSpeed.Speed)) + float remainingRotation = rotationSpeed.Amount - change; + float newSpeed = rotationSpeed.Speed - (rotationSpeed.Drag * deltaSec); + if (Math.Sign(newSpeed) != Math.Sign(rotationSpeed.Speed) || remainingRotation <= 0) { World.Remove(entity); } @@ -34,7 +36,7 @@ public class RotationSystem : MoonTools.ECS.System World.Set(entity, rotationSpeed with { Speed = newSpeed, - Amount = rotationSpeed.Amount - change, + Amount = remainingRotation, }); } }