chore: updated engine to use MonoGame integrations

This commit is contained in:
Syntriax 2025-06-01 18:37:59 +03:00
parent 1ff26f5be4
commit dcee3a0ff0
22 changed files with 479 additions and 320 deletions

2
Engine

@ -1 +1 @@
Subproject commit 7a3202a053b6fd7c9adf9bc210bf8026f3412328
Subproject commit 8d49fb467cda79187a1a5677f7d594af4dbaef6e

View File

@ -1,110 +0,0 @@
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework.Input;
using Syntriax.Engine.Core;
using Syntriax.Engine.Systems.Input;
namespace Pong.Platforms.Desktop;
public class KeyboardInputsBehaviour : Behaviour, IButtonInputs<Keys>
{
public Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments> OnAnyButtonPressed { get; } = new();
public Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments> OnAnyButtonReleased { get; } = new();
private readonly Dictionary<Keys, Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments>> OnPressed = new(256);
private readonly Dictionary<Keys, Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments>> OnReleased = new(256);
private int cachePressedCurrentlyCount = 0;
private readonly Keys[] cachePressedCurrently = new Keys[256];
private int cachePressedPreviouslyCount = 0;
private readonly Keys[] cachePressedPreviously = new Keys[256];
public void RegisterOnPress(Keys key, Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments>.EventHandler callback)
{
if (!OnPressed.TryGetValue(key, out Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments>? delegateCallback))
{
delegateCallback = new();
OnPressed.Add(key, delegateCallback);
}
delegateCallback.AddListener(callback);
}
public void UnregisterOnPress(Keys key, Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments>.EventHandler callback)
{
if (OnPressed.TryGetValue(key, out Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments>? delegateCallback))
delegateCallback.RemoveListener(callback);
}
public void RegisterOnRelease(Keys key, Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments>.EventHandler callback)
{
if (!OnReleased.TryGetValue(key, out Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments>? delegateCallback))
{
delegateCallback = new();
OnReleased.Add(key, delegateCallback);
}
delegateCallback.AddListener(callback);
}
public void UnregisterOnRelease(Keys key, Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments>.EventHandler callback)
{
if (OnReleased.TryGetValue(key, out Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments>? delegateCallback))
delegateCallback.RemoveListener(callback);
}
protected override void OnUpdate()
{
KeyboardState keyboardState = Keyboard.GetState();
keyboardState.GetPressedKeys(cachePressedCurrently);
cachePressedCurrentlyCount = keyboardState.GetPressedKeyCount();
for (int i = 0; i < cachePressedCurrentlyCount; i++)
{
Keys currentlyPressedKey = cachePressedCurrently[i];
if (WasPressed(currentlyPressedKey))
continue;
if (OnPressed.TryGetValue(currentlyPressedKey, out Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments>? callback))
callback?.Invoke(this, new(currentlyPressedKey));
OnAnyButtonPressed?.Invoke(this, new(currentlyPressedKey));
}
for (int i = 0; i < cachePressedPreviouslyCount; i++)
{
Keys previouslyPressedKey = cachePressedPreviously[i];
if (IsPressed(previouslyPressedKey))
continue;
if (OnReleased.TryGetValue(previouslyPressedKey, out Event<IButtonInputs<Keys>, IButtonInputs<Keys>.ButtonCallbackArguments>? callback))
callback?.Invoke(this, new(previouslyPressedKey));
OnAnyButtonReleased?.Invoke(this, new(previouslyPressedKey));
}
Array.Copy(cachePressedCurrently, cachePressedPreviously, cachePressedCurrentlyCount);
cachePressedPreviouslyCount = cachePressedCurrentlyCount;
}
public bool IsPressed(Keys key)
{
for (int i = 0; i < cachePressedCurrentlyCount; i++)
if (cachePressedCurrently[i] == key)
return true;
return false;
}
private bool WasPressed(Keys key)
{
for (int i = 0; i < cachePressedPreviouslyCount; i++)
if (cachePressedPreviously[i] == key)
return true;
return false;
}
}

View File

@ -1,7 +1,7 @@
using Pong.Platforms.Desktop;
using Syntriax.Engine.Core;
using Syntriax.Engine.Core;
using Syntriax.Engine.Integration.MonoGame;
Syntriax.Engine.Core.IUniverseObject universeObject = Syntriax.Engine.Core.Factory.UniverseObjectFactory.Instantiate().SetUniverseObject("Desktop HO");
IUniverseObject universeObject = Syntriax.Engine.Core.Factory.UniverseObjectFactory.Instantiate().SetUniverseObject("Desktop HO");
universeObject.BehaviourController.AddBehaviour<KeyboardInputsBehaviour>();
using var game = new Pong.GamePong(universeObject);
game.Run();

View File

