diff --git a/src/EngineSharp.Core/EngineSharp.Core/ECS/Entity.cs b/src/EngineSharp.Core/EngineSharp.Core/ECS/Entity.cs index 050ffa4..ec19b1d 100644 --- a/src/EngineSharp.Core/EngineSharp.Core/ECS/Entity.cs +++ b/src/EngineSharp.Core/EngineSharp.Core/ECS/Entity.cs @@ -8,7 +8,7 @@ namespace EngineSharp.Core.ECS; // https://github.com/friflo/Friflo.Engine.ECS/tree/main // OR: https://github.com/genaray/Arch -public class Entity +public abstract class Entity { // TODO: Maybe use a Hashset and implement a comparer or something? Should be faster than the current .OfType<>().Any() implementation private readonly List _logicComponents = []; @@ -49,6 +49,11 @@ public class Entity }; } + /// + /// Gets called once the entity gets loaded but before the components get executed for the first time. + /// + public abstract void OnLoad(); + internal void InitialiseComponents() { foreach (var components in _logicComponents) diff --git a/src/EngineSharp.Core/EngineSharp.Core/ECS/Scene.cs b/src/EngineSharp.Core/EngineSharp.Core/ECS/Scene.cs index 0279234..adf6d52 100644 --- a/src/EngineSharp.Core/EngineSharp.Core/ECS/Scene.cs +++ b/src/EngineSharp.Core/EngineSharp.Core/ECS/Scene.cs @@ -25,6 +25,7 @@ public class Scene { foreach (var entity in _entities.Values) { + entity.OnLoad(); entity.InitialiseComponents(); } } diff --git a/src/EngineSharp.Core/EngineSharp.Core/IEngine.cs b/src/EngineSharp.Core/EngineSharp.Core/IEngine.cs index 411bb55..9015892 100644 --- a/src/EngineSharp.Core/EngineSharp.Core/IEngine.cs +++ b/src/EngineSharp.Core/EngineSharp.Core/IEngine.cs @@ -9,6 +9,8 @@ public interface IEngine void Stop(); Shader GetShader(string vertexShaderPath); + + InputService GetInputService(); } public enum GraphicsAPI diff --git a/src/EngineSharp.Core/EngineSharp.Core/Input.cs b/src/EngineSharp.Core/EngineSharp.Core/InputService.cs similarity index 56% rename from src/EngineSharp.Core/EngineSharp.Core/Input.cs rename to src/EngineSharp.Core/EngineSharp.Core/InputService.cs index 445ecac..a2b46d0 100644 --- a/src/EngineSharp.Core/EngineSharp.Core/Input.cs +++ b/src/EngineSharp.Core/EngineSharp.Core/InputService.cs @@ -1,16 +1,20 @@ using System.Collections.Generic; using System.Linq; +using System.Numerics; using Silk.NET.Input; +using Silk.NET.Maths; namespace EngineSharp.Core; -public class Input +public class InputService { private readonly IInputContext _context; - private readonly IDictionary> _pressedKeys = new Dictionary>(); + private readonly Dictionary> _pressedKeys = new(); + private Vector2D _currentMouseDelta = Vector2D.Zero; + private Vector2? _currentMousePosition; // TODO: Add mouse - internal Input(IInputContext inputContext) + internal InputService(IInputContext inputContext) { _context = inputContext; foreach (var keyboard in _context.Keyboards) @@ -18,6 +22,12 @@ public class Input keyboard.KeyDown += OnKeyDown; keyboard.KeyUp += OnKeyUp; } + + foreach (var mouse in _context.Mice) + { + mouse.MouseMove += OnMouseMove; + } + _context.ConnectionChanged += OnDeviceConnectionChanges; } @@ -37,6 +47,15 @@ public class Input return false; } + + public Vector2D GetMouseDelta() + { + var tempDelta = _currentMouseDelta; + _currentMouseDelta = Vector2D.Zero; + return tempDelta; + } + + public Vector2D GetMouseDelta(IMouse mouse) => GetMouseDelta(); private void OnKeyDown(IKeyboard keyboard, Key key, int keyCode) { @@ -58,6 +77,25 @@ public class Input } } + private void OnMouseMove(IMouse mouse, Vector2 newPosition) + { + if (!_currentMousePosition.HasValue) + { + _currentMousePosition = newPosition; + return; + } + + var delta = _currentMousePosition.Value - newPosition; + + // TODO: Very very bad solution! I need to find a good way to determine if the cursor has left the window and now has re-entered it + if (Math.Abs(delta.X) <= 100 || Math.Abs(delta.Y) <= 100) + { + _currentMouseDelta = new Vector2D(-delta.X, delta.Y); + } + + _currentMousePosition = newPosition; + } + private void OnDeviceConnectionChanges(IInputDevice device, bool connected) { // TODO: See how to best utilise this. probably type pattern matching for IKeyboard etc. diff --git a/src/EngineSharp.Core/EngineSharp.Core/OpenGLEngine.cs b/src/EngineSharp.Core/EngineSharp.Core/OpenGLEngine.cs index 4acaa0b..2ec8a94 100644 --- a/src/EngineSharp.Core/EngineSharp.Core/OpenGLEngine.cs +++ b/src/EngineSharp.Core/EngineSharp.Core/OpenGLEngine.cs @@ -14,7 +14,7 @@ internal class OpenGLEngine : Engine private readonly IWindow _window; private readonly Dictionary _scenes; private GL _gl = null!; // because between constructing the engine and OnLoad being called nothing should call these fields. Therefore, we do "!" to get rid of warnings - private Input _input = null!; + private InputService _inputService = null!; private PerspectiveCamera _camera = null!; private Scene? _currentScene; @@ -47,6 +47,8 @@ internal class OpenGLEngine : Engine _window.Close(); } + public override InputService GetInputService() => _inputService; + private void InitialiseShaders(IEnumerable vertexShaderFiles) { foreach (var vertexShaderFile in vertexShaderFiles) @@ -91,7 +93,7 @@ internal class OpenGLEngine : Engine } _gl = _window.CreateOpenGL(); - _input = new Input(_window.CreateInput()); + _inputService = new InputService(_window.CreateInput()); _gl.ClearColor(Color.Black); _gl.Enable(EnableCap.DepthTest); @@ -104,10 +106,13 @@ internal class OpenGLEngine : Engine private void OnUpdate(double deltaTime) { - if (_input.IsKeyPressed(Key.Escape)) + if (_inputService.IsKeyPressed(Key.Escape)) { _window.Close(); } + + TemporaryMovementProcessing((float)deltaTime); + TemporaryMouseProcessing(); _currentScene?.UpdateComponents(deltaTime); } @@ -129,4 +134,43 @@ internal class OpenGLEngine : Engine _camera.UpdateAspect(newDimensions); _gl.Viewport(newDimensions); } + + private void TemporaryMovementProcessing(float deltaTime) + { + var moveDirection = Vector3D.Zero; + if (_inputService.IsKeyPressed(Key.A)) + { + moveDirection.X -= 1; + } + else if (_inputService.IsKeyPressed(Key.D)) + { + moveDirection.X += 1; + } + + if (_inputService.IsKeyPressed(Key.W)) + { + moveDirection.Z += 1; + } + else if (_inputService.IsKeyPressed(Key.S)) + { + moveDirection.Z -= 1; + } + + if (_inputService.IsKeyPressed(Key.Space)) + { + moveDirection.Y += 1; + } + else if (_inputService.IsKeyPressed(Key.ControlLeft)) + { + moveDirection.Y -= 1; + } + + _camera.ProcessMovement(deltaTime, moveDirection); + } + + private void TemporaryMouseProcessing() + { + var offset = _inputService.GetMouseDelta() * 0.1f; + _camera.ProcessMouse(offset); + } } \ No newline at end of file diff --git a/src/EngineSharp.Core/EngineSharp.Core/PerspectiveCamera.cs b/src/EngineSharp.Core/EngineSharp.Core/PerspectiveCamera.cs index 1a43a51..1046e71 100644 --- a/src/EngineSharp.Core/EngineSharp.Core/PerspectiveCamera.cs +++ b/src/EngineSharp.Core/EngineSharp.Core/PerspectiveCamera.cs @@ -3,6 +3,7 @@ using Silk.NET.Maths; namespace EngineSharp.Core; +// Camera should be an entity just like anything else (property IsDefault so the engine knows which camera to use for drawing to the screen) public class PerspectiveCamera { private readonly Vector3D _worldUp; diff --git a/src/EngineSharp.Core/EngineSharp.Core/Rendering/Engine.cs b/src/EngineSharp.Core/EngineSharp.Core/Rendering/Engine.cs index 93ebb9f..e8a37d4 100644 --- a/src/EngineSharp.Core/EngineSharp.Core/Rendering/Engine.cs +++ b/src/EngineSharp.Core/EngineSharp.Core/Rendering/Engine.cs @@ -19,6 +19,8 @@ internal abstract class Engine : IEngine public abstract void Start(); public abstract void Stop(); + public abstract InputService GetInputService(); + internal abstract Scene CreateScene(string name); internal abstract void SetCurrentScene(string sceneName); diff --git a/src/EngineSharp/InputEntity.cs b/src/EngineSharp/InputEntity.cs new file mode 100644 index 0000000..f98f7cf --- /dev/null +++ b/src/EngineSharp/InputEntity.cs @@ -0,0 +1,33 @@ +using EngineSharp.Core; +using EngineSharp.Core.ECS; + +namespace EngineSharp; + +public class InputEntity : Entity +{ + public InputService? InputService { get; set; } + public PerspectiveCamera? Camera { get; set; } + + private readonly InputManager _inputComponent; + + public InputEntity() + { + _inputComponent = new InputManager(); + AddComponent(_inputComponent); + } + + public override void OnLoad() + { + if (InputService is null) + { + throw new InvalidOperationException($"Input service is not initialized. Cannot load Entity '{nameof(InputEntity)}'."); + } + if (Camera is null) + { + throw new InvalidOperationException($"Camera is not initialized. Cannot load Entity '{nameof(InputEntity)}'."); + } + + _inputComponent.InputService = InputService; + _inputComponent.Camera = Camera; + } +} \ No newline at end of file diff --git a/src/EngineSharp/InputManager.cs b/src/EngineSharp/InputManager.cs new file mode 100644 index 0000000..af8619a --- /dev/null +++ b/src/EngineSharp/InputManager.cs @@ -0,0 +1,64 @@ +using EngineSharp.Core; +using EngineSharp.Core.ECS; +using Silk.NET.Input; +using Silk.NET.Maths; + +namespace EngineSharp; + +public class InputManager : LogicComponent +{ + public InputService? InputService { get; set; } + + public PerspectiveCamera? Camera { get; set; } + + public override void Initialise() + { + + } + + public override void OnUpdate(double deltaTime) + { + // TODO: update camera position and rotation (currently without transform component) + if (InputService is null) + { + Console.WriteLine("ERROR: No InputService given. Skipping this iteration."); + return; + } + + if (Camera is null) + { + Console.WriteLine("ERROR: No Camera given. Skipping this iteration."); + return; + } + + var moveDirection = Vector3D.Zero; + if (InputService.IsKeyPressed(Key.A)) + { + moveDirection.X -= 1; + } + else if (InputService.IsKeyPressed(Key.D)) + { + moveDirection.X += 1; + } + + if (InputService.IsKeyPressed(Key.W)) + { + moveDirection.Z -= 1; + } + else if (InputService.IsKeyPressed(Key.S)) + { + moveDirection.Z += 1; + } + + if (InputService.IsKeyPressed(Key.Space)) + { + moveDirection.Y += 1; + } + else if (InputService.IsKeyPressed(Key.ControlLeft)) + { + moveDirection.Y -= 1; + } + + Camera.ProcessMovement((float)deltaTime, moveDirection); + } +} \ No newline at end of file diff --git a/src/EngineSharp/Planet.cs b/src/EngineSharp/Planet.cs index 4cdbdb3..b7cb453 100644 --- a/src/EngineSharp/Planet.cs +++ b/src/EngineSharp/Planet.cs @@ -8,4 +8,9 @@ public class Planet : Core.ECS.Entity { AddComponent(new PlanetGenerator()); } + + public override void OnLoad() + { + // Nothing to do + } } \ No newline at end of file diff --git a/src/EngineSharp/Program.cs b/src/EngineSharp/Program.cs index f149348..7314c17 100644 --- a/src/EngineSharp/Program.cs +++ b/src/EngineSharp/Program.cs @@ -20,6 +20,11 @@ internal static class Program var mainScene = SceneManager.CreateDefaultScene("default"); var planet = mainScene.CreateEntity("planet"); + // I probably need some sort of dependency injection. It is just terrible to work with an uinitialised engine etc. + // var input = mainScene.CreateEntity("inputEntity"); + // input.InputService = engine.GetInputService(); + // input.Camera = engine.GetCamera(); + engine.Start(); } } \ No newline at end of file