diff --git a/DoomDeathmatch.sln b/DoomDeathmatch.sln
index 3dc2ea0..ebe97a0 100644
--- a/DoomDeathmatch.sln
+++ b/DoomDeathmatch.sln
@@ -12,6 +12,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PresenterNative", "Presente
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DoomDeathmatch", "DoomDeathmatch\DoomDeathmatch.csproj", "{4FA5E1F8-B647-4764-9147-B6F5A4E9D1D0}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestUtil", "TestUtil\TestUtil.csproj", "{923DEAFC-55C6-426A-8EE8-D74142FDC342}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GameTests", "GameTests\GameTests.csproj", "{C2CE10BB-DB8C-4283-BC22-88CDA8B54DEB}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -42,5 +46,13 @@ Global
{4FA5E1F8-B647-4764-9147-B6F5A4E9D1D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{4FA5E1F8-B647-4764-9147-B6F5A4E9D1D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{4FA5E1F8-B647-4764-9147-B6F5A4E9D1D0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {923DEAFC-55C6-426A-8EE8-D74142FDC342}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {923DEAFC-55C6-426A-8EE8-D74142FDC342}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {923DEAFC-55C6-426A-8EE8-D74142FDC342}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {923DEAFC-55C6-426A-8EE8-D74142FDC342}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C2CE10BB-DB8C-4283-BC22-88CDA8B54DEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C2CE10BB-DB8C-4283-BC22-88CDA8B54DEB}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C2CE10BB-DB8C-4283-BC22-88CDA8B54DEB}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C2CE10BB-DB8C-4283-BC22-88CDA8B54DEB}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/DoomDeathmatch/DoomDeathmatch.csproj b/DoomDeathmatch/DoomDeathmatch.csproj
index 7763b52..da0f068 100644
--- a/DoomDeathmatch/DoomDeathmatch.csproj
+++ b/DoomDeathmatch/DoomDeathmatch.csproj
@@ -6,6 +6,14 @@
enable
+
+
+
+
+
+
+
+
diff --git a/DoomDeathmatch/src/Component/MVC/Health/HealthController.cs b/DoomDeathmatch/src/Component/MVC/Health/HealthController.cs
index 2c2780b..35d1032 100644
--- a/DoomDeathmatch/src/Component/MVC/Health/HealthController.cs
+++ b/DoomDeathmatch/src/Component/MVC/Health/HealthController.cs
@@ -18,6 +18,11 @@ public class HealthController : Engine.Scene.Component.Component
///
public bool IsAlive => _healthModel.Health > 0;
+ ///
+ /// The current health value of the entity.
+ ///
+ public float Health => _healthModel.Health;
+
///
/// The health model containing the current and maximum health.
///
diff --git a/DoomDeathmatch/src/Component/MVC/Health/HealthView.cs b/DoomDeathmatch/src/Component/MVC/Health/HealthView.cs
index 176a90a..0ff011a 100644
--- a/DoomDeathmatch/src/Component/MVC/Health/HealthView.cs
+++ b/DoomDeathmatch/src/Component/MVC/Health/HealthView.cs
@@ -24,7 +24,7 @@ public class HealthView : Engine.Scene.Component.Component, IView
}
///
- public virtual void UpdateView(HealthModel parHealthModel)
+ public void UpdateView(HealthModel parHealthModel)
{
var percentage = parHealthModel.Health / parHealthModel.MaxHealth * 100;
if (parHealthModel.Health != 0)
diff --git a/DoomDeathmatch/src/Component/MVC/MovementComponent.cs b/DoomDeathmatch/src/Component/MVC/MovementComponent.cs
index 2e287e6..8aeb202 100644
--- a/DoomDeathmatch/src/Component/MVC/MovementComponent.cs
+++ b/DoomDeathmatch/src/Component/MVC/MovementComponent.cs
@@ -26,7 +26,7 @@ public class MovementComponent : Engine.Scene.Component.Component
///
/// The drag component for the game object.
///
- private DragComponent _dragComponent = null!;
+ private DragComponent? _dragComponent;
public override void Awake()
{
@@ -34,7 +34,6 @@ public class MovementComponent : Engine.Scene.Component.Component
_dragComponent = GameObject.GetComponent()!;
ArgumentNullException.ThrowIfNull(_rigidbody);
- ArgumentNullException.ThrowIfNull(_dragComponent);
}
///
@@ -43,7 +42,7 @@ public class MovementComponent : Engine.Scene.Component.Component
/// The direction of movement.
public void ApplyMovement(Vector3 parDirection)
{
- _rigidbody.Force += _dragComponent.Drag * Speed * parDirection.Normalized();
+ _rigidbody.Force += (_dragComponent?.Drag ?? 1) * Speed * parDirection.Normalized();
}
///
@@ -53,6 +52,6 @@ public class MovementComponent : Engine.Scene.Component.Component
public void ApplyRotation(Vector3 parAxis)
{
var radiansPerSecond = MathHelper.DegreesToRadians(RotationSpeed);
- _rigidbody.Torque += _dragComponent.RotationalDrag * radiansPerSecond * parAxis.Normalized();
+ _rigidbody.Torque += (_dragComponent?.RotationalDrag ?? 1) * radiansPerSecond * parAxis.Normalized();
}
}
\ No newline at end of file
diff --git a/DoomDeathmatch/src/Component/MVC/Weapon/WeaponController.cs b/DoomDeathmatch/src/Component/MVC/Weapon/WeaponController.cs
index 090d5e3..8712475 100644
--- a/DoomDeathmatch/src/Component/MVC/Weapon/WeaponController.cs
+++ b/DoomDeathmatch/src/Component/MVC/Weapon/WeaponController.cs
@@ -30,7 +30,7 @@ public class WeaponController : Engine.Scene.Component.Component
///
/// View responsible for displaying weapon information and animations.
///
- private IWeaponView _weaponView = null!;
+ private IWeaponView? _weaponView;
///
/// Initializes a new instance of the class with an initial weapon.
@@ -43,7 +43,7 @@ public class WeaponController : Engine.Scene.Component.Component
public override void Awake()
{
- _weaponView = GameObject.GetComponent()!;
+ _weaponView = GameObject.GetComponent();
_weaponModel.OnWeaponSelected += WeaponSelected;
WeaponSelected(null, _weaponModel.SelectedWeapon);
@@ -63,7 +63,7 @@ public class WeaponController : Engine.Scene.Component.Component
_weaponModel.SelectedWeapon.Ammo--;
OnWeaponShot?.Invoke(_weaponModel.SelectedWeapon);
- _weaponView.PlayFireAnimation();
+ _weaponView?.PlayFireAnimation();
return true;
}
@@ -178,7 +178,7 @@ public class WeaponController : Engine.Scene.Component.Component
}
parNewWeapon.OnAmmoChanged += AmmoChanged;
- _weaponView.UpdateView(parNewWeapon);
+ _weaponView?.UpdateView(parNewWeapon);
AmmoChanged(parNewWeapon);
}
@@ -190,6 +190,6 @@ public class WeaponController : Engine.Scene.Component.Component
{
var ammoData = new AmmoData { Ammo = parWeapon.Ammo, MaxAmmo = parWeapon.MaxAmmo };
- _weaponView.UpdateView(ammoData);
+ _weaponView?.UpdateView(ammoData);
}
}
\ No newline at end of file
diff --git a/DoomDeathmatch/src/Component/Physics/RigidbodyComponent.cs b/DoomDeathmatch/src/Component/Physics/RigidbodyComponent.cs
index 837ebde..4a3e368 100644
--- a/DoomDeathmatch/src/Component/Physics/RigidbodyComponent.cs
+++ b/DoomDeathmatch/src/Component/Physics/RigidbodyComponent.cs
@@ -154,16 +154,9 @@ public class RigidbodyComponent : Engine.Scene.Component.Component
_angularAcceleration = Torque / MomentOfInertia;
AngularVelocity += _angularAcceleration * (float)parDeltaTime;
- // Update rotation using quaternion math
- var rotation = GameObject.Transform.Rotation;
- var angularVelocityQuat = new Quaternion(AngularVelocity, 0.0f);
-
- // Quaternion rotation integration: Δq = 0.5 * angularVelocityQuat * rotation
- var deltaRotation = 0.5f * angularVelocityQuat * rotation;
- rotation += deltaRotation * (float)parDeltaTime;
- rotation.Normalize(); // Ensure the quaternion remains normalized
-
- GameObject.Transform.Rotation = rotation;
+ GameObject.Transform.Rotation *= Quaternion.FromAxisAngle(Vector3.UnitX, AngularVelocity.X * (float)parDeltaTime);
+ GameObject.Transform.Rotation *= Quaternion.FromAxisAngle(Vector3.UnitY, AngularVelocity.Y * (float)parDeltaTime);
+ GameObject.Transform.Rotation *= Quaternion.FromAxisAngle(Vector3.UnitZ, AngularVelocity.Z * (float)parDeltaTime);
Torque = Vector3.Zero;
}
diff --git a/DoomDeathmatch/src/Component/UI/TextAlignComponent.cs b/DoomDeathmatch/src/Component/UI/TextAlignComponent.cs
index eaff416..40aae68 100644
--- a/DoomDeathmatch/src/Component/UI/TextAlignComponent.cs
+++ b/DoomDeathmatch/src/Component/UI/TextAlignComponent.cs
@@ -58,9 +58,9 @@ public class TextAlignComponent : Engine.Scene.Component.Component
{
return Alignment switch
{
- Align.Left => new Vector2(0, -parSize.Y / 2),
- Align.Center => new Vector2(-parSize.X / 2, -parSize.Y / 2),
- Align.Right => new Vector2(-parSize.X, -parSize.Y / 2),
+ Align.Left => new Vector2(0, parSize.Y / 2 - _textRenderer.Font.Metadata.Metrics.Ascender),
+ Align.Center => new Vector2(-parSize.X / 2, parSize.Y / 2 - _textRenderer.Font.Metadata.Metrics.Ascender),
+ Align.Right => new Vector2(-parSize.X, parSize.Y / 2 - _textRenderer.Font.Metadata.Metrics.Ascender),
_ => throw new ArgumentOutOfRangeException(nameof(Alignment), Alignment, null)
};
}
diff --git a/DoomDeathmatch/src/Scene/Main/MainScene.cs b/DoomDeathmatch/src/Scene/Main/MainScene.cs
index 1bf3471..5d38ff1 100644
--- a/DoomDeathmatch/src/Scene/Main/MainScene.cs
+++ b/DoomDeathmatch/src/Scene/Main/MainScene.cs
@@ -180,9 +180,14 @@ public static class MainScene
var (rulesObject, rulesUi, _) = UiPrefabs.CreateTextUi(parScene, parUiContainer,
UiPrefabs.GetDoomFont(), "Правила");
+ var (actualRulesObject, actualRulesUi, _) = UiPrefabs.CreateTextUi(parScene, parUiContainer,
+ UiPrefabs.GetDoomFont(), "Игрок управляет персонажем, который может передвигаться, собирать предметы и использовать оружие.\nВ игре два вида оружия: пистолет и дробовик, каждое с уникальными характеристиками и ограничением по боеприпасам.\nПротивники делятся на демонов, которые преследуют игрока и наносят ближний урон, и импом,\nкоторые атакуют издалека и создают огненные шары.\nНа уровне случайно появляются предметы, такие как оружие и аптечки, которые восполняют здоровье или боеприпасы.\nЗа уничтожение врагов игрок получает очки, отображаемые в счетчике.\nЛучшие результаты сохраняются в таблице рекордов.");
+
+ actualRulesObject.Transform.Scale.Xy = new Vector2(0.5f);
+
var (stackObject, stack) = UiPrefabs.CreateStackUi(parScene,
- new StackComponent { Offset = new Vector2(0, -1f), Container = parUiContainer, Children = { rulesUi, backUi } });
- stackObject.Transform.Size.Xy = new Vector2(1f, 6f);
+ new StackComponent { Offset = new Vector2(0, -1.5f), Container = parUiContainer, Children = { rulesUi, actualRulesUi, backUi } });
+ stackObject.Transform.Size.Xy = new Vector2(1f, 9f);
var (selectorObject, selector) = UiPrefabs.CreateSelectorUi(parScene, new SelectorComponent { Children = { backUi } });
@@ -191,6 +196,7 @@ public static class MainScene
parScene.AddChild(parentObject, stackObject);
parScene.AddChild(stackObject, rulesObject);
+ parScene.AddChild(stackObject, actualRulesObject);
parScene.AddChild(stackObject, backUiObject);
return parentObject;
diff --git a/DoomDeathmatch/src/Scene/Play/Prefab/PlayerPrefab.cs b/DoomDeathmatch/src/Scene/Play/Prefab/PlayerPrefab.cs
index edd578a..9e430ed 100644
--- a/DoomDeathmatch/src/Scene/Play/Prefab/PlayerPrefab.cs
+++ b/DoomDeathmatch/src/Scene/Play/Prefab/PlayerPrefab.cs
@@ -19,7 +19,7 @@ public static class PlayerPrefab
perspectiveCameraObject.Transform.Translation.Z = 2;
var playerObject = GameObjectUtil.CreateGameObject(parScene, [
new RigidbodyComponent(),
- new DragComponent { Drag = 10f, RotationalDrag = 10f, },
+ new DragComponent { Drag = 10f, RotationalDrag = 20f, },
new AABBColliderComponent
{
diff --git a/DoomDeathmatch/src/Scene/UiPrefabs.cs b/DoomDeathmatch/src/Scene/UiPrefabs.cs
index b69face..786a432 100644
--- a/DoomDeathmatch/src/Scene/UiPrefabs.cs
+++ b/DoomDeathmatch/src/Scene/UiPrefabs.cs
@@ -42,10 +42,10 @@ public static class UiPrefabs
public static (GameObject, UiContainerComponent, (GameObject, TextRenderer)) CreateTextUi(Engine.Scene.Scene parScene,
UiContainerComponent parContainer,
- Font parFont, string parText, Align parAlign = Align.Center,
+ Font? parFont, string parText, Align parAlign = Align.Center,
RenderLayer? parRenderLayer = null, float parScale = 1)
{
- var size = parFont.Measure(parText);
+ var size = parFont?.Measure(parText) ?? Vector2.Zero;
var outerObject = new GameObject
{
Transform =
diff --git a/Engine/Engine.csproj b/Engine/Engine.csproj
index 73950f4..e86f59e 100644
--- a/Engine/Engine.csproj
+++ b/Engine/Engine.csproj
@@ -15,6 +15,7 @@
+
diff --git a/Engine/src/Context/EngineContext.cs b/Engine/src/Context/EngineContext.cs
new file mode 100644
index 0000000..dc51489
--- /dev/null
+++ b/Engine/src/Context/EngineContext.cs
@@ -0,0 +1,41 @@
+using Engine.Graphics;
+using Engine.Input;
+using Engine.Resource;
+using Engine.Scene;
+
+namespace Engine.Context;
+
+///
+/// A context for the engine, providing access to the engine's services.
+///
+public class EngineContext : IContext
+{
+ public IInputHandler InputHandler => _engine.InputHandler!;
+
+ public IResourceManager AssetResourceManager => _engine.AssetResourceManager;
+
+ public ISceneManager SceneManager => _engine.SceneManager;
+
+ public IRenderer Renderer => _engine.Renderer;
+
+ public string DataFolder => _engine.DataFolder;
+
+ ///
+ /// The engine instance associated with this context.
+ ///
+ private readonly Engine _engine;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The engine instance to use for this context.
+ public EngineContext(Engine parEngine)
+ {
+ _engine = parEngine;
+ }
+
+ public void Close()
+ {
+ _engine.Close();
+ }
+}
\ No newline at end of file
diff --git a/Engine/src/Context/IContext.cs b/Engine/src/Context/IContext.cs
new file mode 100644
index 0000000..0ff9cc8
--- /dev/null
+++ b/Engine/src/Context/IContext.cs
@@ -0,0 +1,42 @@
+using Engine.Graphics;
+using Engine.Input;
+using Engine.Resource;
+using Engine.Scene;
+
+namespace Engine.Context;
+
+///
+/// Defines an interface for the engine's context, providing access to the engine's services.
+///
+public interface IContext
+{
+ ///
+ /// The input handler for the engine.
+ ///
+ public IInputHandler InputHandler { get; }
+
+ ///
+ /// The resource manager for the engine.
+ ///
+ public IResourceManager AssetResourceManager { get; }
+
+ ///
+ /// The scene manager for the engine.
+ ///
+ public ISceneManager SceneManager { get; }
+
+ ///
+ /// The renderer for the engine.
+ ///
+ public IRenderer Renderer { get; }
+
+ ///
+ /// The data folder for the engine.
+ ///
+ public string DataFolder { get; }
+
+ ///
+ /// Closes the engine, shutting down any running systems and freeing resources.
+ ///
+ public void Close();
+}
\ No newline at end of file
diff --git a/Engine/src/Engine.cs b/Engine/src/Engine.cs
index c21964d..4e3acbe 100644
--- a/Engine/src/Engine.cs
+++ b/Engine/src/Engine.cs
@@ -3,6 +3,7 @@ using System.Text;
using Engine.Asset;
using Engine.Asset.Font;
using Engine.Asset.Mesh;
+using Engine.Context;
using Engine.Graphics;
using Engine.Graphics.Pipeline;
using Engine.Graphics.Pixel;
@@ -12,6 +13,7 @@ using Engine.Input;
using Engine.Resource;
using Engine.Resource.Loader;
using Engine.Scene;
+using Engine.Util;
using OpenTK.Mathematics;
using OpenTK.Windowing.Common;
using OpenTK.Windowing.Desktop;
@@ -33,7 +35,7 @@ public sealed class Engine
///
/// The scene manager for managing and updating scenes.
///
- public SceneManager SceneManager { get; } = new();
+ public ISceneManager SceneManager => _sceneManager;
///
/// The resource manager responsible for asset management.
@@ -87,6 +89,11 @@ public sealed class Engine
///
internal Window Window { get; }
+ ///
+ /// The scene manager for managing and updating scenes.
+ ///
+ private readonly SceneManager _sceneManager = new();
+
///
/// The logger instance used by the engine.
///
@@ -154,6 +161,8 @@ public sealed class Engine
Renderer = new Renderer(this, parWidth, parHeight, settings);
Window = new Window(this, Renderer.NativeWindow, parHeadless);
+
+ EngineUtil.SetContext(new EngineContext(this));
}
///
@@ -250,7 +259,7 @@ public sealed class Engine
}
}
- SceneManager.Render();
+ _sceneManager.Render();
}
Monitor.Exit(_sceneLock);
@@ -283,7 +292,7 @@ public sealed class Engine
{
try
{
- SceneManager.Update(deltaTime);
+ _sceneManager.Update(deltaTime);
}
catch (Exception ex)
{
diff --git a/Engine/src/Graphics/GenericRenderer.cs b/Engine/src/Graphics/GenericRenderer.cs
index c019ab7..3eb987f 100644
--- a/Engine/src/Graphics/GenericRenderer.cs
+++ b/Engine/src/Graphics/GenericRenderer.cs
@@ -10,22 +10,22 @@ namespace Engine.Graphics;
///
/// A generic renderer that supports rendering quads, meshes, and text.
///
-public class GenericRenderer : IRenderer
+public class GenericRenderer : IGenericRenderer
{
///
/// Provides functionality to render quads.
///
- public QuadRenderer QuadRenderer => _quadRenderer ??= new QuadRenderer(_engine, 1024 * 8);
+ public IQuadRenderer QuadRenderer => _quadRenderer ??= new QuadRenderer(_engine, 1024 * 8);
///
/// Provides functionality to render any type of mesh.
///
- public AnyMeshRenderer AnyMeshRenderer => _anyMeshRenderer ??= new AnyMeshRenderer(_engine, 1024);
+ public IAnyMeshRenderer AnyMeshRenderer => _anyMeshRenderer ??= new AnyMeshRenderer(_engine, 1024);
///
/// Provides functionality to render text.
///
- public TextRenderer TextRenderer => _textRenderer ??= new TextRenderer(_engine, 1024 * 8);
+ public ITextRenderer TextRenderer => _textRenderer ??= new TextRenderer(_engine, 1024 * 8);
///
/// The framebuffer used for rendering.
@@ -73,13 +73,19 @@ public class GenericRenderer : IRenderer
.Build();
}
- ///
+ ///
+ /// Prepares the renderer for a new frame.
+ ///
public void StartFrame()
{
_frameStarted = true;
}
- ///
+ ///
+ /// Finalizes the rendering pipeline for the current frame.
+ ///
+ /// The projection matrix to use for rendering.
+ /// The view matrix to use for rendering.
public void EndFrame(in Matrix4 parProjectionMatrix, in Matrix4 parViewMatrix)
{
if (!_frameStarted)
@@ -115,7 +121,11 @@ public class GenericRenderer : IRenderer
_frameStarted = false;
}
- ///
+ ///
+ /// Resizes the renderer to accommodate changes in viewport dimensions.
+ ///
+ /// The new width of the viewport.
+ /// The new height of the viewport.
public void Resize(int parWidth, int parHeight)
{
_framebuffer.Resize(parWidth, parHeight);
diff --git a/Engine/src/Graphics/IGenericRenderer.cs b/Engine/src/Graphics/IGenericRenderer.cs
new file mode 100644
index 0000000..de345f7
--- /dev/null
+++ b/Engine/src/Graphics/IGenericRenderer.cs
@@ -0,0 +1,26 @@
+using Engine.Graphics.Render.Mesh;
+using Engine.Graphics.Render.Quad;
+using Engine.Graphics.Render.Text;
+
+namespace Engine.Graphics;
+
+///
+/// Interface defining the essential functionality for a renderer.
+///
+public interface IGenericRenderer
+{
+ ///
+ /// Provides functionality to render quads.
+ ///
+ public IQuadRenderer QuadRenderer { get; }
+
+ ///
+ /// Provides functionality to render any type of mesh.
+ ///
+ public IAnyMeshRenderer AnyMeshRenderer { get; }
+
+ ///
+ /// Provides functionality to render text.
+ ///
+ public ITextRenderer TextRenderer { get; }
+}
\ No newline at end of file
diff --git a/Engine/src/Graphics/IRenderer.cs b/Engine/src/Graphics/IRenderer.cs
index 1c9f9f6..aaaf015 100644
--- a/Engine/src/Graphics/IRenderer.cs
+++ b/Engine/src/Graphics/IRenderer.cs
@@ -1,28 +1,14 @@
-using OpenTK.Mathematics;
+using Engine.Graphics.Pipeline;
namespace Engine.Graphics;
-///
-/// Interface defining the essential functionality for a renderer.
-///
-internal interface IRenderer
+public interface IRenderer
{
///
- /// Prepares the renderer for a new frame.
+ /// Retrieves the renderer for the specified render layer.
///
- public void StartFrame();
-
- ///
- /// Finalizes the rendering pipeline for the current frame.
- ///
- /// The projection matrix to use for rendering.
- /// The view matrix to use for rendering.
- public void EndFrame(in Matrix4 parProjectionMatrix, in Matrix4 parViewMatrix);
-
- ///
- /// Resizes the renderer to accommodate changes in viewport dimensions.
- ///
- /// The new width of the viewport.
- /// The new height of the viewport.
- public void Resize(int parWidth, int parHeight);
+ /// The render layer to retrieve.
+ /// The for the specified render layer.
+ /// Thrown if the render layer does not exist.
+ public IGenericRenderer this[RenderLayer parRenderLayer] { get; }
}
\ No newline at end of file
diff --git a/Engine/src/Graphics/Render/Mesh/AnyMeshRenderer.cs b/Engine/src/Graphics/Render/Mesh/AnyMeshRenderer.cs
index aec0807..c30be33 100644
--- a/Engine/src/Graphics/Render/Mesh/AnyMeshRenderer.cs
+++ b/Engine/src/Graphics/Render/Mesh/AnyMeshRenderer.cs
@@ -7,7 +7,7 @@ namespace Engine.Graphics.Render.Mesh;
/// A renderer class that manages multiple meshes and delegates rendering to individual mesh renderers.
/// Handles batching of mesh instances and ensures that only the necessary mesh renderers are created.
///
-public class AnyMeshRenderer
+public class AnyMeshRenderer : IAnyMeshRenderer
{
///
/// A dictionary that maps each mesh to its corresponding .
@@ -40,14 +40,8 @@ public class AnyMeshRenderer
_program = parEngine.EngineResourceManager.Load("shader/mesh");
}
- ///
- /// Commits an instance of a mesh to the renderer, adding it to the render queue with the specified model matrix and optional texture.
- /// If the mesh is not already being tracked, a new will be created for it.
- ///
- /// The mesh to render.
- /// The model transformation matrix to apply to the mesh.
- /// An optional texture to apply to the mesh. If null, no texture is applied.
- public void Commit(Asset.Mesh.Mesh parMesh, Matrix4 parModelMatrix, Texture.Texture? parAlbedo = null)
+ ///
+ public void Commit(Asset.Mesh.Mesh parMesh, in Matrix4 parModelMatrix, Texture.Texture? parAlbedo = null)
{
if (_meshRenderers.TryGetValue(parMesh, out var meshRenderer))
{
diff --git a/Engine/src/Graphics/Render/Mesh/IAnyMeshRenderer.cs b/Engine/src/Graphics/Render/Mesh/IAnyMeshRenderer.cs
new file mode 100644
index 0000000..7c3988b
--- /dev/null
+++ b/Engine/src/Graphics/Render/Mesh/IAnyMeshRenderer.cs
@@ -0,0 +1,18 @@
+using OpenTK.Mathematics;
+
+namespace Engine.Graphics.Render.Mesh;
+
+///
+/// Defines an interface for a renderer that can render any type of mesh.
+///
+public interface IAnyMeshRenderer
+{
+ ///
+ /// Commits an instance of a mesh to the renderer, adding it to the render queue with the specified model matrix and optional texture.
+ /// If the mesh is not already being tracked, a new will be created for it.
+ ///
+ /// The mesh to render.
+ /// The model transformation matrix to apply to the mesh.
+ /// An optional texture to apply to the mesh. If null, no texture is applied.
+ public void Commit(Asset.Mesh.Mesh parMesh, in Matrix4 parModelMatrix, Texture.Texture? parAlbedo = null);
+}
\ No newline at end of file
diff --git a/Engine/src/Graphics/Render/Mesh/MeshRenderer.cs b/Engine/src/Graphics/Render/Mesh/MeshRenderer.cs
index 975268a..7b769d7 100644
--- a/Engine/src/Graphics/Render/Mesh/MeshRenderer.cs
+++ b/Engine/src/Graphics/Render/Mesh/MeshRenderer.cs
@@ -52,7 +52,7 @@ public class MeshRenderer : InstancedRenderer
/// The model transformation matrix for this instance.
/// An optional texture to apply to the mesh. If null, no texture is applied.
- public void Commit(Matrix4 parModelMatrix, Texture.Texture? parTexture = null)
+ public void Commit(in Matrix4 parModelMatrix, Texture.Texture? parTexture = null)
{
if (_queuedInstanceCount >= _instanceCount)
{
diff --git a/Engine/src/Graphics/Render/Quad/IQuadRenderer.cs b/Engine/src/Graphics/Render/Quad/IQuadRenderer.cs
new file mode 100644
index 0000000..4c9035d
--- /dev/null
+++ b/Engine/src/Graphics/Render/Quad/IQuadRenderer.cs
@@ -0,0 +1,17 @@
+using OpenTK.Mathematics;
+
+namespace Engine.Graphics.Render.Quad;
+
+///
+/// Defines an interface for a renderer that can render quads.
+///
+public interface IQuadRenderer
+{
+ ///
+ /// Commits an instance to the renderer, adding it to the queue with the specified model matrix, color, and optional texture.
+ ///
+ /// The model transformation matrix for this instance.
+ /// The color to apply to this instance.
+ /// An optional texture to apply to the quad. If null, no texture is applied.
+ public void Commit(in Matrix4 parModelMatrix, in Vector4 parColor, Texture.Texture? parTexture = null);
+}
\ No newline at end of file
diff --git a/Engine/src/Graphics/Render/Quad/QuadRenderer.cs b/Engine/src/Graphics/Render/Quad/QuadRenderer.cs
index 79fc185..d37f475 100644
--- a/Engine/src/Graphics/Render/Quad/QuadRenderer.cs
+++ b/Engine/src/Graphics/Render/Quad/QuadRenderer.cs
@@ -9,7 +9,7 @@ namespace Engine.Graphics.Render.Quad;
/// A renderer class for rendering quadrilaterals (quads) using instancing.
/// Supports dynamic texture binding and manages the state for rendering multiple instances of quads.
///
-public class QuadRenderer : InstancedRenderer
+public class QuadRenderer : InstancedRenderer, IQuadRenderer
{
///
/// Maps textures to texture units with a limit of 16 texture units.
@@ -47,12 +47,7 @@ public class QuadRenderer : InstancedRenderer
- /// Commits an instance to the renderer, adding it to the queue with the specified model matrix, color, and optional texture.
- ///
- /// The model transformation matrix for this instance.
- /// The color to apply to this instance.
- /// An optional texture to apply to the quad. If null, no texture is applied.
+ ///
public void Commit(in Matrix4 parModelMatrix, in Vector4 parColor, Texture.Texture? parTexture = null)
{
if (_queuedInstanceCount >= _instanceCount)
diff --git a/Engine/src/Graphics/Render/Text/ITextRenderer.cs b/Engine/src/Graphics/Render/Text/ITextRenderer.cs
new file mode 100644
index 0000000..c75e388
--- /dev/null
+++ b/Engine/src/Graphics/Render/Text/ITextRenderer.cs
@@ -0,0 +1,19 @@
+using Engine.Asset.Font;
+using OpenTK.Mathematics;
+
+namespace Engine.Graphics.Render.Text;
+
+///
+/// Defines an interface for a renderer that can render text.
+///
+public interface ITextRenderer
+{
+ ///
+ /// Commits a string of text to the renderer, creating the necessary glyphs and adding them to the render queue.
+ ///
+ /// The font to use for rendering the text.
+ /// The text string to render.
+ /// The color to apply to the text.
+ /// The model transformation matrix to apply to the text.
+ public void Commit(Font parFont, string parText, in Vector4 parColor, in Matrix4 parModelMatrix);
+}
\ No newline at end of file
diff --git a/Engine/src/Graphics/Render/Text/TextRenderer.cs b/Engine/src/Graphics/Render/Text/TextRenderer.cs
index 41ad085..757062c 100644
--- a/Engine/src/Graphics/Render/Text/TextRenderer.cs
+++ b/Engine/src/Graphics/Render/Text/TextRenderer.cs
@@ -11,7 +11,7 @@ namespace Engine.Graphics.Render.Text;
/// A renderer class for rendering text using glyphs from a font atlas.
/// Handles dynamic font rendering with support for textures.
///
-public class TextRenderer
+public class TextRenderer : ITextRenderer
{
///
/// The shader program used for rendering the text.
@@ -80,14 +80,8 @@ public class TextRenderer
_vertexArray.BindVertexBuffer(_glyphVertexBuffer);
}
- ///
- /// Commits a string of text to the renderer, creating the necessary glyphs and adding them to the render queue.
- ///
- /// The font to use for rendering the text.
- /// The text string to render.
- /// The color to apply to the text.
- /// The model transformation matrix to apply to the text.
- public void Commit(Font parFont, string parText, Vector4 parColor, in Matrix4 parModelMatrix)
+ ///
+ public void Commit(Font parFont, string parText, in Vector4 parColor, in Matrix4 parModelMatrix)
{
if (_queuedCharacterCount >= _characterCount)
{
diff --git a/Engine/src/Graphics/Renderer.cs b/Engine/src/Graphics/Renderer.cs
index 235e533..592abdd 100644
--- a/Engine/src/Graphics/Renderer.cs
+++ b/Engine/src/Graphics/Renderer.cs
@@ -10,7 +10,7 @@ namespace Engine.Graphics;
///
/// Handles the rendering pipeline, manages render layers, and provides tools for rendering graphics in the engine.
///
-public class Renderer
+public class Renderer : IRenderer
{
///
/// The width of the viewport.
@@ -89,13 +89,8 @@ public class Renderer
}
}
- ///
- /// Retrieves the renderer for the specified render layer.
- ///
- /// The render layer to retrieve.
- /// The for the specified render layer.
- /// Thrown if the render layer does not exist.
- public GenericRenderer this[RenderLayer parRenderLayer]
+ ///
+ public IGenericRenderer this[RenderLayer parRenderLayer]
{
get
{
diff --git a/Engine/src/Scene/Component/BuiltIn/Renderer/Box2DRenderer.cs b/Engine/src/Scene/Component/BuiltIn/Renderer/Box2DRenderer.cs
index 6e1636e..5b3f164 100644
--- a/Engine/src/Scene/Component/BuiltIn/Renderer/Box2DRenderer.cs
+++ b/Engine/src/Scene/Component/BuiltIn/Renderer/Box2DRenderer.cs
@@ -1,5 +1,6 @@
using Engine.Graphics.Pipeline;
using Engine.Graphics.Texture;
+using Engine.Util;
using OpenTK.Mathematics;
namespace Engine.Scene.Component.BuiltIn.Renderer;
@@ -32,7 +33,7 @@ public class Box2DRenderer : Component
///
public override void Render()
{
- Engine.Instance.Renderer[RenderLayer].QuadRenderer
+ EngineUtil.Renderer[RenderLayer].QuadRenderer
.Commit(GameObject.Transform.FullTransformMatrix, Color, Texture);
}
}
\ No newline at end of file
diff --git a/Engine/src/Scene/Component/BuiltIn/Renderer/MeshRenderer.cs b/Engine/src/Scene/Component/BuiltIn/Renderer/MeshRenderer.cs
index 9d73e6e..63b4a2f 100644
--- a/Engine/src/Scene/Component/BuiltIn/Renderer/MeshRenderer.cs
+++ b/Engine/src/Scene/Component/BuiltIn/Renderer/MeshRenderer.cs
@@ -1,6 +1,7 @@
using Engine.Asset.Mesh;
using Engine.Graphics.Pipeline;
using Engine.Graphics.Texture;
+using Engine.Util;
namespace Engine.Scene.Component.BuiltIn.Renderer;
@@ -27,7 +28,7 @@ public class MeshRenderer : Component
///
public override void Render()
{
- Engine.Instance.Renderer[RenderLayer].AnyMeshRenderer
+ EngineUtil.Renderer[RenderLayer].AnyMeshRenderer
.Commit(Mesh, GameObject.Transform.FullTransformMatrix, Albedo);
}
}
\ No newline at end of file
diff --git a/Engine/src/Scene/Component/BuiltIn/Renderer/TextRenderer.cs b/Engine/src/Scene/Component/BuiltIn/Renderer/TextRenderer.cs
index 8eecd02..7839e03 100644
--- a/Engine/src/Scene/Component/BuiltIn/Renderer/TextRenderer.cs
+++ b/Engine/src/Scene/Component/BuiltIn/Renderer/TextRenderer.cs
@@ -1,5 +1,6 @@
using Engine.Asset.Font;
using Engine.Graphics.Pipeline;
+using Engine.Util;
using OpenTK.Mathematics;
namespace Engine.Scene.Component.BuiltIn.Renderer;
@@ -12,7 +13,7 @@ public class TextRenderer : Component
///
/// The font used for rendering the text.
///
- public Font Font { get; set; } = null!;
+ public Font? Font { get; set; }
///
/// The color of the text.
@@ -37,12 +38,12 @@ public class TextRenderer : Component
///
public override void Render()
{
- if (Text == null)
+ if (Text == null || Font == null)
{
return;
}
- Engine.Instance.Renderer[RenderLayer].TextRenderer
+ EngineUtil.Renderer[RenderLayer].TextRenderer
.Commit(Font, Text, Color, GameObject.Transform.FullTransformMatrix);
}
}
\ No newline at end of file
diff --git a/Engine/src/Scene/ISceneManager.cs b/Engine/src/Scene/ISceneManager.cs
new file mode 100644
index 0000000..f48d810
--- /dev/null
+++ b/Engine/src/Scene/ISceneManager.cs
@@ -0,0 +1,18 @@
+namespace Engine.Scene;
+
+///
+/// Defines an interface for a scene manager that handles the current scene and scene transitions.
+///
+public interface ISceneManager
+{
+ ///
+ /// The current scene, or null if no scene is active.
+ ///
+ public Scene? CurrentScene { get; }
+
+ ///
+ /// Transitions to the specified scene.
+ ///
+ /// The generator function for the scene to transition to.
+ public void TransitionTo(Func? parScene);
+}
\ No newline at end of file
diff --git a/Engine/src/Scene/Scene.cs b/Engine/src/Scene/Scene.cs
index 7b79b32..b772068 100644
--- a/Engine/src/Scene/Scene.cs
+++ b/Engine/src/Scene/Scene.cs
@@ -17,7 +17,7 @@ public class Scene : IUpdate, IRender
///
/// The time scale for updating the scene. A value of 1.0 means normal speed.
///
- public float TimeScale { get; set; } = 1.0f;
+ public double TimeScale { get; set; } = 1.0;
///
/// A hierarchy of game objects in the scene.
@@ -206,7 +206,7 @@ public class Scene : IUpdate, IRender
///
/// Processes changes in the hierarchy and scene actions.
///
- private void ProcessChanges()
+ internal void ProcessChanges()
{
Hierarchy.ProcessChanges();
diff --git a/Engine/src/Scene/SceneManager.cs b/Engine/src/Scene/SceneManager.cs
index 5910237..001f369 100644
--- a/Engine/src/Scene/SceneManager.cs
+++ b/Engine/src/Scene/SceneManager.cs
@@ -3,7 +3,7 @@
///
/// Manages the current scene in the game, handles scene transitions, and facilitates updating and rendering the current scene.
///
-public class SceneManager : IUpdate, IRender
+public class SceneManager : ISceneManager, IUpdate, IRender
{
///
/// The current scene being managed by the scene manager.
diff --git a/Engine/src/Util/EngineUtil.cs b/Engine/src/Util/EngineUtil.cs
index 4f44094..434750f 100644
--- a/Engine/src/Util/EngineUtil.cs
+++ b/Engine/src/Util/EngineUtil.cs
@@ -1,4 +1,6 @@
-using Engine.Input;
+using Engine.Context;
+using Engine.Graphics;
+using Engine.Input;
using Engine.Resource;
using Engine.Scene;
@@ -13,38 +15,47 @@ public static class EngineUtil
///
/// The engine's input handler, which processes user input.
///
- public static IInputHandler InputHandler => Engine.Instance.InputHandler!;
+ public static IInputHandler InputHandler => CONTEXT_INSTANCE.InputHandler;
///
/// The engine's scene manager, which handles the current scene and scene transitions.
///
- public static SceneManager SceneManager => Engine.Instance.SceneManager;
+ public static ISceneManager SceneManager => CONTEXT_INSTANCE.SceneManager;
///
/// The engine's asset resource manager, which handles loading and caching of assets.
///
- public static IResourceManager AssetResourceManager => Engine.Instance.AssetResourceManager;
+ public static IResourceManager AssetResourceManager => CONTEXT_INSTANCE.AssetResourceManager;
+
+ ///
+ /// The engine's renderer, which handles rendering operations.
+ ///
+ public static IRenderer Renderer => CONTEXT_INSTANCE.Renderer;
///
/// The engine's data folder, which contains assets and other data files.
///
- public static string DataFolder => Engine.Instance.DataFolder;
-
- ///
- /// Creates a game object and adds it to the current scene.
- ///
- /// The game object to be added to the scene.
- public static void CreateObject(GameObject parGameObject)
- {
- var scene = Engine.Instance.SceneManager.CurrentScene!;
- scene.Add(parGameObject);
- }
+ public static string DataFolder => CONTEXT_INSTANCE.DataFolder;
///
/// Closes the engine, shutting down any running systems and freeing resources.
///
public static void Close()
{
- Engine.Instance.Close();
+ CONTEXT_INSTANCE.Close();
+ }
+
+ ///
+ /// The engine's context, which provides access to the engine's services.
+ ///
+ private static IContext CONTEXT_INSTANCE;
+
+ ///
+ /// Sets the engine's context, allowing access to the engine's services.
+ ///
+ /// The context to use for the engine.
+ internal static void SetContext(IContext parContext)
+ {
+ CONTEXT_INSTANCE = parContext;
}
}
\ No newline at end of file
diff --git a/EngineTests/EngineTests.csproj b/EngineTests/EngineTests.csproj
index a5e5c2e..50cd526 100644
--- a/EngineTests/EngineTests.csproj
+++ b/EngineTests/EngineTests.csproj
@@ -23,10 +23,7 @@
-
-
-
-
+
diff --git a/EngineTests/src/Scene/GameObjectTests.cs b/EngineTests/src/Scene/GameObjectTests.cs
index b186134..da57a57 100644
--- a/EngineTests/src/Scene/GameObjectTests.cs
+++ b/EngineTests/src/Scene/GameObjectTests.cs
@@ -68,4 +68,220 @@ public class GameObjectTests
Assert.Throws(() => _gameObject.ProcessChanges());
}
+
+ [Test]
+ public void AddComponent_ShouldSetGameObjectForAddedComponent()
+ {
+ var testComponent = new TestComponent();
+ _gameObject.AddComponent(testComponent);
+ _gameObject.ProcessChanges();
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(testComponent.GameObject, Is.EqualTo(_gameObject));
+ });
+ }
+
+ [Test]
+ public void AddComponent_ShouldCallAwakeOnAddedComponentOnPreUpdate()
+ {
+ var testComponent = new TestComponent();
+ var wasAwakeCalled = false;
+ testComponent.OnAwake += () => wasAwakeCalled = true;
+
+ _gameObject.AddComponent(testComponent);
+ _gameObject.ProcessChanges();
+ _gameObject.PreUpdate(0);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(wasAwakeCalled, Is.True);
+ });
+ }
+
+ [Test]
+ public void AddComponent_ShouldCallStartOnAddedComponentOnPreUpdate()
+ {
+ var testComponent = new TestComponent();
+ var wasStartCalled = false;
+ testComponent.OnStart += () => wasStartCalled = true;
+
+ _gameObject.AddComponent(testComponent);
+ _gameObject.ProcessChanges();
+ _gameObject.PreUpdate(0);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(wasStartCalled, Is.True);
+ });
+ }
+
+ [Test]
+ public void AddComponent_ShouldCallPreUpdateOnAddedComponentOnPreUpdate()
+ {
+ var testComponent = new TestComponent();
+ var wasPreUpdateCalled = false;
+ testComponent.OnPreUpdate += _ => wasPreUpdateCalled = true;
+
+ _gameObject.AddComponent(testComponent);
+ _gameObject.ProcessChanges();
+ _gameObject.PreUpdate(0);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(wasPreUpdateCalled, Is.True);
+ });
+ }
+
+ [Test]
+ public void AddComponent_ShouldCallUpdateOnAddedComponentOnUpdate()
+ {
+ var testComponent = new TestComponent();
+ var wasUpdateCalled = false;
+ testComponent.OnUpdate += _ => wasUpdateCalled = true;
+
+ _gameObject.AddComponent(testComponent);
+ _gameObject.ProcessChanges();
+ _gameObject.Update(0);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(wasUpdateCalled, Is.True);
+ });
+ }
+
+ [Test]
+ public void AddComponent_ShouldCallPostUpdateOnAddedComponentOnPostUpdate()
+ {
+ var testComponent = new TestComponent();
+ var wasPostUpdateCalled = false;
+ testComponent.OnPostUpdate += _ => wasPostUpdateCalled = true;
+
+ _gameObject.AddComponent(testComponent);
+ _gameObject.ProcessChanges();
+ _gameObject.PostUpdate(0);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(wasPostUpdateCalled, Is.True);
+ });
+ }
+
+ [Test]
+ public void AddComponent_ShouldCallRenderOnAddedComponentOnRender()
+ {
+ var testComponent = new TestComponent();
+ var wasRenderCalled = false;
+ testComponent.OnRender += () => wasRenderCalled = true;
+
+ _gameObject.AddComponent(testComponent);
+ _gameObject.ProcessChanges();
+ _gameObject.Render();
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(wasRenderCalled, Is.True);
+ });
+ }
+
+ [Test]
+ public void AddComponent_ShouldCallDestroyOnAddedComponentOnDestroy()
+ {
+ var testComponent = new TestComponent();
+ var wasDestroyCalled = false;
+ testComponent.OnDestroy += () => wasDestroyCalled = true;
+
+ _gameObject.AddComponent(testComponent);
+ _gameObject.ProcessChanges();
+ _gameObject.Destroy();
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(wasDestroyCalled, Is.True);
+ });
+ }
+
+ [Test]
+ public void AddComponent_ShouldCallDisableOnAddedComponentOnDisable()
+ {
+ var testComponent = new TestComponent();
+ var wasDisableCalled = false;
+ testComponent.OnDisable += () => wasDisableCalled = true;
+
+ _gameObject.IsEnabled = false;
+
+ _gameObject.AddComponent(testComponent);
+ _gameObject.ProcessChanges();
+ _gameObject.Update(0);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(wasDisableCalled, Is.True);
+ });
+ }
+
+ [Test]
+ public void AddComponent_ShouldCallEnableOnAddedComponentOnEnable()
+ {
+ var testComponent = new TestComponent();
+ var wasEnableCalled = false;
+ testComponent.OnEnable += () => wasEnableCalled = true;
+
+ _gameObject.IsEnabled = false;
+
+ _gameObject.AddComponent(testComponent);
+ _gameObject.ProcessChanges();
+ _gameObject.Update(0);
+
+ _gameObject.IsEnabled = true;
+ _gameObject.ProcessChanges();
+ _gameObject.Update(0);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(wasEnableCalled, Is.True);
+ });
+ }
+
+ [Test]
+ public void RemoveComponent_ShouldThrowIfComponentIsTransform()
+ {
+ Assert.Throws(() => _gameObject.RemoveComponent());
+ }
+
+ [Test]
+ public void RemoveComponent_ShouldThrowIfComponentDoesNotExist()
+ {
+ _gameObject.RemoveComponent();
+
+ Assert.Throws(() => _gameObject.ProcessChanges());
+ }
+
+ [Test]
+ public void GetComponent_ShouldReturnComponentIfExists()
+ {
+ var testComponent = new TestComponent();
+ _gameObject.AddComponent(testComponent);
+ _gameObject.ProcessChanges();
+
+ var component = _gameObject.GetComponent();
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(component, Is.EqualTo(testComponent));
+ });
+ }
+
+ [Test]
+ public void GetComponent_ShouldReturnNullIfComponentDoesNotExist()
+ {
+ _gameObject.ProcessChanges();
+
+ var component = _gameObject.GetComponent();
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(component, Is.Null);
+ });
+ }
}
\ No newline at end of file
diff --git a/EngineTests/src/Scene/SceneTests.cs b/EngineTests/src/Scene/SceneTests.cs
new file mode 100644
index 0000000..333f665
--- /dev/null
+++ b/EngineTests/src/Scene/SceneTests.cs
@@ -0,0 +1,199 @@
+using Engine.Scene;
+
+namespace EngineTests.Scene;
+
+public class SceneTests
+{
+ private Engine.Scene.Scene _scene;
+
+ [SetUp]
+ public void Setup()
+ {
+ _scene = new Engine.Scene.Scene();
+ }
+
+ [Test]
+ public void Enter_ShouldSetSceneToPlaying()
+ {
+ Assert.Multiple(() =>
+ {
+ Assert.That(_scene.IsPlaying, Is.False);
+ });
+
+ _scene.Enter();
+ Assert.Multiple(() =>
+ {
+ Assert.That(_scene.IsPlaying, Is.True);
+ });
+ }
+
+ [Test]
+ public void Exit_ShouldUnsetSceneToPlaying()
+ {
+ _scene.Enter();
+ Assert.Multiple(() =>
+ {
+ Assert.That(_scene.IsPlaying, Is.True);
+ });
+
+ _scene.Exit();
+ Assert.Multiple(() =>
+ {
+ Assert.That(_scene.IsPlaying, Is.False);
+ });
+ }
+
+ [Test]
+ public void Update_ShouldRespectTimeScale()
+ {
+ const double INITIAL_DELTA_TIME = 1.0;
+ const double TIME_SCALE = 2.0;
+
+ var (gameObject, testComponent) = CreateTestGameObject();
+ var actualDeltaTime = 0.0;
+ testComponent.OnUpdate += parDeltaTime => actualDeltaTime = parDeltaTime;
+
+ _scene.Add(gameObject);
+ _scene.Enter();
+
+ _scene.TimeScale = TIME_SCALE;
+ _scene.Update(INITIAL_DELTA_TIME);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(actualDeltaTime, Is.EqualTo(INITIAL_DELTA_TIME * TIME_SCALE));
+ });
+ }
+
+ [Test]
+ public void Add_ShouldSetSceneForAddedGameObject()
+ {
+ var (gameObject, _) = CreateTestGameObject();
+ _scene.Add(gameObject);
+ _scene.ProcessChanges();
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(gameObject.Scene, Is.EqualTo(_scene));
+ });
+ }
+
+ [Test]
+ public void Add_ShouldAddGameObjectToHierarchy()
+ {
+ var (gameObject, _) = CreateTestGameObject();
+ _scene.Add(gameObject);
+ _scene.ProcessChanges();
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(_scene.Hierarchy.Objects, Contains.Item(gameObject));
+ });
+ }
+
+ [Test]
+ public void Remove_ShouldUnsetSceneForRemovedGameObject()
+ {
+ var (gameObject, _) = CreateTestGameObject();
+ _scene.Add(gameObject);
+ _scene.ProcessChanges();
+
+ _scene.Remove(gameObject);
+ _scene.ProcessChanges();
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(gameObject.Scene, Is.Null);
+ });
+ }
+
+ [Test]
+ public void Remove_ShouldRemoveGameObjectFromHierarchy()
+ {
+ var (gameObject, _) = CreateTestGameObject();
+ _scene.Add(gameObject);
+ _scene.ProcessChanges();
+
+ _scene.Remove(gameObject);
+ _scene.ProcessChanges();
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(_scene.Hierarchy.Objects, Is.Not.Contains(gameObject));
+ });
+ }
+
+ [Test]
+ public void FindAllComponents_ShouldReturnAllComponentsCount1()
+ {
+ var (gameObject, testComponent) = CreateTestGameObject();
+ _scene.Add(gameObject);
+ _scene.ProcessChanges();
+
+ var components = _scene.FindAllComponents();
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(components, Contains.Item(testComponent));
+ Assert.That(components, Has.Count.EqualTo(1));
+ });
+ }
+
+ [Test]
+ public void FindAllComponents_ShouldReturnAllComponentsCount2()
+ {
+ var (gameObject1, testComponent1) = CreateTestGameObject();
+ var (gameObject2, testComponent2) = CreateTestGameObject();
+ _scene.Add(gameObject1);
+ _scene.Add(gameObject2);
+ _scene.ProcessChanges();
+
+ var components = _scene.FindAllComponents();
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(components, Contains.Item(testComponent1));
+ Assert.That(components, Contains.Item(testComponent2));
+ Assert.That(components, Has.Count.EqualTo(2));
+ });
+ }
+
+ [Test]
+ public void FindFirstComponent_ShouldReturnFirstComponent()
+ {
+ var (gameObject, testComponent) = CreateTestGameObject();
+ _scene.Add(gameObject);
+ _scene.ProcessChanges();
+
+ var component = _scene.FindFirstComponent();
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(component, Is.EqualTo(testComponent));
+ });
+ }
+
+ [Test]
+ public void FindFirstComponent_ShouldReturnNullIfComponentDoesNotExist()
+ {
+ _scene.ProcessChanges();
+
+ var component = _scene.FindFirstComponent();
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(component, Is.Null);
+ });
+ }
+
+ private static (GameObject, TestComponent) CreateTestGameObject()
+ {
+ var gameObject = new GameObject();
+ var testComponent = new TestComponent();
+
+ gameObject.AddComponent(testComponent);
+ gameObject.ProcessChanges();
+
+ return (gameObject, testComponent);
+ }
+}
\ No newline at end of file
diff --git a/EngineTests/src/Scene/TestComponent.cs b/EngineTests/src/Scene/TestComponent.cs
new file mode 100644
index 0000000..6e4efce
--- /dev/null
+++ b/EngineTests/src/Scene/TestComponent.cs
@@ -0,0 +1,61 @@
+using Engine.Scene.Component;
+
+namespace EngineTests.Scene;
+
+public class TestComponent : Component
+{
+ public event Action? OnAwake;
+ public event Action? OnStart;
+ public event Action? OnPreUpdate;
+ public event Action? OnUpdate;
+ public event Action? OnPostUpdate;
+ public event Action? OnRender;
+ public event Action? OnDestroy;
+ public event Action? OnEnable;
+ public event Action? OnDisable;
+
+ public override void Awake()
+ {
+ OnAwake?.Invoke();
+ }
+
+ public override void Start()
+ {
+ OnStart?.Invoke();
+ }
+
+ public override void PreUpdate(double parDeltaTime)
+ {
+ OnPreUpdate?.Invoke(parDeltaTime);
+ }
+
+ public override void Update(double parDeltaTime)
+ {
+ OnUpdate?.Invoke(parDeltaTime);
+ }
+
+ public override void PostUpdate(double parDeltaTime)
+ {
+ OnPostUpdate?.Invoke(parDeltaTime);
+ }
+
+ public override void Render()
+ {
+ OnRender?.Invoke();
+ }
+
+ public override void Destroy()
+ {
+ OnDestroy?.Invoke();
+ }
+
+ public override void Enable()
+ {
+ OnEnable?.Invoke();
+ }
+
+ public override void Disable()
+ {
+ OnDisable?.Invoke();
+ }
+}
\ No newline at end of file
diff --git a/GameTests/GameTests.csproj b/GameTests/GameTests.csproj
new file mode 100644
index 0000000..48a106a
--- /dev/null
+++ b/GameTests/GameTests.csproj
@@ -0,0 +1,30 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+ false
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/GameTests/src/HealthControllerTest.cs b/GameTests/src/HealthControllerTest.cs
new file mode 100644
index 0000000..f0cc6c4
--- /dev/null
+++ b/GameTests/src/HealthControllerTest.cs
@@ -0,0 +1,52 @@
+using DoomDeathmatch;
+using DoomDeathmatch.Component.MVC.Health;
+using DoomDeathmatch.Component.MVC.Weapon;
+
+namespace GameTests;
+
+public class HealthControllerTest : SceneMockTest
+{
+ [Test]
+ public void HealthController_HealthShouldBeCappedToMaxHealthWhenHealed()
+ {
+ const float MAX_HEALTH = 100;
+
+ var healthController = new HealthController(MAX_HEALTH);
+ var gameObject = GameObjectUtil.CreateGameObject(Scene, [
+ healthController
+ ]);
+
+ Scene.Add(gameObject);
+ SceneManager.Update(1);
+
+ healthController.Heal(MAX_HEALTH * 2);
+ SceneManager.Update(1);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(healthController.Health, Is.EqualTo(MAX_HEALTH));
+ });
+ }
+
+ [Test]
+ public void HealthController_HealthShouldBeCappedToZeroWhenHealedBelowZero()
+ {
+ const float MAX_HEALTH = 100;
+
+ var healthController = new HealthController(MAX_HEALTH);
+ var gameObject = GameObjectUtil.CreateGameObject(Scene, [
+ healthController
+ ]);
+
+ Scene.Add(gameObject);
+ SceneManager.Update(1);
+
+ healthController.TakeDamage(MAX_HEALTH * 2);
+ SceneManager.Update(1);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(healthController.Health, Is.EqualTo(0));
+ });
+ }
+}
\ No newline at end of file
diff --git a/GameTests/src/MovementComponentTest.cs b/GameTests/src/MovementComponentTest.cs
new file mode 100644
index 0000000..d4029ea
--- /dev/null
+++ b/GameTests/src/MovementComponentTest.cs
@@ -0,0 +1,154 @@
+using DoomDeathmatch;
+using DoomDeathmatch.Component.MVC;
+using DoomDeathmatch.Component.Physics;
+using DoomDeathmatch.Component.Util;
+using DoomDeathmatch.Scene.Play.Prefab;
+using Engine.Input;
+using OpenTK.Mathematics;
+
+namespace GameTests;
+
+public class MovementComponentTest : SceneMockTest
+{
+ [Test]
+ public void PlayerMovement_ShouldMovePlayerLeftWithKeyboardA()
+ {
+ var playerMovementComponent = new PlayerMovementComponent();
+ var movementComponent = new MovementComponent();
+ var playerObject = GameObjectUtil.CreateGameObject(Scene, [
+ new RigidbodyComponent(),
+ movementComponent,
+ playerMovementComponent
+ ]);
+
+ Scene.Add(playerObject);
+ SceneManager.Update(1);
+
+ InputHandler.PressedKeyboardButtons.Add(KeyboardButtonCode.A);
+ SceneManager.Update(1);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(playerObject.Transform.Translation.X, Is.EqualTo(-movementComponent.Speed));
+ });
+ }
+
+ [Test]
+ public void PlayerMovement_ShouldMovePlayerRightWithKeyboardD()
+ {
+ var playerMovementComponent = new PlayerMovementComponent();
+ var movementComponent = new MovementComponent();
+ var playerObject = GameObjectUtil.CreateGameObject(Scene, [
+ new RigidbodyComponent(),
+ movementComponent,
+ playerMovementComponent
+ ]);
+
+ Scene.Add(playerObject);
+ SceneManager.Update(1);
+
+ InputHandler.PressedKeyboardButtons.Add(KeyboardButtonCode.D);
+ SceneManager.Update(1);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(playerObject.Transform.Translation.X, Is.EqualTo(movementComponent.Speed));
+ });
+ }
+
+ [Test]
+ public void PlayerMovement_ShouldMovePlayerUpWithKeyboardW()
+ {
+ var playerMovementComponent = new PlayerMovementComponent();
+ var movementComponent = new MovementComponent();
+ var playerObject = GameObjectUtil.CreateGameObject(Scene, [
+ new RigidbodyComponent(),
+ movementComponent,
+ playerMovementComponent
+ ]);
+
+ Scene.Add(playerObject);
+ SceneManager.Update(1);
+
+ InputHandler.PressedKeyboardButtons.Add(KeyboardButtonCode.W);
+ SceneManager.Update(1);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(playerObject.Transform.Translation.Y, Is.EqualTo(movementComponent.Speed));
+ });
+ }
+
+ [Test]
+ public void PlayerMovement_ShouldMovePlayerDownWithKeyboardS()
+ {
+ var playerMovementComponent = new PlayerMovementComponent();
+ var movementComponent = new MovementComponent();
+ var playerObject = GameObjectUtil.CreateGameObject(Scene, [
+ new RigidbodyComponent(),
+ movementComponent,
+ playerMovementComponent
+ ]);
+
+ Scene.Add(playerObject);
+ SceneManager.Update(1);
+
+ InputHandler.PressedKeyboardButtons.Add(KeyboardButtonCode.S);
+ SceneManager.Update(1);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(playerObject.Transform.Translation.Y, Is.EqualTo(-movementComponent.Speed));
+ });
+ }
+
+ [Test]
+ public void PlayerMovement_ShouldRotatePlayerRightWithKeyboardQ()
+ {
+ var playerMovementComponent = new PlayerMovementComponent();
+ var movementComponent = new MovementComponent();
+ var playerObject = GameObjectUtil.CreateGameObject(Scene, [
+ new RigidbodyComponent(),
+ movementComponent,
+ playerMovementComponent
+ ]);
+
+ Scene.Add(playerObject);
+ SceneManager.Update(1);
+
+ InputHandler.PressedKeyboardButtons.Add(KeyboardButtonCode.Q);
+ SceneManager.Update(1);
+
+ Console.WriteLine(playerObject.Transform.Rotation.ToEulerAngles());
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(playerObject.Transform.Rotation.ToEulerAngles().Z,
+ Is.EqualTo(MathHelper.DegreesToRadians(movementComponent.RotationSpeed)));
+ });
+ }
+
+ [Test]
+ public void PlayerMovement_ShouldRotatePlayerRightWithKeyboardE()
+ {
+ var playerMovementComponent = new PlayerMovementComponent();
+ var movementComponent = new MovementComponent();
+ var playerObject = GameObjectUtil.CreateGameObject(Scene, [
+ new RigidbodyComponent(),
+ movementComponent,
+ playerMovementComponent
+ ]);
+
+ Scene.Add(playerObject);
+ SceneManager.Update(1);
+
+ InputHandler.PressedKeyboardButtons.Add(KeyboardButtonCode.E);
+ SceneManager.Update(1);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(playerObject.Transform.Rotation.ToEulerAngles().Z,
+ Is.EqualTo(-MathHelper.DegreesToRadians(movementComponent.RotationSpeed)));
+ });
+ }
+}
\ No newline at end of file
diff --git a/GameTests/src/SceneMockTest.cs b/GameTests/src/SceneMockTest.cs
new file mode 100644
index 0000000..a2a59f0
--- /dev/null
+++ b/GameTests/src/SceneMockTest.cs
@@ -0,0 +1,51 @@
+using Engine.Graphics;
+using Engine.Graphics.Pipeline;
+using Engine.Scene;
+using TestUtil;
+using TestUtil.Renderer;
+
+namespace GameTests;
+
+public abstract class SceneMockTest
+{
+ public Scene Scene => _scene;
+ public MockContext Context => _context;
+ public MockInputHandler InputHandler => _inputHandler;
+ public MockResourceManager ResourceManager => _resourceManager;
+ public SceneManager SceneManager => _sceneManager;
+ public MockRenderer Renderer => _renderer;
+
+ private Scene _scene;
+
+ private MockContext _context;
+ private MockInputHandler _inputHandler;
+ private MockResourceManager _resourceManager;
+ private SceneManager _sceneManager;
+ private MockRenderer _renderer;
+
+ [SetUp]
+ public void SetUp()
+ {
+ _scene = new Scene();
+
+ _inputHandler = new MockInputHandler();
+ _resourceManager = new MockResourceManager();
+ _sceneManager = new SceneManager();
+ _renderer = new MockRenderer(new Dictionary
+ {
+ [RenderLayer.DEFAULT] = new MockGenericRenderer(new MockQuadRenderer(), new MockAnyMeshRenderer(),
+ new MockTextRenderer()),
+ [RenderLayer.OVERLAY] = new MockGenericRenderer(new MockQuadRenderer(), new MockAnyMeshRenderer(),
+ new MockTextRenderer()),
+ [RenderLayer.HUD] = new MockGenericRenderer(new MockQuadRenderer(), new MockAnyMeshRenderer(),
+ new MockTextRenderer())
+ });
+
+ _context = new MockContext(_inputHandler, _resourceManager, _sceneManager, _renderer, "");
+
+ MockContext.SetMockContext(_context);
+
+ _sceneManager.TransitionTo(() => Scene);
+ _sceneManager.Update(0);
+ }
+}
\ No newline at end of file
diff --git a/GameTests/src/WeaponControllerTest.cs b/GameTests/src/WeaponControllerTest.cs
new file mode 100644
index 0000000..48c70c4
--- /dev/null
+++ b/GameTests/src/WeaponControllerTest.cs
@@ -0,0 +1,104 @@
+using DoomDeathmatch;
+using DoomDeathmatch.Component.MVC.Weapon;
+using DoomDeathmatch.Script.Model.Weapon;
+
+namespace GameTests;
+
+public class WeaponControllerTest : SceneMockTest
+{
+ [Test]
+ public void WeaponController_DefaultWeaponShouldBeSelected()
+ {
+ var weapon = WeaponData.Pistol;
+ var weaponController = new WeaponController(weapon);
+ var gameObject = GameObjectUtil.CreateGameObject(Scene, [
+ weaponController
+ ]);
+
+ Scene.Add(gameObject);
+ SceneManager.Update(1);
+
+ weaponController.SelectWeapon(0);
+ SceneManager.Update(1);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(weaponController.WeaponData, Is.EqualTo(weapon));
+ });
+ }
+
+ [Test]
+ public void WeaponController_CannotRemoveDefaultWeapon()
+ {
+ var weapon = WeaponData.Pistol;
+ var weaponController = new WeaponController(weapon);
+ var gameObject = GameObjectUtil.CreateGameObject(Scene, [
+ weaponController
+ ]);
+
+ Scene.Add(gameObject);
+ SceneManager.Update(1);
+
+ weaponController.RemoveWeapon(0);
+ SceneManager.Update(1);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(weaponController.WeaponData, Is.EqualTo(weapon));
+ });
+ }
+
+ [Test]
+ public void WeaponController_CannotSelectNonExistentWeapon()
+ {
+ var weapon = WeaponData.Pistol;
+ var weaponController = new WeaponController(weapon);
+ var gameObject = GameObjectUtil.CreateGameObject(Scene, [
+ weaponController
+ ]);
+
+ Scene.Add(gameObject);
+ SceneManager.Update(1);
+
+ weaponController.SelectWeapon(1);
+ SceneManager.Update(1);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(weaponController.WeaponData, Is.EqualTo(weapon));
+ });
+ }
+
+ [Test]
+ public void WeaponController_ShouldAddAmmoOnWeaponMerge()
+ {
+ const int INITIAL_AMMO1 = 10;
+ const int INITIAL_AMMO2 = 10;
+
+ var weapon1 = WeaponData.Pistol;
+ var weapon2 = WeaponData.Pistol;
+ weapon1.Ammo = INITIAL_AMMO1;
+ weapon2.Ammo = INITIAL_AMMO2;
+
+ var weaponController = new WeaponController(weapon1);
+ var gameObject = GameObjectUtil.CreateGameObject(Scene, [
+ weaponController
+ ]);
+
+ Scene.Add(gameObject);
+ SceneManager.Update(1);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(weaponController.WeaponData.Ammo, Is.EqualTo(INITIAL_AMMO1));
+ });
+
+ weaponController.AddOrMergeWeapon(weapon2);
+ SceneManager.Update(1);
+
+ Assert.Multiple(() =>
+ {
+ Assert.That(weaponController.WeaponData.Ammo, Is.EqualTo(INITIAL_AMMO1 + INITIAL_AMMO2));
+ });
+ }
+}
\ No newline at end of file
diff --git a/PresenterConsole/assets/shader/ascii.shader b/PresenterConsole/assets/shader/ascii.shader
index 6e79aac..c58eef7 100644
--- a/PresenterConsole/assets/shader/ascii.shader
+++ b/PresenterConsole/assets/shader/ascii.shader
@@ -81,36 +81,36 @@ float perceptualColorDistance(vec3 color1, vec3 color2) {
return dot(delta, delta);
}
-//int findMostPerceptuallyAccurateColor(vec3 color) {
-// int bestMatchIndex = 0;
-// float minDistance = perceptualColorDistance(color, ConsoleColorVec3[0]);
-//
-// for (int i = 1; i < 16; i++) {
-// float currentDistance = perceptualColorDistance(color, ConsoleColorVec3[i]);
-// if (currentDistance < minDistance) {
-// minDistance = currentDistance;
-// bestMatchIndex = i;
-// }
-// }
-//
-// return bestMatchIndex;
-//}
-
int findMostPerceptuallyAccurateColor(vec3 color) {
- int closestIndex = 0;
- float minDistance = distance(color, ConsoleColorVec3[0]);
+ int bestMatchIndex = 0;
+ float minDistance = perceptualColorDistance(color, ConsoleColorVec3[0]);
for (int i = 1; i < 16; i++) {
- float dist = distance(color, ConsoleColorVec3[i]);
- if (dist < minDistance) {
- minDistance = dist;
- closestIndex = i;
+ float currentDistance = perceptualColorDistance(color, ConsoleColorVec3[i]);
+ if (currentDistance < minDistance) {
+ minDistance = currentDistance;
+ bestMatchIndex = i;
}
}
- return closestIndex;
+ return bestMatchIndex;
}
+//int findMostPerceptuallyAccurateColor(vec3 color) {
+// int closestIndex = 0;
+// float minDistance = distance(color, ConsoleColorVec3[0]);
+//
+// for (int i = 1; i < 16; i++) {
+// float dist = distance(color, ConsoleColorVec3[i]);
+// if (dist < minDistance) {
+// minDistance = dist;
+// closestIndex = i;
+// }
+// }
+//
+// return closestIndex;
+//}
+
// Enhanced luminosity calculation considering human perception
float calculatePerceptualLuminance(vec3 color) {
// BT.709 luminance coefficients with slight adjustment
diff --git a/TestUtil/TestUtil.csproj b/TestUtil/TestUtil.csproj
new file mode 100644
index 0000000..e1baa34
--- /dev/null
+++ b/TestUtil/TestUtil.csproj
@@ -0,0 +1,13 @@
+
+
+
+ net8.0
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/TestUtil/src/MockContext.cs b/TestUtil/src/MockContext.cs
new file mode 100644
index 0000000..e65e4cb
--- /dev/null
+++ b/TestUtil/src/MockContext.cs
@@ -0,0 +1,40 @@
+using Engine.Context;
+using Engine.Graphics;
+using Engine.Input;
+using Engine.Resource;
+using Engine.Scene;
+using Engine.Util;
+
+namespace TestUtil;
+
+public class MockContext : IContext
+{
+ public Action? OnClose;
+
+ public IInputHandler InputHandler { get; }
+ public IResourceManager AssetResourceManager { get; }
+ public ISceneManager SceneManager { get; }
+ public IRenderer Renderer { get; }
+ public string DataFolder { get; }
+
+ public MockContext(IInputHandler parInputHandler, IResourceManager parAssetResourceManager,
+ ISceneManager parSceneManager, IRenderer parRenderer,
+ string parDataFolder)
+ {
+ InputHandler = parInputHandler;
+ AssetResourceManager = parAssetResourceManager;
+ SceneManager = parSceneManager;
+ Renderer = parRenderer;
+ DataFolder = parDataFolder;
+ }
+
+ public static void SetMockContext(MockContext parContext)
+ {
+ EngineUtil.SetContext(parContext);
+ }
+
+ public void Close()
+ {
+ OnClose?.Invoke();
+ }
+}
\ No newline at end of file
diff --git a/TestUtil/src/MockInputHandler.cs b/TestUtil/src/MockInputHandler.cs
new file mode 100644
index 0000000..1f2a083
--- /dev/null
+++ b/TestUtil/src/MockInputHandler.cs
@@ -0,0 +1,46 @@
+using System.Globalization;
+using Engine.Input;
+using OpenTK.Mathematics;
+
+namespace TestUtil;
+
+public class MockInputHandler : IInputHandler
+{
+ public CultureInfo CurrentInputLanguage { get; set; } = new(1033);
+
+ public Vector2 MousePosition { get; set; } = Vector2.Zero;
+
+ public ISet PressedKeyboardButtons => _pressedKeyboardButtons;
+ public ISet JustPressedKeyboardButtons => _justPressedKeyboardButtons;
+ public ISet PressedMouseButtons => _pressedMouseButtons;
+ public ISet JustPressedMouseButtons => _justPressedMouseButtons;
+
+ private readonly HashSet _pressedKeyboardButtons = [];
+ private readonly HashSet _justPressedKeyboardButtons = [];
+ private readonly HashSet _pressedMouseButtons = [];
+ private readonly HashSet _justPressedMouseButtons = [];
+
+ public void Update(double parDeltaTime)
+ {
+ }
+
+ public bool IsKeyPressed(KeyboardButtonCode parKeyboardButtonCode)
+ {
+ return _pressedKeyboardButtons.Contains(parKeyboardButtonCode);
+ }
+
+ public bool IsKeyJustPressed(KeyboardButtonCode parKeyboardButtonCode)
+ {
+ return _justPressedKeyboardButtons.Contains(parKeyboardButtonCode);
+ }
+
+ public bool IsMouseButtonPressed(MouseButtonCode parButtonCode)
+ {
+ return _pressedMouseButtons.Contains(parButtonCode);
+ }
+
+ public bool IsMouseButtonJustPressed(MouseButtonCode parButtonCode)
+ {
+ return _justPressedMouseButtons.Contains(parButtonCode);
+ }
+}
\ No newline at end of file
diff --git a/TestUtil/src/MockResourceManager.cs b/TestUtil/src/MockResourceManager.cs
new file mode 100644
index 0000000..6db4633
--- /dev/null
+++ b/TestUtil/src/MockResourceManager.cs
@@ -0,0 +1,15 @@
+using Engine.Resource;
+
+namespace TestUtil;
+
+public class MockResourceManager : IResourceManager
+{
+ public event Action? OnLoad;
+
+ public T Load(string parPath) where T : class
+ {
+ OnLoad?.Invoke(typeof(T), parPath);
+
+ return null!;
+ }
+}
\ No newline at end of file
diff --git a/TestUtil/src/Renderer/MockAnyMeshRenderer.cs b/TestUtil/src/Renderer/MockAnyMeshRenderer.cs
new file mode 100644
index 0000000..12859c8
--- /dev/null
+++ b/TestUtil/src/Renderer/MockAnyMeshRenderer.cs
@@ -0,0 +1,16 @@
+using Engine.Asset.Mesh;
+using Engine.Graphics.Render.Mesh;
+using Engine.Graphics.Texture;
+using OpenTK.Mathematics;
+
+namespace TestUtil.Renderer;
+
+public class MockAnyMeshRenderer : IAnyMeshRenderer
+{
+ public event Action? OnCommit;
+
+ public void Commit(Mesh parMesh, in Matrix4 parModelMatrix, Texture? parAlbedo = null)
+ {
+ OnCommit?.Invoke(parMesh, parModelMatrix, parAlbedo);
+ }
+}
\ No newline at end of file
diff --git a/TestUtil/src/Renderer/MockGenericRenderer.cs b/TestUtil/src/Renderer/MockGenericRenderer.cs
new file mode 100644
index 0000000..1e2e563
--- /dev/null
+++ b/TestUtil/src/Renderer/MockGenericRenderer.cs
@@ -0,0 +1,21 @@
+using Engine.Graphics;
+using Engine.Graphics.Render.Mesh;
+using Engine.Graphics.Render.Quad;
+using Engine.Graphics.Render.Text;
+
+namespace TestUtil.Renderer;
+
+public class MockGenericRenderer : IGenericRenderer
+{
+ public IQuadRenderer QuadRenderer { get; }
+ public IAnyMeshRenderer AnyMeshRenderer { get; }
+ public ITextRenderer TextRenderer { get; }
+
+ public MockGenericRenderer(IQuadRenderer parQuadRenderer, IAnyMeshRenderer parAnyMeshRenderer,
+ ITextRenderer parTextRenderer)
+ {
+ QuadRenderer = parQuadRenderer;
+ AnyMeshRenderer = parAnyMeshRenderer;
+ TextRenderer = parTextRenderer;
+ }
+}
\ No newline at end of file
diff --git a/TestUtil/src/Renderer/MockQuadRenderer.cs b/TestUtil/src/Renderer/MockQuadRenderer.cs
new file mode 100644
index 0000000..6fcb11a
--- /dev/null
+++ b/TestUtil/src/Renderer/MockQuadRenderer.cs
@@ -0,0 +1,15 @@
+using Engine.Graphics.Render.Quad;
+using Engine.Graphics.Texture;
+using OpenTK.Mathematics;
+
+namespace TestUtil.Renderer;
+
+public class MockQuadRenderer : IQuadRenderer
+{
+ public event Action? OnCommit;
+
+ public void Commit(in Matrix4 parModelMatrix, in Vector4 parColor, Texture? parTexture = null)
+ {
+ OnCommit?.Invoke(parModelMatrix, parColor, parTexture);
+ }
+}
\ No newline at end of file
diff --git a/TestUtil/src/Renderer/MockRenderer.cs b/TestUtil/src/Renderer/MockRenderer.cs
new file mode 100644
index 0000000..63c838e
--- /dev/null
+++ b/TestUtil/src/Renderer/MockRenderer.cs
@@ -0,0 +1,16 @@
+using Engine.Graphics;
+using Engine.Graphics.Pipeline;
+
+namespace TestUtil.Renderer;
+
+public class MockRenderer : IRenderer
+{
+ public IGenericRenderer this[RenderLayer parRenderLayer] => _renderers[parRenderLayer];
+
+ private readonly Dictionary _renderers;
+
+ public MockRenderer(Dictionary parRenderers)
+ {
+ _renderers = parRenderers;
+ }
+}
\ No newline at end of file
diff --git a/TestUtil/src/Renderer/MockTextRenderer.cs b/TestUtil/src/Renderer/MockTextRenderer.cs
new file mode 100644
index 0000000..7999ae2
--- /dev/null
+++ b/TestUtil/src/Renderer/MockTextRenderer.cs
@@ -0,0 +1,15 @@
+using Engine.Asset.Font;
+using Engine.Graphics.Render.Text;
+using OpenTK.Mathematics;
+
+namespace TestUtil.Renderer;
+
+public class MockTextRenderer : ITextRenderer
+{
+ public event Action? OnCommit;
+
+ public void Commit(Font parFont, string parText, in Vector4 parColor, in Matrix4 parModelMatrix)
+ {
+ OnCommit?.Invoke(parFont, parText, parColor, parModelMatrix);
+ }
+}
\ No newline at end of file