@ -19,36 +19,104 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Desktop", "Platforms\Deskto
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Systems", "Engine\Engine.Systems\Engine.Systems.csproj", "{8863A1BA-2E83-419F-BACB-D4A4156EC71C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Engine.Integration", "Engine.Integration", "{9059393F-4073-9273-0EEC-2B1BA61B620B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Engine.Integration.MonoGame", "Engine\Engine.Integration\Engine.Integration.MonoGame\Engine.Integration.MonoGame.csproj", "{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{990CA10C-1EBB-4395-A43A-456B7029D8C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{990CA10C-1EBB-4395-A43A-456B7029D8C9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{990CA10C-1EBB-4395-A43A-456B7029D8C9}.Debug|x64.ActiveCfg = Debug|Any CPU
{990CA10C-1EBB-4395-A43A-456B7029D8C9}.Debug|x64.Build.0 = Debug|Any CPU
{990CA10C-1EBB-4395-A43A-456B7029D8C9}.Debug|x86.ActiveCfg = Debug|Any CPU
{990CA10C-1EBB-4395-A43A-456B7029D8C9}.Debug|x86.Build.0 = Debug|Any CPU
{990CA10C-1EBB-4395-A43A-456B7029D8C9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{990CA10C-1EBB-4395-A43A-456B7029D8C9}.Release|Any CPU.Build.0 = Release|Any CPU
{990CA10C-1EBB-4395-A43A-456B7029D8C9}.Release|x64.ActiveCfg = Release|Any CPU
{990CA10C-1EBB-4395-A43A-456B7029D8C9}.Release|x64.Build.0 = Release|Any CPU
{990CA10C-1EBB-4395-A43A-456B7029D8C9}.Release|x86.ActiveCfg = Release|Any CPU
{990CA10C-1EBB-4395-A43A-456B7029D8C9}.Release|x86.Build.0 = Release|Any CPU
{0D97F83C-B043-48B1-B155-7354C4E84FC0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0D97F83C-B043-48B1-B155-7354C4E84FC0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0D97F83C-B043-48B1-B155-7354C4E84FC0}.Debug|x64.ActiveCfg = Debug|Any CPU
{0D97F83C-B043-48B1-B155-7354C4E84FC0}.Debug|x64.Build.0 = Debug|Any CPU
{0D97F83C-B043-48B1-B155-7354C4E84FC0}.Debug|x86.ActiveCfg = Debug|Any CPU
{0D97F83C-B043-48B1-B155-7354C4E84FC0}.Debug|x86.Build.0 = Debug|Any CPU
{0D97F83C-B043-48B1-B155-7354C4E84FC0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0D97F83C-B043-48B1-B155-7354C4E84FC0}.Release|Any CPU.Build.0 = Release|Any CPU
{0D97F83C-B043-48B1-B155-7354C4E84FC0}.Release|x64.ActiveCfg = Release|Any CPU
{0D97F83C-B043-48B1-B155-7354C4E84FC0}.Release|x64.Build.0 = Release|Any CPU
{0D97F83C-B043-48B1-B155-7354C4E84FC0}.Release|x86.ActiveCfg = Release|Any CPU
{0D97F83C-B043-48B1-B155-7354C4E84FC0}.Release|x86.Build.0 = Release|Any CPU
{2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Debug|x64.ActiveCfg = Debug|Any CPU
{2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Debug|x64.Build.0 = Debug|Any CPU
{2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Debug|x86.ActiveCfg = Debug|Any CPU
{2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Debug|x86.Build.0 = Debug|Any CPU
{2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Release|Any CPU.Build.0 = Release|Any CPU
{2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Release|x64.ActiveCfg = Release|Any CPU
{2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Release|x64.Build.0 = Release|Any CPU
{2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Release|x86.ActiveCfg = Release|Any CPU
{2F6B1E26-1217-4EFD-874C-05ADEE4C7969}.Release|x86.Build.0 = Release|Any CPU
{590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Debug|Any CPU.Build.0 = Debug|Any CPU
{590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Debug|x64.ActiveCfg = Debug|Any CPU
{590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Debug|x64.Build.0 = Debug|Any CPU
{590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Debug|x86.ActiveCfg = Debug|Any CPU
{590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Debug|x86.Build.0 = Debug|Any CPU
{590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Release|Any CPU.ActiveCfg = Release|Any CPU
{590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Release|Any CPU.Build.0 = Release|Any CPU
{590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Release|x64.ActiveCfg = Release|Any CPU
{590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Release|x64.Build.0 = Release|Any CPU
{590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Release|x86.ActiveCfg = Release|Any CPU
{590E392E-9FB3-49FA-B4DA-6C8F7126FFE5}.Release|x86.Build.0 = Release|Any CPU
{2B627F66-5A61-4F69-B479-62EEAB603D01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{2B627F66-5A61-4F69-B479-62EEAB603D01}.Debug|Any CPU.Build.0 = Debug|Any CPU
{2B627F66-5A61-4F69-B479-62EEAB603D01}.Debug|x64.ActiveCfg = Debug|Any CPU
{2B627F66-5A61-4F69-B479-62EEAB603D01}.Debug|x64.Build.0 = Debug|Any CPU
{2B627F66-5A61-4F69-B479-62EEAB603D01}.Debug|x86.ActiveCfg = Debug|Any CPU
{2B627F66-5A61-4F69-B479-62EEAB603D01}.Debug|x86.Build.0 = Debug|Any CPU
{2B627F66-5A61-4F69-B479-62EEAB603D01}.Release|Any CPU.ActiveCfg = Release|Any CPU
{2B627F66-5A61-4F69-B479-62EEAB603D01}.Release|Any CPU.Build.0 = Release|Any CPU
{2B627F66-5A61-4F69-B479-62EEAB603D01}.Release|x64.ActiveCfg = Release|Any CPU
{2B627F66-5A61-4F69-B479-62EEAB603D01}.Release|x64.Build.0 = Release|Any CPU
{2B627F66-5A61-4F69-B479-62EEAB603D01}.Release|x86.ActiveCfg = Release|Any CPU
{2B627F66-5A61-4F69-B479-62EEAB603D01}.Release|x86.Build.0 = Release|Any CPU
{8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Debug|x64.ActiveCfg = Debug|Any CPU
{8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Debug|x64.Build.0 = Debug|Any CPU
{8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Debug|x86.ActiveCfg = Debug|Any CPU
{8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Debug|x86.Build.0 = Debug|Any CPU
{8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Release|Any CPU.Build.0 = Release|Any CPU
{8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Release|x64.ActiveCfg = Release|Any CPU
{8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Release|x64.Build.0 = Release|Any CPU
{8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Release|x86.ActiveCfg = Release|Any CPU
{8863A1BA-2E83-419F-BACB-D4A4156EC71C}.Release|x86.Build.0 = Release|Any CPU
{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Debug|x64.ActiveCfg = Debug|Any CPU
{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Debug|x64.Build.0 = Debug|Any CPU
{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Debug|x86.ActiveCfg = Debug|Any CPU
{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Debug|x86.Build.0 = Debug|Any CPU
{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Release|Any CPU.Build.0 = Release|Any CPU
{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Release|x64.ActiveCfg = Release|Any CPU
{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Release|x64.Build.0 = Release|Any CPU
{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Release|x86.ActiveCfg = Release|Any CPU
{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -59,5 +127,7 @@ Global
{2F6B1E26-1217-4EFD-874C-05ADEE4C7969} = {F7F62670-237A-4C93-A30E-CE661C6FC401}
{2B627F66-5A61-4F69-B479-62EEAB603D01} = {FECFFD54-338F-4060-9161-1E5770D1DC33}
{8863A1BA-2E83-419F-BACB-D4A4156EC71C} = {F7F62670-237A-4C93-A30E-CE661C6FC401}
{9059393F-4073-9273-0EEC-2B1BA61B620B} = {F7F62670-237A-4C93-A30E-CE661C6FC401}
{7CC31BC4-38EE-40F4-BBBA-9FC2F4CF6283} = {9059393F-4073-9273-0EEC-2B1BA61B620B}
EndGlobalSection
EndGlobal

View File

@ -1,8 +0,0 @@
using Microsoft.Xna.Framework.Graphics;
namespace Syntriax.Engine.Core;
public interface IDisplayableSprite
{
public void Draw(SpriteBatch spriteBatch);
}

View File

@ -2,7 +2,7 @@ using Apos.Shapes;
namespace Syntriax.Engine.Core;
public interface IDisplayableShape
public interface IDrawableShape : IBehaviour
{
void Draw(ShapeBatch shapeBatch);
}

View File

@ -5,7 +5,7 @@ using Syntriax.Engine.Systems.Tween;
namespace Pong.Behaviours;
public class BallBehaviour : Behaviour2D, IPhysicsUpdate, INetworkEntity,
public class BallBehaviour : Behaviour2D, IFirstFrameUpdate, IPhysicsUpdate, INetworkEntity,
IPacketListenerClient<BallBehaviour.BallUpdatePacket>
{
public float Speed { get; set; } = 500f;
@ -17,7 +17,7 @@ public class BallBehaviour : Behaviour2D, IPhysicsUpdate, INetworkEntity,
private ITweenManager tweenManager = null!;
private INetworkCommunicatorServer? networkServer = null;
protected override void OnFirstActiveFrame()
public void FirstActiveFrame()
{
BehaviourController.GetRequiredBehaviour<ICollider2D>().OnCollisionDetected.AddListener(OnCollisionDetected);
physicsEngine2D = Universe.GetRequiredUniverseObject<IPhysicsEngine2D>();

View File

@ -1,27 +1,28 @@
using Microsoft.Xna.Framework.Input;
using Syntriax.Engine.Core;
using Syntriax.Engine.Integration.MonoGame;
using Syntriax.Engine.Systems.Input;
namespace Pong.Behaviours;
public class CameraController : Behaviour
public class CameraController : Behaviour, IFirstFrameUpdate, IUpdate
{
private MonoGameCamera2DBehaviour cameraBehaviour = null!;
private IButtonInputs<Keys> buttonInputs = null!;
private float defaultZoomLevel = 1f;
protected override void OnFirstActiveFrame()
public void FirstActiveFrame()
{
cameraBehaviour ??= BehaviourController.GetRequiredBehaviour<MonoGameCamera2DBehaviour>();
cameraBehaviour = BehaviourController.GetRequiredBehaviour<MonoGameCamera2DBehaviour>();
buttonInputs = Universe.FindRequiredBehaviour<IButtonInputs<Keys>>();
buttonInputs.RegisterOnPress(Keys.F, SwitchToFullScreen);
buttonInputs.RegisterOnPress(Keys.R, ResetCamera);
}
protected override void OnUpdate()
public void Update()
{
if (buttonInputs.IsPressed(Keys.U))
cameraBehaviour.Zoom += Universe.Time.DeltaTime * 5f;

View File

@ -6,7 +6,7 @@ using Syntriax.Engine.Core;
namespace Pong.Behaviours;
public class CircleBehaviour : Syntriax.Engine.Physics2D.Collider2DCircleBehaviour, IDisplayableShape
public class CircleBehaviour : Syntriax.Engine.Physics2D.Collider2DCircleBehaviour, IDrawableShape
{
public Color Color { get; set; } = Color.White;
public float Thickness { get; set; } = .5f;

View File

@ -1,101 +0,0 @@
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Syntriax.Engine.Core;
namespace Pong.Behaviours;
public class MonoGameCamera2DBehaviour(GraphicsDeviceManager Graphics) : Behaviour2D, ICamera2D
{
public event OnMatrixTransformChangedDelegate? OnMatrixTransformChanged = null;
public event OnViewportChangedDelegate? OnViewportChanged = null;
public event OnZoomChangedDelegate? OnZoomChanged = null;
private Matrix _matrixTransform = Matrix.Identity;
private Viewport _viewport = default;
private float _zoom = 1f;
public GraphicsDeviceManager Graphics { get; } = Graphics;
public Matrix MatrixTransform
{
get => _matrixTransform;
set
{
if (_matrixTransform == value)
return;
_matrixTransform = value;
OnMatrixTransformChanged?.Invoke(this);
}
}
public Vector2D Position
{
get => Transform.Position;
set => Transform.Position = value;
}
public Viewport Viewport
{
get => _viewport;
set
{
if (_viewport.Equals(value))
return;
_viewport = value;
OnViewportChanged?.Invoke(this);
}
}
public float Zoom
{
get => _zoom;
set
{
float newValue = Math.Max(0.1f, value);
if (_zoom == newValue)
return;
_zoom = newValue;
OnZoomChanged?.Invoke(this);
}
}
public float Rotation
{
get => Transform.Rotation;
set => Transform.Rotation = value;
}
// TODO This causes delay since OnPreDraw calls assuming this is called in in Update
public Vector2D ScreenToWorldPosition(Vector2D screenPosition)
{
Vector2D worldPosition = Vector2.Transform(screenPosition.ToVector2(), Matrix.Invert(MatrixTransform)).ToVector2D();
return worldPosition.Scale(EngineConverter.screenScale);
}
public Vector2D WorldToScreenPosition(Vector2D worldPosition)
{
Vector2D screenPosition = Vector2.Transform(worldPosition.ToVector2(), MatrixTransform).ToVector2D();
return screenPosition.Scale(EngineConverter.screenScale);
}
protected override void OnFirstActiveFrame()
=> Viewport = Graphics.GraphicsDevice.Viewport;
protected override void OnPreDraw()
{
MatrixTransform =
Matrix.CreateTranslation(new Vector3(-Position.X, Position.Y, 0f)) *
Matrix.CreateRotationZ(Rotation * Math.DegreeToRadian) *
Matrix.CreateScale(Zoom) *
Matrix.CreateTranslation(new Vector3(_viewport.Width * .5f, _viewport.Height * .5f, 0f));
}
public delegate void OnMatrixTransformChangedDelegate(MonoGameCamera2DBehaviour sender);
public delegate void OnViewportChangedDelegate(MonoGameCamera2DBehaviour sender);
public delegate void OnZoomChangedDelegate(MonoGameCamera2DBehaviour sender);
}

View File

@ -3,7 +3,7 @@ using Syntriax.Engine.Physics2D;
namespace Pong.Behaviours;
public class MovementBallBehaviour : Behaviour2D
public class MovementBallBehaviour : Behaviour2D, IFirstFrameUpdate, IUpdate
{
public Vector2D StartDirection { get; private set; } = Vector2D.Zero;
public float Speed { get; set; } = 500f;
@ -11,7 +11,7 @@ public class MovementBallBehaviour : Behaviour2D
private IRigidBody2D rigidBody = null!;
protected override void OnFirstActiveFrame()
public void FirstActiveFrame()
{
rigidBody = BehaviourController.GetRequiredBehaviour<IRigidBody2D>();
@ -19,7 +19,7 @@ public class MovementBallBehaviour : Behaviour2D
BehaviourController.GetRequiredBehaviour<ICollider2D>().OnCollisionDetected.AddListener(OnCollisionDetected);
}
protected override void OnUpdate()
public void Update()
{
if (rigidBody.Velocity.MagnitudeSquared <= 0.01f)
return;

View File

@ -9,7 +9,7 @@ using Syntriax.Engine.Systems.Input;
namespace Pong.Behaviours;
public class PaddleBehaviour(Keys Up, Keys Down, float High, float Low, float Speed) : Behaviour2D, IPostPhysicsUpdate,
public class PaddleBehaviour(Keys Up, Keys Down, float High, float Low, float Speed) : Behaviour2D, IFirstFrameUpdate, IPostPhysicsUpdate,
IPacketListenerServer<PaddleBehaviour.PaddleKeyStatePacket>, IPacketListenerClient<PaddleBehaviour.PaddleKeyStatePacket>
{
private Keys Up { get; } = Up;
@ -32,7 +32,7 @@ public class PaddleBehaviour(Keys Up, Keys Down, float High, float Low, float Sp
Transform.Position = new Vector2D(Transform.Position.X, MathF.Max(MathF.Min(Transform.Position.Y, High), Low));
}
protected override void OnFirstActiveFrame()
public void FirstActiveFrame()
{
physicsEngine2D = Universe.GetRequiredUniverseObject<IPhysicsEngine2D>();
inputs = Universe.FindRequired<IButtonInputs<Keys>>();
@ -99,14 +99,18 @@ public class PaddleBehaviour(Keys Up, Keys Down, float High, float Low, float Sp
public bool IsDownPressed { get; set; } = default!;
public long Timestamp { get; set; } = 0;
public PaddleKeyStatePacket() { }
public PaddleKeyStatePacket(PaddleBehaviour paddleBehaviour)
public PaddleKeyStatePacket Set(PaddleBehaviour paddleBehaviour)
{
EntityId = paddleBehaviour.Id;
Position = paddleBehaviour.Transform.Position;
IsUpPressed = paddleBehaviour.isUpPressed;
IsDownPressed = paddleBehaviour.isDownPressed;
Timestamp = DateTime.UtcNow.Ticks;
return this;
}
public PaddleKeyStatePacket() { }
public PaddleKeyStatePacket(PaddleBehaviour paddleBehaviour) => Set(paddleBehaviour);
}
}

View File

@ -8,7 +8,7 @@ using Syntriax.Engine.Systems.Input;
namespace Pong.Behaviours;
public class PongManagerBehaviour : Behaviour, INetworkEntity,
public class PongManagerBehaviour : Behaviour, INetworkEntity, IFirstFrameUpdate,
IPacketListenerServer<PongManagerBehaviour.RequestStartPacket>
{
public Action<PongManagerBehaviour>? OnReset { get; set; } = null;
@ -30,7 +30,7 @@ public class PongManagerBehaviour : Behaviour, INetworkEntity,
public PongManagerBehaviour() => WinScore = 5;
public PongManagerBehaviour(int winScore) => WinScore = winScore;
protected override void OnFirstActiveFrame()
public void FirstActiveFrame()
{
IButtonInputs<Keys> buttonInputs = Universe.FindRequired<IButtonInputs<Keys>>();
buttonInputs.RegisterOnRelease(Keys.Space, (_, _1) => networkClient?.SendToServer(new RequestStartPacket()));

View File

@ -8,7 +8,7 @@ using Syntriax.Engine.Systems.Input;
namespace Pong.Behaviours;
public class ShapeAABBBehaviour : Behaviour2D, IDisplayableShape
public class ShapeAABBBehaviour : Behaviour2D, IDrawableShape, IFirstFrameUpdate
{
private IShapeCollider2D? shapeCollider = null;
@ -16,7 +16,7 @@ public class ShapeAABBBehaviour : Behaviour2D, IDisplayableShape
public float Thickness { get; set; } = .5f;
public bool display = true;
protected override void OnFirstActiveFrame()
public void FirstActiveFrame()
{
BehaviourController.TryGetBehaviour(out shapeCollider);

View File

@ -0,0 +1,35 @@
using System.Collections.Generic;
using Apos.Shapes;
using Syntriax.Engine.Core;
using Syntriax.Engine.Integration.MonoGame;
namespace Pong.Behaviours;
public class ShapeBatcher : BehaviourBase, IFirstFrameUpdate, IDraw
{
private static Comparer<IBehaviour> SortByPriority() => Comparer<IBehaviour>.Create((x, y) => y.Priority.CompareTo(x.Priority));
private ShapeBatch shapeBatch = null!;
private MonoGameCamera2DBehaviour camera2D = null!;
private readonly ActiveBehaviourCollectorSorted<IDrawableShape> drawableShapes = new() { SortBy = SortByPriority() };
public void FirstActiveFrame()
{
MonoGameWindowContainer windowContainer = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour<MonoGameWindowContainer>();
camera2D = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour<MonoGameCamera2DBehaviour>();
shapeBatch = new(windowContainer.Window.GraphicsDevice, windowContainer.Window.Content);
drawableShapes.Unassign();
drawableShapes.Assign(Universe);
}
public void Draw()
{
shapeBatch.Begin(camera2D.MatrixTransform);
for (int i = drawableShapes.Count - 1; i >= 0; i--)
drawableShapes[i].Draw(shapeBatch);
shapeBatch.End();
}
}

View File

@ -6,7 +6,7 @@ using Syntriax.Engine.Core;
namespace Pong.Behaviours;
public class ShapeBehaviour : Syntriax.Engine.Physics2D.Collider2DShapeBehaviour, IDisplayableShape
public class ShapeBehaviour : Syntriax.Engine.Physics2D.Collider2DShapeBehaviour, IDrawableShape
{
public Color Color { get; set; } = Color.White;
public float Thickness { get; set; } = .5f;

View File

@ -2,10 +2,11 @@ using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Syntriax.Engine.Core;
using Syntriax.Engine.Integration.MonoGame;
namespace Pong.Behaviours;
public class TextBehaviour : Behaviour2D, IDisplayableSprite
public class TextBehaviour : Behaviour2D, IDrawableSprite
{
public SpriteFont? Font { get; set; } = null;
public int Size { get; set; } = 16;
@ -13,7 +14,7 @@ public class TextBehaviour : Behaviour2D, IDisplayableSprite
public void Draw(SpriteBatch spriteBatch)
{
if (!IsActive || Font is null)
if (Font is null)
return;
spriteBatch.DrawString(Font, Text, Transform.Position.ToDisplayVector2(), Color.White, Transform.Rotation, Vector2.One * .5f, Transform.Scale.Magnitude, SpriteEffects.None, 0f);

View File

@ -3,13 +3,13 @@ using Syntriax.Engine.Core;
namespace Pong.Behaviours;
public class TextScoreBehaviour : TextBehaviour
public class TextScoreBehaviour : TextBehaviour, IFirstFrameUpdate
{
public bool IsLeft { get; }
private PongManagerBehaviour? pongManager = null;
protected override void OnFirstActiveFrame()
public void FirstActiveFrame()
{
if (!UniverseObject.Universe.TryFindBehaviour(out pongManager))
return;

View File

@ -5,11 +5,11 @@ using Syntriax.Engine.Physics2D;
namespace Pong.Behaviours;
public class WallScoreBehaviour(Action OnCollision) : Behaviour2D
public class WallScoreBehaviour(Action OnCollision) : Behaviour2D, IFirstFrameUpdate
{
private Action OnCollision { get; } = OnCollision;
protected override void OnFirstActiveFrame()
public void FirstActiveFrame()
{
if (!BehaviourController.TryGetBehaviour(out ICollider2D? collider2D))
return;

View File

@ -5,8 +5,6 @@ using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Apos.Shapes;
using Pong.Behaviours;
using Syntriax.Engine.Core;
@ -14,53 +12,24 @@ using Syntriax.Engine.Core.Factory;
using Syntriax.Engine.Network;
using Syntriax.Engine.Physics2D;
using Syntriax.Engine.Systems.Tween;
using Syntriax.Engine.Integration.MonoGame;
namespace Pong;
public class GamePong : Game
public class GamePong : MonoGameWindow
{
private readonly IUniverseObject platformSpecificUniverseObject = null!;
private readonly GraphicsDeviceManager graphics = null!;
private SpriteBatch spriteBatch = null!;
private ShapeBatch shapeBatch = null!;
private Universe universe = null!;
private BehaviourCollector<IDisplayableSprite> displayableCollector = null!;
private BehaviourCollector<IDisplayableShape> displayableShapeCollector = null!;
private MonoGameCamera2DBehaviour cameraBehaviour = null!;
private PongManagerBehaviour pongManager = null!;
public GamePong(IUniverseObject platformSpecificUniverseObject)
{
this.platformSpecificUniverseObject = platformSpecificUniverseObject;
graphics = new GraphicsDeviceManager(this)
{
PreferredBackBufferWidth = 1024,
PreferredBackBufferHeight = 576,
GraphicsProfile = GraphicsProfile.HiDef
};
Content.RootDirectory = "Content";
IsMouseVisible = true;
Graphics.PreferredBackBufferWidth = 1024;
Graphics.PreferredBackBufferHeight = 576;
Graphics.GraphicsProfile = GraphicsProfile.HiDef;
}
protected override void Initialize()
protected override void PopulateUniverse(IUniverse universe)
{
// TODO: Add your initialization logic here
universe = new();
universe.Initialize();
displayableCollector = new(universe);
displayableShapeCollector = new(universe);
base.Initialize();
}
protected override void LoadContent()
{
spriteBatch = new SpriteBatch(GraphicsDevice);
shapeBatch = new ShapeBatch(GraphicsDevice, Content);
SpriteFont spriteFont = Content.Load<SpriteFont>("UbuntuMono");
universe.Register(platformSpecificUniverseObject);
@ -79,7 +48,9 @@ public class GamePong : Game
client.Connect("localhost", 8888);
Window.Title = $"Client";
universe.InstantiateUniverseObject<DrawManager>().SetUniverseObject("Draw Manager");
DrawManager drawManager = universe.InstantiateUniverseObject<DrawManager>().SetUniverseObject("Draw Manager");
universe.InstantiateUniverseObject().SetUniverseObject("Shape Batcher", drawManager).BehaviourController.AddBehaviour<ShapeBatcher>();
universe.InstantiateUniverseObject().SetUniverseObject("Sprite Batcher", drawManager).BehaviourController.AddBehaviour<SpriteBatcher>();
}
universe.InstantiateUniverseObject<UpdateManager>().SetUniverseObject("Update Manager");
@ -89,14 +60,14 @@ public class GamePong : Game
////////////////////////////////////////////////////////////////////////////////////
cameraBehaviour = universe.InstantiateUniverseObject().SetUniverseObject("Camera")
universe.InstantiateUniverseObject().SetUniverseObject("Camera")
.BehaviourController.AddBehaviour<Transform2D>()
.BehaviourController.AddBehaviour<CameraController>()
.BehaviourController.AddBehaviour<MonoGameCamera2DBehaviour>(graphics);
.BehaviourController.AddBehaviour<MonoGameCamera2DBehaviour>();
////////////////////////////////////////////////////////////////////////////////////
pongManager = universe.InstantiateUniverseObject().SetUniverseObject("Pong Game Manager")
PongManagerBehaviour pongManager = universe.InstantiateUniverseObject().SetUniverseObject("Pong Game Manager")
.BehaviourController.AddBehaviour<PongManagerBehaviour>(5);
////////////////////////////////////////////////////////////////////////////////////
@ -161,34 +132,4 @@ public class GamePong : Game
.BehaviourController.AddBehaviour<Transform2D>().SetTransform(position: new Vector2D(250f, 250f), scale: Vector2D.One * .25f)
.BehaviourController.AddBehaviour<TextScoreBehaviour>(false, spriteFont);
}
protected override void Update(GameTime gameTime)
{
if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed || Keyboard.GetState().IsKeyDown(Keys.Escape))
Exit();
universe.Update(gameTime.ToUniverseTime());
base.Update(gameTime);
}
protected override void Draw(GameTime gameTime)
{
GraphicsDevice.Clear(new Color() { R = 32, G = 32, B = 32 });
// TODO: Add your drawing code here
universe.Draw();
spriteBatch.Begin(SpriteSortMode.Deferred, transformMatrix: cameraBehaviour.MatrixTransform);
for (int i = displayableCollector.Count - 1; i >= 0; i--)
displayableCollector[i].Draw(spriteBatch);
spriteBatch.End();
shapeBatch.Begin(cameraBehaviour.MatrixTransform);
for (int i = displayableShapeCollector.Count - 1; i >= 0; i--)
displayableShapeCollector[i].Draw(shapeBatch);
shapeBatch.End();
base.Draw(gameTime);
}
}

View File

@ -0,0 +1,325 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Syntriax.Engine.Core;
namespace Syntriax.Engine.Network;
public class NetworkManagerSlo : UniverseObject, INetworkManager
{
private readonly List<(Type packetType, Delegate callback)> packetDelegates = [];
private readonly Dictionary<Type, Dictionary<Type, List<DelegateData>>> clientListenerDelegates = [];
private readonly Dictionary<Type, Dictionary<Type, List<DelegateData>>> serverListenerDelegates = [];
private readonly Dictionary<Type, Dictionary<string, Event<string, object>>> clientPacketListeners = [];
private readonly Dictionary<Type, Dictionary<string, Event<string, object>>> serverPacketListeners = [];
private readonly Dictionary<string, INetworkEntity> _networkEntities = [];
private readonly BehaviourCollector<INetworkEntity> _networkEntityCollector = new();
public IReadOnlyDictionary<string, INetworkEntity> NetworkEntities => _networkEntities;
public IBehaviourCollector<INetworkEntity> NetworkEntityCollector => _networkEntityCollector;
private INetworkCommunicator _networkCommunicator = null!;
public INetworkCommunicator NetworkCommunicator
{
get => _networkCommunicator;
set
{
if (_networkCommunicator == value)
return;
INetworkCommunicator previousCommunicator = _networkCommunicator;
_networkCommunicator = value;
if (previousCommunicator is not null)
UnsubscribeDelegates(previousCommunicator);
if (_networkCommunicator is not null)
SubscribeDelegates(_networkCommunicator);
}
}
#region Network Communicator Subscriptions
private static MethodInfo? GetCommunicatorMethod(string methodName)
=> typeof(INetworkCommunicator).GetMethod(methodName, BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
private void SubscribeDelegates(INetworkCommunicator communicator)
{
MethodInfo subscribeMethod = GetCommunicatorMethod(nameof(INetworkCommunicator.SubscribeToPackets))!;
foreach ((Type packetType, Delegate callback) in packetDelegates)
{
MethodInfo genericMethod = subscribeMethod.MakeGenericMethod(packetType);
genericMethod.Invoke(communicator, [callback]);
}
}
private void UnsubscribeDelegates(INetworkCommunicator communicator)
{
MethodInfo unsubscribeMethod = GetCommunicatorMethod(nameof(INetworkCommunicator.UnsubscribeFromPackets))!;
foreach ((Type packetType, Delegate callback) in packetDelegates)
{
MethodInfo genericMethod = unsubscribeMethod.MakeGenericMethod(packetType);
genericMethod.Invoke(communicator, [callback]);
}
}
#endregion
#region Engine Callbacks
private void OnEntityCollected(IBehaviourCollector<INetworkEntity> sender, IBehaviourCollector<INetworkEntity>.BehaviourCollectedArguments args)
{
INetworkEntity entity = args.BehaviourCollected;
if (!_networkEntities.TryAdd(entity.Id, entity))
throw new Exception($"Unable to add {entity.Id} to {nameof(NetworkManager)}");
RegisterEntityListeners(entity);
}
private void OnEntityRemoved(IBehaviourCollector<INetworkEntity> sender, IBehaviourCollector<INetworkEntity>.BehaviourRemovedArguments args)
{
INetworkEntity removedBehaviour = args.BehaviourRemoved;
if (!_networkEntities.Remove(args.BehaviourRemoved.Id))
return;
UnRegisterEntityListeners(removedBehaviour);
}
protected override void OnExitingUniverse(IUniverse universe)
{
_networkEntityCollector.Unassign();
}
protected override void OnEnteringUniverse(IUniverse universe)
{
_networkEntityCollector.Assign(universe);
NetworkCommunicator = this.GetRequiredUniverseObjectInParent<INetworkCommunicator>();
}
#endregion
#region Packet Retrieval
private void OnPacketReceived<T>(string senderClientId, T packet)
{
if (packet is IEntityNetworkPacket entityPacket)
RouteEntityPacket(senderClientId, entityPacket.EntityId, packet);
else
RouteBroadcastPacket(senderClientId, packet!);
}
private void RouteEntityPacket<T>(string senderClientId, string entityId, T packet)
{
if (NetworkCommunicator is INetworkCommunicatorClient)
InvokeListenerIfExists(clientPacketListeners, entityId, senderClientId, packet!);
if (NetworkCommunicator is INetworkCommunicatorServer)
InvokeListenerIfExists(serverPacketListeners, entityId, senderClientId, packet!);
}
private void RouteBroadcastPacket<T>(string senderClientId, T packet)
{
if (NetworkCommunicator is INetworkCommunicatorClient)
InvokeAllListeners(clientPacketListeners, senderClientId, packet!);
if (NetworkCommunicator is INetworkCommunicatorServer)
InvokeAllListeners(serverPacketListeners, senderClientId, packet!);
}
private static void InvokeListenerIfExists<T>(
Dictionary<Type, Dictionary<string, Event<string, object>>> listeners,
string entityId,
string senderClientId,
T packet)
{
if (!listeners.TryGetValue(packet!.GetType(), out Dictionary<string, Event<string, object>>? entityListeners))
return;
if (entityListeners.TryGetValue(entityId, out Event<string, object>? listenerEvent))
listenerEvent.Invoke(senderClientId, packet);
}
private static void InvokeAllListeners<T>(
Dictionary<Type, Dictionary<string, Event<string, object>>> listeners,
string senderClientId,
T packet)
{
if (!listeners.TryGetValue(packet!.GetType(), out Dictionary<string, Event<string, object>>? entityListeners))
return;
foreach ((string _, Event<string, object> listenerEvent) in entityListeners)
listenerEvent.Invoke(senderClientId, packet);
}
#endregion
#region Listener Setups
private void RegisterEntityListeners(INetworkEntity entity)
{
RegisterEntityListenersForSide(entity, clientListenerDelegates, clientPacketListeners, isServer: false);
RegisterEntityListenersForSide(entity, serverListenerDelegates, serverPacketListeners, isServer: true);
}
private static void RegisterEntityListenersForSide(
INetworkEntity entity,
Dictionary<Type, Dictionary<Type, List<DelegateData>>> listenerDelegates,
Dictionary<Type, Dictionary<string, Event<string, object>>> packetListeners,
bool isServer)
{
if (!listenerDelegates.TryGetValue(entity.GetType(), out Dictionary<Type, List<DelegateData>>? interfaceDelegates))
return;
foreach ((Type interfaceType, List<DelegateData> delegateDataList) in interfaceDelegates)
foreach ((Type parameterType, MethodInfo receiveMethod) in delegateDataList)
{
Dictionary<string, Event<string, object>> listeners = GetOrCreateListenersForPacketType(packetListeners, parameterType);
Event<string, object> listenerEvent = CreateListenerEvent(entity, receiveMethod, isServer);
listeners.Add(entity.Id, listenerEvent);
}
}
private void UnRegisterEntityListeners(INetworkEntity entity)
{
UnregisterEntityListenersForSide(entity, clientListenerDelegates, clientPacketListeners);
UnregisterEntityListenersForSide(entity, serverListenerDelegates, serverPacketListeners);
}
private static void UnregisterEntityListenersForSide(
INetworkEntity entity,
Dictionary<Type, Dictionary<Type, List<DelegateData>>> listenerDelegates,
Dictionary<Type, Dictionary<string, Event<string, object>>> packetListeners)
{
if (!listenerDelegates.TryGetValue(entity.GetType(), out Dictionary<Type, List<DelegateData>>? interfaceDelegates))
return;
foreach ((Type interfaceType, List<DelegateData> delegateDataList) in interfaceDelegates)
foreach ((Type parameterType, MethodInfo receiveMethod) in delegateDataList)
{
Dictionary<string, Event<string, object>> listeners = GetOrCreateListenersForPacketType(packetListeners, parameterType);
if (!listeners.TryGetValue(entity.Id, out Event<string, object>? listenerEvent))
continue;
listeners.Remove(entity.Id);
listenerDelegates.Clear();
}
}
private static Dictionary<string, Event<string, object>> GetOrCreateListenersForPacketType(
Dictionary<Type, Dictionary<string, Event<string, object>>> packetListeners,
Type packetType)
{
if (!packetListeners.TryGetValue(packetType, out Dictionary<string, Event<string, object>>? listeners))
{
listeners = [];
packetListeners.Add(packetType, listeners);
}
return listeners;
}
private static Event<string, object> CreateListenerEvent(INetworkEntity entity, MethodInfo receiveMethod, bool isServer)
{
Event<string, object> listenerEvent = new();
if (isServer)
listenerEvent.AddListener((sender, packet) => receiveMethod.Invoke(entity, [sender, packet]));
else
listenerEvent.AddListener((sender, packet) => receiveMethod.Invoke(entity, [packet]));
return listenerEvent;
}
#endregion
#region Initialization
private static IEnumerable<Type> DiscoverPacketTypes()
=> AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes())
.Where(
t => typeof(INetworkPacket).IsAssignableFrom(t)
&& !t.IsInterface
&& !t.IsAbstract
&& !t.IsGenericType
);
private static IEnumerable<Type> DiscoverClassesImplementing(Type interfaceType)
=> AppDomain.CurrentDomain.GetAssemblies()
.SelectMany(a => a.GetTypes())
.Where(
t => t.GetInterfaces().Any(
i => i.IsGenericType && i.GetGenericTypeDefinition() == interfaceType
)
);
private static IEnumerable<Type> GetInterfacesOfType(Type type, Type interfaceType)
=> type.GetInterfaces().Where(i => i.IsGenericType && i.GetGenericTypeDefinition() == interfaceType);
private static void CacheListenerDelegatesForSide(Dictionary<Type, Dictionary<Type, List<DelegateData>>> targetCache, Type listenerInterfaceType, string methodName)
{
IEnumerable<Type> listenerClasses = DiscoverClassesImplementing(listenerInterfaceType);
foreach (Type listenerClass in listenerClasses)
{
Dictionary<Type, List<DelegateData>> interfaceListeners = [];
targetCache.Add(listenerClass, interfaceListeners);
IEnumerable<Type> interfaces = GetInterfacesOfType(listenerClass, listenerInterfaceType);
foreach (Type listenerInterface in interfaces)
{
Type packetType = listenerInterface.GetGenericArguments().First();
List<DelegateData> methods = listenerInterface.GetMethods()
.Where(m => m.Name == methodName)
.Select(m => new DelegateData(packetType, m))
.ToList();
interfaceListeners.Add(packetType, methods);
}
}
}
private void CacheListenerDelegates()
{
CacheListenerDelegatesForSide(clientListenerDelegates, typeof(IPacketListenerClient<>), nameof(IPacketListenerClient<INetworkEntity>.OnClientPacketArrived));
CacheListenerDelegatesForSide(serverListenerDelegates, typeof(IPacketListenerServer<>), nameof(IPacketListenerServer<INetworkEntity>.OnServerPacketArrived));
}
private void CachePacketDelegates()
{
IEnumerable<Type> packetTypes = DiscoverPacketTypes();
MethodInfo onPacketReceivedMethod = GetType().GetMethod(
nameof(OnPacketReceived),
BindingFlags.NonPublic | BindingFlags.Instance
)!;
foreach (Type packetType in packetTypes)
{
MethodInfo genericMethod = onPacketReceivedMethod.MakeGenericMethod(packetType);
Type delegateType = typeof(Event<,>.EventHandler).MakeGenericType(typeof(string), packetType);
Delegate packetDelegate = Delegate.CreateDelegate(delegateType, this, genericMethod);
packetDelegates.Add((packetType, packetDelegate));
}
}
public NetworkManagerSlo()
{
CachePacketDelegates();
CacheListenerDelegates();
_networkEntityCollector.OnCollected.AddListener(OnEntityCollected);
_networkEntityCollector.OnRemoved.AddListener(OnEntityRemoved);
}
#endregion
private readonly record struct DelegateData(Type ParameterType, MethodInfo ReceiveMethod)
{
public static implicit operator (Type ParameterType, MethodInfo ReceiveMethod)(DelegateData value)
=> (value.ParameterType, value.ReceiveMethod);
public static implicit operator DelegateData((Type ParameterType, MethodInfo ReceiveMethod) value)
=> new(value.ParameterType, value.ReceiveMethod);
}
}

View File

@ -13,5 +13,6 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../Engine/Engine/Engine.csproj" />
<ProjectReference Include="../Engine/Engine.Integration/Engine.Integration.MonoGame/Engine.Integration.MonoGame.csproj" />
</ItemGroup>
</Project>