Add a simple EasingSystem

This commit is contained in:
Sage Vaillancourt 2025-04-24 14:34:25 -04:00
parent 1ff7806c7a
commit 61625a4add
6 changed files with 164 additions and 10 deletions

View File

@ -1,5 +1,6 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AContentManager_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F607c842ed163400ca987bebdb007e915137000_003F2c_003F08ef808b_003FContentManager_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ADictionary_00602_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F65134d3e6ebe417ebdb98f66cb6df17eb528_003Fd0_003F1dd51100_003FDictionary_00602_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AGameTime_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F607c842ed163400ca987bebdb007e915137000_003F19_003F2ed69731_003FGameTime_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AGame_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F607c842ed163400ca987bebdb007e915137000_003Fe8_003Fc288a217_003FGame_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003APath_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fbf07be36dfaa4f47b843d44a6617c8b9d19e00_003Fab_003Ffafcbf34_003FPath_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>

View File

@ -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);
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();

View File

@ -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);
}

View File

@ -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)));
}
}

View File

@ -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<ReverseSpanEnumerator<Entity>, float> ProcessEntities);
private readonly EasingFilter[] _easingFilters;
public EasingSystem(World world) : base(world)
{
_easingFilters =
[
BuildEasingFilter<MoveLinearly>(p => p),
BuildEasingFilter<CubicEaseIn>(p => p * p * p),
BuildEasingFilter<CubicEaseOut>(p =>
{
float f = p - 1;
return (f * f * f) + 1;
}),
BuildEasingFilter<CubicEaseInOut>(p =>
{
if (p < 0.5)
{
return 4 * p * p * p;
}
float f = 2 * p - 2;
return 0.5f * f * f * f + 1;
}),
BuildEasingFilter<QuinticEaseIn>(p => p * p * p * p * p),
BuildEasingFilter<QuinticEaseOut>(p =>
{
float f = p - 1;
return (f * f * f * f * f) + 1;
}),
];
}
private EasingFilter BuildEasingFilter<T>(Func<float, float> easingFunc) where T : unmanaged, IMovement
{
var filter = FilterBuilder.Include<T>().Build();
return new EasingFilter(filter, (entities, deltaSec) =>
{
foreach (var entity in entities)
{
var currentMovement = World.Get<T>(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<T>(
float deltaSec,
Entity entity,
T currentMovement,
Func<float, float> 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<T>(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 });
}
}
}

View File

@ -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<Rotation>(entity);
var rotationSpeed = World.Get<RotationSpeed>(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<RotationSpeed>(entity);
}
@ -34,7 +36,7 @@ public class RotationSystem : MoonTools.ECS.System
World.Set(entity, rotationSpeed with
{
Speed = newSpeed,
Amount = rotationSpeed.Amount - change,
Amount = remainingRotation,
});
}
}