From c2909091ed7e6d4b82113b7e4b04312da707c113 Mon Sep 17 00:00:00 2001 From: Nik Cherednik Date: Tue, 19 Sep 2023 15:19:57 +0300 Subject: [PATCH] refactor: ecs & di & composition root - Needs refactoring yet & docs update --- docs/Workflow.md | 4 + src/Apps/GameDesktop/Content/Content.mgcb | 3 + src/Apps/GameDesktop/Game.cs | 45 +++++----- src/Apps/GameDesktop/GameCompositionRoot.cs | 12 ++- src/Apps/GameDesktop/GameDesktop.csproj | 1 + src/Apps/GameDesktop/Program.cs | 8 +- .../GameDesktop/RootFeatureCompositionRoot.cs | 84 +++++++++++++++++++ src/Libs/Features/Features.csproj | 4 + src/Libs/Features/PlayerMovementFeature.cs | 7 +- src/Libs/Features/RootFeature.cs | 18 ++++ .../Factories/AnimatedCharactersFactory.cs | 8 +- src/Libs/Systems/AnimatedMovementSystem.cs | 16 ++-- src/Libs/Systems/CreatePlayerEntitySystem.cs | 35 ++++---- src/Libs/Systems/InputSystem.cs | 24 ++++-- src/Libs/Systems/MovementSystem.cs | 25 ++++-- src/Libs/Systems/Systems.csproj | 1 + 16 files changed, 221 insertions(+), 74 deletions(-) create mode 100644 src/Apps/GameDesktop/RootFeatureCompositionRoot.cs create mode 100644 src/Libs/Features/RootFeature.cs diff --git a/docs/Workflow.md b/docs/Workflow.md index e952e5a..2e63df4 100644 --- a/docs/Workflow.md +++ b/docs/Workflow.md @@ -11,6 +11,10 @@ - [Creating global `const`](#creating-global-const) - [State Machine](#state-machine) +## MonoGame Pipeline + +Don't forget to rebuild `.mgcb` file after changing files in it. + ## Makefile If something happens like commands don't run, variables not evaluated, but the file is correct 99%, diff --git a/src/Apps/GameDesktop/Content/Content.mgcb b/src/Apps/GameDesktop/Content/Content.mgcb index ddc4c36..c2bdd81 100644 --- a/src/Apps/GameDesktop/Content/Content.mgcb +++ b/src/Apps/GameDesktop/Content/Content.mgcb @@ -13,3 +13,6 @@ #---------------------------------- Content ---------------------------------# +#begin SpriteSheets/Player.aseprite +/copy:SpriteSheets/Player.aseprite + diff --git a/src/Apps/GameDesktop/Game.cs b/src/Apps/GameDesktop/Game.cs index df8c686..4f42681 100644 --- a/src/Apps/GameDesktop/Game.cs +++ b/src/Apps/GameDesktop/Game.cs @@ -1,22 +1,29 @@ -using Microsoft.Xna.Framework; +using Features; +using LightInject; +using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; -using Serilog.Core; +using Serilog; namespace GameDesktop; public class Game : Microsoft.Xna.Framework.Game { - private readonly Logger _logger; + private readonly ILogger _logger; private readonly Contexts _contexts; + private readonly IServiceContainer _container; + + private Entitas.Extended.Feature _rootFeature; + private SpriteBatch _spriteBatch; - public SpriteBatch SpriteBatch { get; private set; } public Game( - Logger logger, - Contexts contexts) + ILogger logger, + Contexts contexts, + IServiceContainer container) { _logger = logger; _contexts = contexts; + _container = container; _logger.ForContext().Verbose("ctor"); } @@ -26,10 +33,11 @@ protected override void Initialize() _logger.ForContext().Verbose($"Initialize(): start; available {GraphicsDevice}"); _logger.ForContext().Verbose("SpriteBatch initialization..."); - SpriteBatch = new SpriteBatch(GraphicsDevice); + _spriteBatch = new SpriteBatch(GraphicsDevice); _logger.ForContext().Verbose("SpriteBatch initialized"); + base.Initialize(); _logger.ForContext().Verbose("Initialize(): end"); @@ -39,11 +47,14 @@ protected override void LoadContent() { _logger.ForContext().Verbose("LoadContent(): start"); - // TODO: DI with ECS? - // TODO: Projects management (external in Libs/External?) + _container.Register(_ => _spriteBatch); + _container.RegisterFrom(); + + _rootFeature = _container.GetInstance(); + // TODO: Logging with game flags (like LOG_MOVEMENT, etc)? // TODO: Error handling - // _gameFeature.Initialize(); + _rootFeature.Initialize(); _logger.ForContext().Verbose("LoadContent(): end"); } @@ -65,31 +76,25 @@ protected override void EndRun() _logger.ForContext().Verbose("Ended"); } - private void FixedUpdate(GameTime fixedGameTime) - { - // _gameFeature.FixedExecute(fixedGameTime); - } + private void FixedUpdate(GameTime fixedGameTime) => _rootFeature.FixedExecute(fixedGameTime); protected override void Update(GameTime gameTime) { FixedUpdate(gameTime); base.Update(gameTime); - // _gameFeature.Execute(gameTime); + _rootFeature.Execute(gameTime); LateUpdate(gameTime); } - private void LateUpdate(GameTime gameTime) - { - // _gameFeature.LateExecute(gameTime); - } + private void LateUpdate(GameTime gameTime) => _rootFeature.LateExecute(gameTime); protected override void Draw(GameTime gameTime) { GraphicsDevice.Clear(Color.CornflowerBlue); - // _gameFeature.Draw(gameTime, _spriteBatch); + _rootFeature.Draw(gameTime, _spriteBatch); base.Draw(gameTime); } diff --git a/src/Apps/GameDesktop/GameCompositionRoot.cs b/src/Apps/GameDesktop/GameCompositionRoot.cs index f05323b..b0c57eb 100644 --- a/src/Apps/GameDesktop/GameCompositionRoot.cs +++ b/src/Apps/GameDesktop/GameCompositionRoot.cs @@ -1,6 +1,7 @@ -using LightInject; +using Features; +using LightInject; using Microsoft.Xna.Framework; -using Serilog.Core; +using Serilog; namespace GameDesktop; @@ -18,11 +19,14 @@ private void RegisterGameServices(IServiceRegistry serviceRegistry) { serviceRegistry.Register(_ => Contexts.sharedInstance); + serviceRegistry.RegisterFrom(); + serviceRegistry.Register(factory => { Game game = new( - factory.GetInstance(), - factory.GetInstance() + factory.GetInstance(), + factory.GetInstance(), + factory.GetInstance() ) { IsMouseVisible = IsMouseVisible, Content = { RootDirectory = ContentRootDirectory, } }; // Hack. Resolving cycle dependency issue (fundamental architecture) diff --git a/src/Apps/GameDesktop/GameDesktop.csproj b/src/Apps/GameDesktop/GameDesktop.csproj index c3146e0..0ce582c 100644 --- a/src/Apps/GameDesktop/GameDesktop.csproj +++ b/src/Apps/GameDesktop/GameDesktop.csproj @@ -59,6 +59,7 @@ + diff --git a/src/Apps/GameDesktop/Program.cs b/src/Apps/GameDesktop/Program.cs index 3303a39..ddd4513 100644 --- a/src/Apps/GameDesktop/Program.cs +++ b/src/Apps/GameDesktop/Program.cs @@ -2,6 +2,7 @@ using GameDesktop; using LightInject; using Microsoft.Extensions.Configuration; +using Serilog; using Serilog.Core; IConfigurationRoot configuration = ConfigurationFactory.Create(); @@ -18,8 +19,9 @@ }; using ServiceContainer container = new(containerOptions); - container.Register(_ => configuration, new PerContainerLifetime()); - container.Register(_ => logger, new PerContainerLifetime()); + container.Register(_ => container); + container.Register(_ => configuration, new PerContainerLifetime()); + container.Register(_ => logger, new PerContainerLifetime()); container.RegisterFrom(); @@ -28,5 +30,5 @@ } catch (Exception e) { - logger.Error(e.ToString()); + logger.ForContext().Fatal(e.ToString()); } diff --git a/src/Apps/GameDesktop/RootFeatureCompositionRoot.cs b/src/Apps/GameDesktop/RootFeatureCompositionRoot.cs new file mode 100644 index 0000000..11a2e10 --- /dev/null +++ b/src/Apps/GameDesktop/RootFeatureCompositionRoot.cs @@ -0,0 +1,84 @@ +using Components.Sprites; +using Entitas; +using Features; +using LightInject; +using Microsoft.Xna.Framework.Graphics; +using MonoGame.Aseprite.Sprites; +using Serilog; +using Services; +using Services.Factories; +using Services.Input; +using Services.Movement; +using Stateless; +using Systems; + +namespace GameDesktop; + +public class RootFeatureCompositionRoot : ICompositionRoot +{ + public void Compose(IServiceRegistry serviceRegistry) + { + serviceRegistry.Register(new PerContainerLifetime()); + + serviceRegistry.Register(new PerContainerLifetime()); + + serviceRegistry.Register(factory => + { + Contexts contexts = factory.GetInstance(); + IAllOfMatcher inputMovableMatcher = GameMatcher.AllOf(GameMatcher.Transform, + GameMatcher.Movable, + GameMatcher.Player); + IGroup inputMovableGroup = contexts.game.GetGroup(inputMovableMatcher); + + var inputScanner = factory.GetInstance(); + var logger = factory.GetInstance(); + + return new InputSystem(inputScanner, inputMovableGroup, logger); + }, new PerContainerLifetime()); + + serviceRegistry.Register(factory => + { + Contexts contexts = factory.GetInstance(); + IAllOfMatcher movableMatcher = GameMatcher.AllOf(GameMatcher.Transform, + GameMatcher.Movable); + IGroup movableGroup = contexts.game.GetGroup(movableMatcher); + + var movement = factory.GetInstance(); + var logger = factory.GetInstance(); + + return new MovementSystem(movement, movableGroup, logger); + }, new PerContainerLifetime()); + + serviceRegistry.Register(factory => + { + GraphicsDevice graphicsDevice = factory.GetInstance().GraphicsDevice; + SpriteSheet spriteSheet = + AnimatedCharactersFactory.LoadSpriteSheet(graphicsDevice, "Content/SpriteSheets/Player.aseprite"); + + var idleDirAnimations = AnimatedCharactersFactory.CreateAnimations(spriteSheet, "Idle"); + var walkingDirAnimations = AnimatedCharactersFactory.CreateAnimations(spriteSheet, "Walking"); + + return new AnimatedMovementComponent( + new StateMachine(PlayerState.Idle), + idleDirAnimations, + walkingDirAnimations); + }); + + serviceRegistry.Register(factory => new CreatePlayerEntitySystem(factory.GetInstance(), + factory.GetInstance())); + + serviceRegistry.Register(factory => + { + Contexts contexts = factory.GetInstance(); + IAllOfMatcher animatedMovableMatcher = GameMatcher.AllOf(GameMatcher.Movable, + GameMatcher.AnimatedMovement); + IGroup animatedMovableGroup = contexts.game.GetGroup(animatedMovableMatcher); + + var logger = factory.GetInstance(); + + return new AnimatedMovementSystem(animatedMovableGroup, logger); + }); + + serviceRegistry.Register(); + } +} diff --git a/src/Libs/Features/Features.csproj b/src/Libs/Features/Features.csproj index 0a8b0e4..f7940ab 100644 --- a/src/Libs/Features/Features.csproj +++ b/src/Libs/Features/Features.csproj @@ -11,4 +11,8 @@ + + + + diff --git a/src/Libs/Features/PlayerMovementFeature.cs b/src/Libs/Features/PlayerMovementFeature.cs index 0194f72..cb0d9ca 100644 --- a/src/Libs/Features/PlayerMovementFeature.cs +++ b/src/Libs/Features/PlayerMovementFeature.cs @@ -1,9 +1,4 @@ -using Entitas; -using Services.Input; -using Services.Movement; -using Systems; -using Systems.Input; -using Systems.Sprites; +using Systems; namespace Features; diff --git a/src/Libs/Features/RootFeature.cs b/src/Libs/Features/RootFeature.cs new file mode 100644 index 0000000..8b4ed17 --- /dev/null +++ b/src/Libs/Features/RootFeature.cs @@ -0,0 +1,18 @@ +using Systems; + +namespace Features; + +public sealed class RootFeature : Entitas.Extended.Feature +{ + public RootFeature(InputSystem inputSystem, + MovementSystem movementSystem, + CreatePlayerEntitySystem createPlayerEntitySystem, + AnimatedMovementSystem animatedMovementSystem + ) + { + Add(inputSystem); + Add(movementSystem); + Add(createPlayerEntitySystem); + Add(animatedMovementSystem); + } +} diff --git a/src/Libs/Services/Factories/AnimatedCharactersFactory.cs b/src/Libs/Services/Factories/AnimatedCharactersFactory.cs index fff0cf0..6fd9ecb 100644 --- a/src/Libs/Services/Factories/AnimatedCharactersFactory.cs +++ b/src/Libs/Services/Factories/AnimatedCharactersFactory.cs @@ -6,9 +6,9 @@ namespace Services.Factories; -public class AnimatedCharactersFactory +public static class AnimatedCharactersFactory { - private readonly IReadOnlyList _directions = + private static readonly IReadOnlyList Directions = new[] { RadDir.Right, RadDir.Down, RadDir.Left, RadDir.Up }; public static SpriteSheet LoadSpriteSheet(GraphicsDevice graphicsDevice, string path) @@ -41,10 +41,10 @@ private static AnimatedSprite CreateAnimation(SpriteSheet spriteSheet, string ac return animatedSprite; } - public Dictionary CreateAnimations(SpriteSheet spriteSheet, string action) + public static Dictionary CreateAnimations(SpriteSheet spriteSheet, string action) { Dictionary dictionary = - _directions.ToDictionary(dir => dir, dir => CreateAnimation(spriteSheet, action, dir)); + Directions.ToDictionary(dir => dir, dir => CreateAnimation(spriteSheet, action, dir)); // Temp hack dictionary.Add(RadDir.DownLeft, dictionary[RadDir.Left]); diff --git a/src/Libs/Systems/AnimatedMovementSystem.cs b/src/Libs/Systems/AnimatedMovementSystem.cs index a98621e..657972e 100644 --- a/src/Libs/Systems/AnimatedMovementSystem.cs +++ b/src/Libs/Systems/AnimatedMovementSystem.cs @@ -3,17 +3,20 @@ using Entitas.Extended; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; +using Serilog; using IExecuteSystem = Entitas.Extended.IExecuteSystem; -namespace Systems.Sprites; +namespace Systems; public class AnimatedMovementSystem : IExecuteSystem, IDrawSystem { private readonly IGroup _group; + private readonly ILogger _logger; - public AnimatedMovementSystem(IGroup group) + public AnimatedMovementSystem(IGroup group, ILogger logger) { _group = group; + _logger = logger; } public void Execute(GameTime gameTime) @@ -22,9 +25,9 @@ public void Execute(GameTime gameTime) foreach (GameEntity e in entities) { Vector2 velocity = e.transform.Velocity; - AnimatedMovementComponent movementAnimatedSprites = - (AnimatedMovementComponent)e.GetComponents().First(component => component is AnimatedMovementComponent); + AnimatedMovementComponent movementAnimatedSprites = e.animatedMovement; + // TODO: refactor and put it in service system if (velocity.Equals(Vector2.Zero)) { movementAnimatedSprites.StateMachine.Fire(PlayerTrigger.Stop); @@ -42,12 +45,11 @@ public void Draw(GameTime gameTime, SpriteBatch spriteBatch) { spriteBatch.Begin(samplerState: SamplerState.PointClamp); - GameEntity[]? entities = _group.GetEntities(); + GameEntity[] entities = _group.GetEntities(); foreach (GameEntity e in entities) { Vector2 position = e.transform.Position; - AnimatedMovementComponent? movementAnimatedSprites = - (AnimatedMovementComponent)e.GetComponents().First(component => component is AnimatedMovementComponent); + AnimatedMovementComponent movementAnimatedSprites = e.animatedMovement; movementAnimatedSprites.PlayingAnimation.Draw(spriteBatch, position); } diff --git a/src/Libs/Systems/CreatePlayerEntitySystem.cs b/src/Libs/Systems/CreatePlayerEntitySystem.cs index 6e69e6e..4ed3e3f 100644 --- a/src/Libs/Systems/CreatePlayerEntitySystem.cs +++ b/src/Libs/Systems/CreatePlayerEntitySystem.cs @@ -1,37 +1,36 @@ -using Entitas; +using Components.Sprites; +using Components.World; +using Entitas; +using Microsoft.Xna.Framework; namespace Systems; public class CreatePlayerEntitySystem : IInitializeSystem { private readonly Contexts _contexts; + private readonly AnimatedMovementComponent _animatedMovementComponent; + private int _componentsCount = 0; - public CreatePlayerEntitySystem(Contexts contexts) + public CreatePlayerEntitySystem(Contexts contexts, AnimatedMovementComponent animatedMovementComponent) { _contexts = contexts; + _animatedMovementComponent = animatedMovementComponent; } public void Initialize() { - GameEntity? e = _contexts.game.CreateEntity(); + GameEntity e = _contexts.game.CreateEntity(); + + e.AddComponent(_componentsCount++, _animatedMovementComponent); + // e.AddComponent(_componentsCount++, new PlayerComponent()); + // e.AddComponent(_componentsCount++, new MovableComponent()); + // e.AddComponent(_componentsCount++, new TransformComponent()); + // e.AddComponent(_componentsCount++, new RectangleCollisionComponent()); e.isPlayer = true; e.isMovable = true; - // FIXME: Trouble loading. - // SpriteSheet spriteSheet = - // AnimatedCharactersFactory.LoadSpriteSheet(_graphicsDevice, "Content/SpriteSheets/Player.aseprite"); - // AnimatedCharactersFactory animatedCharactersFactory = new(); - // - // StateMachine stateMachine = new(PlayerState.Idle); - // - // AnimatedMovementComponent animatedMovementComponent = new( - // stateMachine, - // animatedCharactersFactory.CreateAnimations(spriteSheet, "Standing"), - // animatedCharactersFactory.CreateAnimations(spriteSheet, "Walking")); - // e.AddComponent(0, animatedMovementComponent); - // - // e.AddTransform(new Vector2(), new Vector2()); - // e.AddRectangleCollision(new Rectangle(0, 0, 16, 16)); + e.AddTransform(new Vector2(), new Vector2()); + e.AddRectangleCollision(new Rectangle(0, 0, 16, 16)); } } diff --git a/src/Libs/Systems/InputSystem.cs b/src/Libs/Systems/InputSystem.cs index 784a4d7..59d154f 100644 --- a/src/Libs/Systems/InputSystem.cs +++ b/src/Libs/Systems/InputSystem.cs @@ -1,30 +1,42 @@ using Entitas; using Microsoft.Xna.Framework; +using Serilog; using Services.Input; using IExecuteSystem = Entitas.Extended.IExecuteSystem; -namespace Systems.Input; +namespace Systems; [Input] public class InputSystem : IExecuteSystem { private readonly IInputScanner _inputScanner; private readonly IGroup _group; + private readonly ILogger _logger; - public InputSystem(IInputScanner inputScanner, IGroup group) + public InputSystem(IInputScanner inputScanner, IGroup group, ILogger logger) { _inputScanner = inputScanner; _group = group; + _logger = logger; } public void Execute(GameTime gameTime) { - Vector2 direction = _inputScanner.GetDirection(); + try + { + Vector2 direction = _inputScanner.GetDirection(); - GameEntity[]? entities = _group.GetEntities(); - foreach (GameEntity e in entities) + GameEntity[] entities = _group.GetEntities(); + foreach (GameEntity e in entities) + { + e.transform.Velocity = direction; + } + } + catch (Exception e) { - e.transform.Velocity = direction; + _logger.ForContext().Fatal(e.ToString()); + + throw new Exception(e.Message); } } } diff --git a/src/Libs/Systems/MovementSystem.cs b/src/Libs/Systems/MovementSystem.cs index ab699d3..6438a01 100644 --- a/src/Libs/Systems/MovementSystem.cs +++ b/src/Libs/Systems/MovementSystem.cs @@ -1,5 +1,6 @@ using Entitas; using Microsoft.Xna.Framework; +using Serilog; using Services; using IExecuteSystem = Entitas.Extended.IExecuteSystem; @@ -9,24 +10,36 @@ public class MovementSystem : IExecuteSystem { private readonly IGroup _group; private readonly IMovement _movement; + private readonly ILogger _logger; - public MovementSystem(IGroup group, IMovement movement) + public MovementSystem(IMovement movement, IGroup group, ILogger logger) { _group = group; _movement = movement; + _logger = logger; } public void Execute(GameTime gameTime) { - GameEntity[]? entities = _group.GetEntities(); - foreach (GameEntity e in entities) + try { - if (e.transform.Velocity.Equals(Vector2.Zero)) + GameEntity[]? entities = _group.GetEntities(); + foreach (GameEntity e in entities) { - continue; + if (e.transform.Velocity.Equals(Vector2.Zero)) + { + continue; + } + + e.transform.Position = _movement.Move(e.transform.Position, e.transform.Velocity); + _logger.ForContext().Verbose(e.transform.Position.ToString()); } + } + catch (Exception e) + { + _logger.ForContext().Fatal(e.ToString()); - e.transform.Position = _movement.Move(e.transform.Position, e.transform.Velocity); + throw new Exception(e.Message); } } } diff --git a/src/Libs/Systems/Systems.csproj b/src/Libs/Systems/Systems.csproj index b13d4b9..05bb65b 100644 --- a/src/Libs/Systems/Systems.csproj +++ b/src/Libs/Systems/Systems.csproj @@ -13,6 +13,7 @@ +