Add some more simple systems.

Generate Assets.cs with generate-asset-class.js
Currently only processes Images/ in the .mgcb
This commit is contained in:
Sage Vaillancourt 2025-04-24 12:57:35 -04:00
parent 8651eb011e
commit 1ff7806c7a
12 changed files with 208 additions and 12 deletions

3
Makefile Normal file
View File

@ -0,0 +1,3 @@
generate-assets-class:
test -f MonoGameBlank2dStartKit/MonoGameBlank2dStartKit.Core/Content/MonoGameBlank2dStartKit.mgcb || exit 1
node ./generate-asset-class.js MonoGameBlank2dStartKit/MonoGameBlank2dStartKit.Core/Content/MonoGameBlank2dStartKit.mgcb > MonoGameBlank2dStartKit/MonoGameBlank2dStartKit.Core/Content/Assets.cs

View File

@ -3,4 +3,5 @@
<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_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_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> <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>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASpriteBatch_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F607c842ed163400ca987bebdb007e915137000_003Fa6_003F45905159_003FSpriteBatch_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary> <s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ASpriteBatch_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003F607c842ed163400ca987bebdb007e915137000_003Fa6_003F45905159_003FSpriteBatch_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003ATimeSpan_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003F_002Econfig_003FJetBrains_003FRider2025_002E1_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fbf07be36dfaa4f47b843d44a6617c8b9d19e00_003F2e_003F2f4498f0_003FTimeSpan_002Ecs/@EntryIndexedValue">ForceIncluded</s:String></wpf:ResourceDictionary>

View File

@ -1,9 +1,34 @@
using System;
using System.Numerics; using System.Numerics;
using Microsoft.Xna.Framework.Graphics;
using MonoGameBlank2dStartKit.Core.Content; using MonoGameBlank2dStartKit.Core.Content;
namespace MonoGameBlank2dStartKit.Core; namespace MonoGameBlank2dStartKit.Core;
public readonly record struct Position(Vector2 Vector2); public readonly record struct Position(Vector2 Vector2)
public readonly record struct Velocity(Vector2 Vector2); {
public Position(float x, float y) : this(new Vector2(x, y)) { }
}
public readonly record struct Velocity(Vector2 Vector2)
{
public Velocity(float x, float y) : this(new Vector2(x, y)) { }
}
public readonly record struct Mass(int Value);
public readonly record struct RotationSpeed(float Speed, float Amount = float.MaxValue, float Drag = 0)
{
}
public readonly record struct Rotation(float Value, Vector2 Origin)
{
public Rotation(float value, Texture2D texture) : this(value, CenterOfTexture(texture)) { }
private static Vector2 CenterOfTexture(Texture2D texture)
{
return new Vector2((float) texture.Width / 2, (float) texture.Height / 2);
}
}
public readonly record struct Image(ImageId ImageId); public readonly record struct Image(ImageId ImageId);

View File

@ -1,3 +1,4 @@
using System; using System;
using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Graphics;
@ -13,8 +14,7 @@ public class Assets
var allImageIds = Enum.GetValues<ImageId>(); var allImageIds = Enum.GetValues<ImageId>();
_images = _images =
[ [
// TODO: Auto-generate this initialization content.Load<Texture2D>("Images/ball"),
content.Load<Texture2D>("Images/ball")
]; ];
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.2 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

View File

@ -19,6 +19,8 @@ namespace MonoGameBlank2dStartKit.Core
public class MonoGameBlank2dStartKitGame : Game public class MonoGameBlank2dStartKitGame : Game
{ {
private TextureDrawSystem TextureDrawSystem { get; set; } private TextureDrawSystem TextureDrawSystem { get; set; }
private VelocitySystem VelocitySystem { get; set; }
private RotationSystem RotationSystem { get; set; }
// Resources for drawing. // Resources for drawing.
private readonly GraphicsDeviceManager graphicsDeviceManager; private readonly GraphicsDeviceManager graphicsDeviceManager;
@ -90,7 +92,9 @@ namespace MonoGameBlank2dStartKit.Core
_assets = new Assets(Content); _assets = new Assets(Content);
TextureDrawSystem = new TextureDrawSystem(World, _assets, _spriteBatch); TextureDrawSystem = new TextureDrawSystem(World, _assets, _spriteBatch);
Scenarios.Basic(World); VelocitySystem = new VelocitySystem(World);
RotationSystem = new RotationSystem(World);
Scenarios.Basic(World, _assets);
base.LoadContent(); base.LoadContent();
} }
@ -107,7 +111,9 @@ namespace MonoGameBlank2dStartKit.Core
|| Keyboard.GetState().IsKeyDown(Keys.Escape)) || Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit(); Exit();
// TODO: Add your update logic here var elapsedGameTime = gameTime.ElapsedGameTime;
VelocitySystem.Update(elapsedGameTime);
RotationSystem.Update(elapsedGameTime);
base.Update(gameTime); base.Update(gameTime);
} }

View File

@ -6,10 +6,13 @@ namespace MonoGameBlank2dStartKit.Core;
public static class Scenarios public static class Scenarios
{ {
public static void Basic(World world) public static void Basic(World world, Assets assets)
{ {
var ball = world.CreateEntity(); var ball = world.CreateEntity();
world.Set(ball, new Position(new Vector2(10, 10))); world.Set(ball, new Position(new Vector2(10, 10)));
world.Set(ball, new Image(ImageId.Ball)); world.Set(ball, new Image(ImageId.Ball));
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));
} }
} }

View File

