added crude camera movement controls

This commit is contained in:
2025-12-30 15:22:15 +01:00
parent 9efda11755
commit 2557f4d6a0
11 changed files with 207 additions and 7 deletions

View File

@@ -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<LogicComponent> _logicComponents = [];
@@ -49,6 +49,11 @@ public class Entity
};
}
/// <summary>
/// Gets called once the entity gets loaded but before the components get executed for the first time.
/// </summary>
public abstract void OnLoad();
internal void InitialiseComponents()
{
foreach (var components in _logicComponents)

View File

@@ -25,6 +25,7 @@ public class Scene
{
foreach (var entity in _entities.Values)
{
entity.OnLoad();
entity.InitialiseComponents();
}
}

View File

@@ -9,6 +9,8 @@ public interface IEngine
void Stop();
Shader GetShader(string vertexShaderPath);
InputService GetInputService();
}
public enum GraphicsAPI

View File

@@ -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<IKeyboard, HashSet<Key>> _pressedKeys = new Dictionary<IKeyboard, HashSet<Key>>();
private readonly Dictionary<IKeyboard, HashSet<Key>> _pressedKeys = new();
private Vector2D<float> _currentMouseDelta = Vector2D<float>.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<float> GetMouseDelta()
{
var tempDelta = _currentMouseDelta;
_currentMouseDelta = Vector2D<float>.Zero;
return tempDelta;
}
public Vector2D<float> 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<float>(-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.

View File

@@ -14,7 +14,7 @@ internal class OpenGLEngine : Engine
private readonly IWindow _window;
private readonly Dictionary<string, Scene> _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<string> 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<float>.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);
}
}

View File

@@ -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<float> _worldUp;

View File

@@ -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);

View File

@@ -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;
}
}

View File

@@ -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<float>.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);
}
}

View File

@@ -8,4 +8,9 @@ public class Planet : Core.ECS.Entity
{
AddComponent(new PlanetGenerator());
}
public override void OnLoad()
{
// Nothing to do
}
}

View File

@@ -20,6 +20,11 @@ internal static class Program
var mainScene = SceneManager.CreateDefaultScene("default");
var planet = mainScene.CreateEntity<Planet>("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>("inputEntity");
// input.InputService = engine.GetInputService();
// input.Camera = engine.GetCamera();
engine.Start();
}
}