@ -0,0 +1,42 @@
using System;
using System.Numerics;
using MonoGameBlank2dStartKit.Core.Utils;
using MoonTools.ECS;
namespace MonoGameBlank2dStartKit.Core.Systems;
public class RotationSystem : MoonTools.ECS.System
{
private readonly Filter _filter;
public RotationSystem(World world) : base(world)
{
// Doesn't filter on Rotation, because it *should* blow up if RSpeed exists without Rotation.
_filter = FilterBuilder.Include<RotationSpeed>().Build();
}
public override void Update(TimeSpan delta)
{
foreach (var entity in _filter.Entities)
{
var currentRotation = World.Get<Rotation>(entity);
var rotationSpeed = World.Get<RotationSpeed>(entity);
float change = rotationSpeed.Speed * delta.SecFloat();
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))
{
World.Remove<RotationSpeed>(entity);
}
else
{
World.Set(entity, rotationSpeed with
{
Speed = newSpeed,
Amount = rotationSpeed.Amount - change,
});
}
}
}
}

View File

@ -9,23 +9,33 @@ namespace MonoGameBlank2dStartKit.Core.Systems;
public class TextureDrawSystem : MoonTools.ECS.System public class TextureDrawSystem : MoonTools.ECS.System
{ {
private readonly Assets _assets; private readonly Assets _assets;
private readonly Filter _filter;
private readonly SpriteBatch _spriteBatch; private readonly SpriteBatch _spriteBatch;
private readonly Filter _unrotatedFilter;
private readonly Filter _rotatedFilter;
public TextureDrawSystem(World world, Assets assets, SpriteBatch spriteBatch) : base(world) public TextureDrawSystem(World world, Assets assets, SpriteBatch spriteBatch) : base(world)
{ {
_assets = assets; _assets = assets;
_spriteBatch = spriteBatch; _spriteBatch = spriteBatch;
_filter = FilterBuilder.Include<Image>().Include<Position>().Build(); _unrotatedFilter = FilterBuilder.Include<Image>().Include<Position>().Exclude<Rotation>().Build();
_rotatedFilter = FilterBuilder.Include<Image>().Include<Position>().Include<Rotation>().Build();
} }
public override void Update(TimeSpan delta) public override void Update(TimeSpan delta)
{ {
foreach (var entity in _filter.Entities) foreach (var entity in _unrotatedFilter.Entities)
{ {
var texture = _assets.GetTexture2D(World.Get<Image>(entity).ImageId); var texture = _assets.GetTexture2D(World.Get<Image>(entity).ImageId);
var position = World.Get<Position>(entity).Vector2; var position = World.Get<Position>(entity).Vector2;
_spriteBatch.Draw(texture, position, Color.White); _spriteBatch.Draw(texture, position, Color.White);
} }
foreach (var entity in _rotatedFilter.Entities)
{
var texture = _assets.GetTexture2D(World.Get<Image>(entity).ImageId);
var position = World.Get<Position>(entity).Vector2;
var rotation = World.Get<Rotation>(entity);
_spriteBatch.Draw(texture, position, null, Color.White, rotation.Value, rotation.Origin, new Vector2(1, 1), SpriteEffects.None, 1);
}
} }
} }

View File

@ -0,0 +1,30 @@
using System;
using System.Numerics;
using MonoGameBlank2dStartKit.Core.Utils;
using MoonTools.ECS;
namespace MonoGameBlank2dStartKit.Core.Systems;
public class VelocitySystem : MoonTools.ECS.System
{
private readonly Filter _filter;
public VelocitySystem(World world) : base(world)
{
_filter = FilterBuilder.Include<Position>().Include<Velocity>().Build();
}
public override void Update(TimeSpan delta)
{
float deltaSec = delta.SecFloat();
foreach (var entity in _filter.Entities)
{
var currentPosition = World.Get<Position>(entity).Vector2;
var velocity = World.Get<Velocity>(entity).Vector2;
World.Set(entity, new Position(
currentPosition.X + (velocity.X * deltaSec),
currentPosition.Y + (velocity.Y * deltaSec)
));
}
}
}

View File

@ -0,0 +1,11 @@
using System;
namespace MonoGameBlank2dStartKit.Core.Utils;
public static class TimeSpanExtensions
{
public static float SecFloat(this TimeSpan span)
{
return (float)span.Ticks / TimeSpan.TicksPerSecond;
}
}

65
generate-asset-class.js Normal file
View File

@ -0,0 +1,65 @@
const fs = require('fs')
const mgcbFile = process.argv[2]
if (!mgcbFile) {
return 1
}
const fileContents = fs.readFileSync(mgcbFile, 'utf8')
/** @type string[] */
const lines = fileContents.split('\n')
/** @type {Object.<string, string[]>} */
const types = {}
lines.forEach(line => {
const prefix = '#begin '
if (!line.startsWith(prefix)) {
return
}
line = line.substring(prefix.length)
const [type, name] = line.split('/')
types[type] ??= []
types[type].push(name)
})
let imageIdTextureLoads = ''
let imageIdEnumString = ''
types['Images'].forEach(image => {
const [name, ext] = image.split('.')
imageIdTextureLoads += '\n '
imageIdTextureLoads += `content.Load<Texture2D>("Images/${name}"),`
imageIdEnumString += '\n '
imageIdEnumString += name[0].toUpperCase() + name.substring(1)
})
console.log(`
using System;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
namespace MonoGameBlank2dStartKit.Core.Content;
public class Assets
{
private readonly Texture2D[] _images;
public Assets(ContentManager content)
{
var allImageIds = Enum.GetValues<ImageId>();
_images =
[${imageIdTextureLoads}
];
}
public Texture2D GetTexture2D(ImageId imageId)
{
return _images[(int)imageId];
}
}
public enum ImageId
{${imageIdEnumString}
}`)