feat: added basic window, input & drawing

This commit is contained in:
2026-01-28 22:38:17 +03:00
parent 18ac6b23fd
commit 129aae563e
12 changed files with 754 additions and 96 deletions

2
Engine

Submodule Engine updated: ad444decbb...b9f3227f73

View File

@@ -10,6 +10,7 @@
<AssemblyName>MyUniverse</AssemblyName> <AssemblyName>MyUniverse</AssemblyName>
</PropertyGroup> </PropertyGroup>
<PropertyGroup> <PropertyGroup>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<ApplicationManifest>app.manifest</ApplicationManifest> <ApplicationManifest>app.manifest</ApplicationManifest>
<ApplicationIcon>Icon.ico</ApplicationIcon> <ApplicationIcon>Icon.ico</ApplicationIcon>
</PropertyGroup> </PropertyGroup>
@@ -28,6 +29,7 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="nulastudio.NetBeauty" Version="2.1.5" /> <PackageReference Include="nulastudio.NetBeauty" Version="2.1.5" />
<PackageReference Include="SDL3-CS" Version="3.3.7" /> <PackageReference Include="SDL3-CS" Version="3.3.7" />
<PackageReference Include="Silk.NET.OpenGL" Version="2.23.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="../../Engine/Engine.Integration/Engine.Integration.LiteNetLib/Engine.Integration.LiteNetLib.csproj" /> <ProjectReference Include="../../Engine/Engine.Integration/Engine.Integration.LiteNetLib/Engine.Integration.LiteNetLib.csproj" />

View File

@@ -0,0 +1,31 @@
using Engine.Core;
using SDL3;
namespace Engine.Integration.SDL3;
public class CameraController2D : Behaviour2D, IFirstFrameUpdate, IUpdate
{
private Sdl3KeyboardInputs keyboardInputs = null!;
private ICamera2D camera = null!;
public void FirstActiveFrame()
{
keyboardInputs = Universe.FindRequiredBehaviour<Sdl3KeyboardInputs>();
camera = BehaviourController.GetRequiredBehaviourInParent<ICamera2D>();
}
public void Update()
{
if (keyboardInputs.IsPressed(SDL.Keycode.W)) Transform.Position += Transform.Up * Universe.Time.DeltaTime * 100f;
if (keyboardInputs.IsPressed(SDL.Keycode.A)) Transform.Position += Transform.Left * Universe.Time.DeltaTime * 100f;
if (keyboardInputs.IsPressed(SDL.Keycode.S)) Transform.Position += Transform.Down * Universe.Time.DeltaTime * 100f;
if (keyboardInputs.IsPressed(SDL.Keycode.D)) Transform.Position += Transform.Right * Universe.Time.DeltaTime * 100f;
if (keyboardInputs.IsPressed(SDL.Keycode.Q)) Transform.Rotation += Universe.Time.DeltaTime * 45f;
if (keyboardInputs.IsPressed(SDL.Keycode.E)) Transform.Rotation -= Universe.Time.DeltaTime * 45f;
if (keyboardInputs.IsPressed(SDL.Keycode.R)) camera.Zoom += Universe.Time.DeltaTime * 15f;
if (keyboardInputs.IsPressed(SDL.Keycode.F)) camera.Zoom -= Universe.Time.DeltaTime * 15f;
}
}

View File

@@ -0,0 +1,8 @@
using SDL3;
namespace Engine.Integration.SDL3;
public interface ISdl3EventProcessor
{
void Process(SDL.Event sdlEvent);
}

View File

@@ -0,0 +1,86 @@
using Engine.Core;
namespace Engine.Integration.SDL3;
public class Sdl3Camera2D : Behaviour, ICamera2D, IFirstFrameUpdate, ILastFrameUpdate, IPreDraw
{
public Event<Sdl3Camera2D> OnViewMatrixChanged { get; } = new();
public Event<Sdl3Camera2D> OnProjectionMatrixChanged { get; } = new();
public Event<Sdl3Camera2D> OnViewportChanged { get; } = new();
public Event<Sdl3Camera2D> OnZoomChanged { get; } = new();
private IWindow window = null!;
public ITransform2D Transform { get; private set; } = null!;
public Matrix4x4 ProjectionMatrix
{
get;
private set
{
if (field == value)
return;
field = value;
OnProjectionMatrixChanged.Invoke(this);
}
} = Matrix4x4.Identity;
public Matrix4x4 ViewMatrix
{
get;
private set
{
if (field == value)
return;
field = value;
OnViewMatrixChanged.Invoke(this);
}
} = Matrix4x4.Identity;
public float Zoom
{
get;
set
{
float newValue = Math.Max(0.1f, value);
if (field == newValue)
return;
field = newValue;
OnZoomChanged.Invoke(this);
}
} = 1f;
// TODO This causes delay since OnPreDraw calls assuming this is called in in Update
public Vector2D ScreenToWorldPosition(Vector2D screenPosition)
{
Vector2D worldPosition = Vector2D.Transform(screenPosition, Transform);
return worldPosition;
}
public Vector2D WorldToScreenPosition(Vector2D worldPosition)
{
Vector2D screenPosition = Vector2D.Transform(worldPosition, Transform);
return screenPosition;
}
public void LastActiveFrame() => Transform = null!;
public void FirstActiveFrame()
{
window = BehaviourController.UniverseObject.Universe.FindRequiredBehaviour<IWindow>();
Transform = BehaviourController.GetRequiredBehaviour<ITransform2D>();
}
public void PreDraw()
{
ProjectionMatrix = Matrix4x4.CreateOrthographicViewCentered(window.Size.X, window.Size.Y);
ViewMatrix = Matrix4x4.Identity
.ApplyScale(Transform.Scale.X.Max(Transform.Scale.Y))
.ApplyScale(Zoom)
.ApplyRotationZ(-Transform.Rotation * Math.DegreeToRadian)
.ApplyTranslation(new Vector3D(-Transform.Position.X, -Transform.Position.Y, 0f));
}
}

View File

@@ -0,0 +1,134 @@
using System;
using System.Collections.Generic;
using Engine.Core;
using Engine.Systems.Input;
using MyUniverse.Shared.Behaviours.Abstract;
using SDL3;
namespace Engine.Integration.SDL3;
public class Sdl3GameInputs : Behaviour, IGameInputs, IFirstFrameUpdate
{
public IButtonInputs<IGameInputs.Button>.InputEvent OnAnyButtonPressed { get; } = new();
public IButtonInputs<IGameInputs.Button>.InputEvent OnAnyButtonReleased { get; } = new();
private readonly Dictionary<IGameInputs.Button, IButtonInputs<IGameInputs.Button>.InputEvent> OnPressed = new(256);
private readonly Dictionary<IGameInputs.Button, IButtonInputs<IGameInputs.Button>.InputEvent> OnReleased = new(256);
private readonly Dictionary<IGameInputs.Button, SDL.Keycode> keyMappingReversed = [];
private readonly Dictionary<SDL.Keycode, IGameInputs.Button> keyMapping = new() {
{ SDL.Keycode.Space, IGameInputs.Button.Interact }
};
private Sdl3KeyboardInputs keyboardInputs = null!;
public void FirstActiveFrame()
{
keyboardInputs = BehaviourController.GetOrAddBehaviour<Sdl3KeyboardInputs>();
keyboardInputs.OnAnyButtonPressed.AddListener(AnyPressCallback);
keyboardInputs.OnAnyButtonReleased.AddListener(AnyReleaseCallback);
foreach ((SDL.Keycode key, IGameInputs.Button button) in keyMapping)
keyboardInputs.RegisterOnPress(key, PressCallback);
foreach ((SDL.Keycode key, IGameInputs.Button button) in keyMapping)
keyboardInputs.RegisterOnRelease(key, ReleaseCallback);
}
public void ExitUniverse(IUniverse universe)
{
keyboardInputs.OnAnyButtonPressed.RemoveListener(AnyPressCallback);
keyboardInputs.OnAnyButtonReleased.RemoveListener(AnyReleaseCallback);
foreach ((SDL.Keycode key, IGameInputs.Button button) in keyMapping)
keyboardInputs.UnregisterOnPress(key, PressCallback);
foreach ((SDL.Keycode key, IGameInputs.Button button) in keyMapping)
keyboardInputs.UnregisterOnRelease(key, ReleaseCallback);
}
private void PressCallback(IButtonInputs<SDL.Keycode> sender, IButtonInputs<SDL.Keycode>.ButtonCallbackArguments args)
{
if (!keyMapping.TryGetValue(args.Button, out IGameInputs.Button button))
return;
if (!OnPressed.TryGetValue(button, out IButtonInputs<IGameInputs.Button>.InputEvent? @event))
return;
@event.Invoke(this, new(button));
}
private void ReleaseCallback(IButtonInputs<SDL.Keycode> sender, IButtonInputs<SDL.Keycode>.ButtonCallbackArguments args)
{
if (!keyMapping.TryGetValue(args.Button, out IGameInputs.Button button))
return;
if (!OnReleased.TryGetValue(button, out IButtonInputs<IGameInputs.Button>.InputEvent? @event))
return;
@event.Invoke(this, new(button));
}
private void AnyReleaseCallback(IButtonInputs<SDL.Keycode> sender, IButtonInputs<SDL.Keycode>.ButtonCallbackArguments args)
{
if (keyMapping.TryGetValue(args.Button, out IGameInputs.Button button))
OnAnyButtonPressed?.Invoke(this, new(button));
}
private void AnyPressCallback(IButtonInputs<SDL.Keycode> sender, IButtonInputs<SDL.Keycode>.ButtonCallbackArguments args)
{
if (keyMapping.TryGetValue(args.Button, out IGameInputs.Button button))
OnAnyButtonPressed?.Invoke(this, new(button));
}
public void RegisterOnPress(IGameInputs.Button button, IButtonInputs<IGameInputs.Button>.InputEvent.EventHandler callback)
{
if (!OnPressed.TryGetValue(button, out IButtonInputs<IGameInputs.Button>.InputEvent? @event))
{
@event = new();
OnPressed.Add(button, @event);
}
@event.AddListener(callback);
}
public void UnregisterOnPress(IGameInputs.Button button, IButtonInputs<IGameInputs.Button>.InputEvent.EventHandler callback)
{
if (OnPressed.TryGetValue(button, out IButtonInputs<IGameInputs.Button>.InputEvent? @event))
@event.RemoveListener(callback);
}
public void RegisterOnRelease(IGameInputs.Button button, IButtonInputs<IGameInputs.Button>.InputEvent.EventHandler callback)
{
if (!OnReleased.TryGetValue(button, out IButtonInputs<IGameInputs.Button>.InputEvent? @event))
{
@event = new();
OnReleased.Add(button, @event);
}
@event.AddListener(callback);
}
public void UnregisterOnRelease(IGameInputs.Button button, IButtonInputs<IGameInputs.Button>.InputEvent.EventHandler callback)
{
if (OnReleased.TryGetValue(button, out IButtonInputs<IGameInputs.Button>.InputEvent? @event))
@event.RemoveListener(callback);
}
public bool IsPressed(IGameInputs.Button button)
{
if (!keyMappingReversed.TryGetValue(button, out SDL.Keycode key))
throw new($"{Enum.GetName(button)} is not mapped correctly");
return keyboardInputs.IsPressed(key);
}
public Sdl3GameInputs()
{
foreach ((SDL.Keycode key, IGameInputs.Button button) in keyMapping)
keyMappingReversed.Add(button, key);
}
}

View File

@@ -0,0 +1,87 @@
using System.Collections.Generic;
using Engine.Core;
using Engine.Systems.Input;
using SDL3;
namespace Engine.Integration.SDL3;
public class Sdl3KeyboardInputs : Behaviour, ISdl3EventProcessor, IButtonInputs<SDL.Keycode>
{
public IButtonInputs<SDL.Keycode>.InputEvent OnAnyButtonPressed { get; } = new();
public IButtonInputs<SDL.Keycode>.InputEvent OnAnyButtonReleased { get; } = new();
private readonly Dictionary<SDL.Keycode, IButtonInputs<SDL.Keycode>.InputEvent> OnPressed = new(256);
private readonly Dictionary<SDL.Keycode, IButtonInputs<SDL.Keycode>.InputEvent> OnReleased = new(256);
private readonly FastList<SDL.Keycode> currentlyPressedKeys = [];
public void RegisterOnPress(SDL.Keycode key, IButtonInputs<SDL.Keycode>.InputEvent.EventHandler callback)
{
if (!OnPressed.TryGetValue(key, out IButtonInputs<SDL.Keycode>.InputEvent? delegateCallback))
{
delegateCallback = new();
OnPressed.Add(key, delegateCallback);
}
delegateCallback.AddListener(callback);
}
public void UnregisterOnPress(SDL.Keycode key, IButtonInputs<SDL.Keycode>.InputEvent.EventHandler callback)
{
if (OnPressed.TryGetValue(key, out IButtonInputs<SDL.Keycode>.InputEvent? delegateCallback))
delegateCallback.RemoveListener(callback);
}
public void RegisterOnRelease(SDL.Keycode key, IButtonInputs<SDL.Keycode>.InputEvent.EventHandler callback)
{
if (!OnReleased.TryGetValue(key, out IButtonInputs<SDL.Keycode>.InputEvent? delegateCallback))
{
delegateCallback = new();
OnReleased.Add(key, delegateCallback);
}
delegateCallback.AddListener(callback);
}
public void UnregisterOnRelease(SDL.Keycode key, IButtonInputs<SDL.Keycode>.InputEvent.EventHandler callback)
{
if (OnReleased.TryGetValue(key, out IButtonInputs<SDL.Keycode>.InputEvent? delegateCallback))
delegateCallback.RemoveListener(callback);
}
public bool IsPressed(SDL.Keycode button) => currentlyPressedKeys.Contains(button);
public void Process(SDL.Event sdlEvent)
{
switch ((SDL.EventType)sdlEvent.Type)
{
case SDL.EventType.KeyDown:
SDL.Keycode currentlyPressedKey = sdlEvent.Key.Key;
if (IsPressed(currentlyPressedKey))
return;
currentlyPressedKeys.Add(currentlyPressedKey);
if (OnPressed.TryGetValue(currentlyPressedKey, out IButtonInputs<SDL.Keycode>.InputEvent? pressCallback))
pressCallback?.Invoke(this, new(currentlyPressedKey));
OnAnyButtonPressed?.Invoke(this, new(currentlyPressedKey));
return;
case SDL.EventType.KeyUp:
SDL.Keycode previouslyPressedKey = sdlEvent.Key.Key;
if (!IsPressed(previouslyPressedKey))
return;
currentlyPressedKeys.Remove(previouslyPressedKey);
if (OnReleased.TryGetValue(previouslyPressedKey, out IButtonInputs<SDL.Keycode>.InputEvent? releaseCallback))
releaseCallback?.Invoke(this, new(previouslyPressedKey));
OnAnyButtonReleased?.Invoke(this, new(previouslyPressedKey));
return;
}
}
}

View File

@@ -0,0 +1,269 @@
using System;
using Engine.Core;
using Engine.Systems.Graphics;
using SDL3;
using Silk.NET.OpenGL;
namespace Engine.Integration.SDL3;
public class Sdl3TriangleBatch : Behaviour, ITriangleBatch, IFirstFrameUpdate
{
private GL gl = null!;
private readonly uint MaxVertices = 1024;
private uint vao;
private uint vbo;
private uint shader;
private uint vertexIndex = 0;
private VertexPositionColor[] vertices = new VertexPositionColor[1024];
private Matrix4x4 view = Matrix4x4.Identity;
private Matrix4x4 projection = Matrix4x4.Identity;
private Sdl3Window sdl3window = null!;
private ICamera? camera = null!;
private nint glContext;
public void FirstActiveFrame()
{
sdl3window = BehaviourController.GetRequiredBehaviourInParent<Sdl3Window>();
camera = BehaviourController.GetRequiredBehaviourInParent<ICamera>();
nint window = sdl3window.Window;
SDL.GLSetAttribute(SDL.GLAttr.ContextMajorVersion, 3);
SDL.GLSetAttribute(SDL.GLAttr.ContextMinorVersion, 3);
SDL.GLSetAttribute(SDL.GLAttr.ContextProfileMask, (int)SDL.GLProfile.Core);
SDL.GLSetAttribute(SDL.GLAttr.DoubleBuffer, 1);
glContext = SDL.GLCreateContext(window);
if (glContext == nint.Zero)
throw new Exception(SDL.GetError());
SDL.GLMakeCurrent(window, glContext);
gl = GL.GetApi(procName => SDL.GLGetProcAddress(procName));
gl.Viewport(0, 0, (uint)sdl3window.Size.X, (uint)sdl3window.Size.Y);
vertices = new VertexPositionColor[MaxVertices];
// --- GL objects ---
vao = gl.GenVertexArray();
vbo = gl.GenBuffer();
gl.BindVertexArray(vao);
gl.BindBuffer(BufferTargetARB.ArrayBuffer, vbo);
unsafe
{
gl.BufferData(
BufferTargetARB.ArrayBuffer,
(nuint)(MaxVertices * sizeof(VertexPositionColor)),
null,
BufferUsageARB.DynamicDraw
);
// position
gl.EnableVertexAttribArray(0);
gl.VertexAttribPointer(
0,
3,
VertexAttribPointerType.Float,
false,
(uint)sizeof(VertexPositionColor),
(void*)0
);
// color
gl.EnableVertexAttribArray(1);
gl.VertexAttribPointer(
1,
4,
VertexAttribPointerType.Float,
false,
(uint)sizeof(VertexPositionColor),
(void*)(3 * sizeof(float))
);
gl.BindVertexArray(0);
}
shader = CreateShader();
}
public void Draw(Triangle triangle, ColorRGBA colorRGBA)
{
if (vertexIndex + 3 >= vertices.Length)
Flush();
Vector2D A = triangle.A;
Vector2D B = triangle.B;
Vector2D C = triangle.C;
Vector4D c = new Vector4D(colorRGBA.R, colorRGBA.G, colorRGBA.B, colorRGBA.A) / 256f;
vertices[vertexIndex++] = new()
{
Position = new Vector3D(A.X, A.Y, 0f),
Color = c
};
vertices[vertexIndex++] = new()
{
Position = new Vector3D(B.X, B.Y, 0f),
Color = c
};
vertices[vertexIndex++] = new()
{
Position = new Vector3D(C.X, C.Y, 0f),
Color = c
};
}
public void Begin(Matrix4x4? view = null, Matrix4x4? projection = null)
{
this.view = view ?? camera?.ViewMatrix ?? Matrix4x4.Identity;
if (projection != null)
this.projection = projection.Value;
else
this.projection = camera?.ProjectionMatrix ?? Matrix4x4.CreateOrthographicViewCentered(sdl3window.Size.X, sdl3window.Size.Y);
vertexIndex = 0;
SDL.GLMakeCurrent(sdl3window.Window, glContext);
gl.ClearColor(sdl3window.BackgroundColor.R / 256f, sdl3window.BackgroundColor.G / 256f, sdl3window.BackgroundColor.B / 256f, 1f);
gl.Clear(ClearBufferMask.ColorBufferBit);
}
public void End()
{
Flush();
SDL.GLSwapWindow(sdl3window.Window);
}
private void Flush()
{
if (vertexIndex == 0)
return;
gl.UseProgram(shader);
int viewLoc = gl.GetUniformLocation(shader, "uView");
int projLoc = gl.GetUniformLocation(shader, "uProjection");
unsafe
{
Span<float> temp = stackalloc float[16];
CopyTo(view.Transposed, temp);
fixed (float* v = temp)
gl.UniformMatrix4(viewLoc, 1, false, v);
CopyTo(projection.Transposed, temp);
fixed (float* p = temp)
gl.UniformMatrix4(projLoc, 1, false, p);
gl.BindVertexArray(vao);
gl.BindBuffer(BufferTargetARB.ArrayBuffer, vbo);
fixed (VertexPositionColor* ptr = vertices)
{
gl.BufferSubData(
BufferTargetARB.ArrayBuffer,
0,
(nuint)(vertexIndex * sizeof(VertexPositionColor)),
ptr
);
}
}
gl.DrawArrays(PrimitiveType.Triangles, 0, vertexIndex);
vertexIndex = 0;
}
public static void CopyTo(Matrix4x4 m, Span<float> destination)
{
if (destination.Length < 16)
throw new ArgumentException("Destination span must have at least 16 elements.");
destination[0] = m.M11;
destination[1] = m.M12;
destination[2] = m.M13;
destination[3] = m.M14;
destination[4] = m.M21;
destination[5] = m.M22;
destination[6] = m.M23;
destination[7] = m.M24;
destination[8] = m.M31;
destination[9] = m.M32;
destination[10] = m.M33;
destination[11] = m.M34;
destination[12] = m.M41;
destination[13] = m.M42;
destination[14] = m.M43;
destination[15] = m.M44;
}
private uint CreateShader()
{
const string vertexSrc = """
#version 330 core
layout (location = 0) in vec3 aPosition;
layout (location = 1) in vec4 aColor;
uniform mat4 uView;
uniform mat4 uProjection;
out vec4 vColor;
void main()
{
gl_Position = uProjection * uView * vec4(aPosition, 1.0);
vColor = aColor;
}
""";
const string fragmentSrc = """
#version 330 core
in vec4 vColor;
out vec4 FragColor;
void main()
{
FragColor = vColor;
}
""";
uint vs = CompileShader(ShaderType.VertexShader, vertexSrc);
uint fs = CompileShader(ShaderType.FragmentShader, fragmentSrc);
uint program = gl.CreateProgram();
gl.AttachShader(program, vs);
gl.AttachShader(program, fs);
gl.LinkProgram(program);
gl.DeleteShader(vs);
gl.DeleteShader(fs);
return program;
}
private uint CompileShader(ShaderType type, string src)
{
uint shader = gl.CreateShader(type);
gl.ShaderSource(shader, src);
gl.CompileShader(shader);
return shader;
}
struct VertexPositionColor
{
public Vector3D Position;
public Vector4D Color;
public const int SizeInBytes = (3 + 4) * sizeof(float);
}
}

View File

@@ -6,7 +6,7 @@ using SDL3;
namespace Engine.Integration.SDL3; namespace Engine.Integration.SDL3;
public class Sdl3Window : Behaviour, IWindow, IUpdate, IDisposable, IEnterUniverse, IExitUniverse public class Sdl3Window : Behaviour, IWindow, IDisposable, IEnterUniverse, IExitUniverse
{ {
public Event<IWindow, IWindow.StateChangedArguments> OnStateChanged { get; } = new(); public Event<IWindow, IWindow.StateChangedArguments> OnStateChanged { get; } = new();
public Event<IWindow, IWindow.FocusStateChangedArguments> OnFocusStateChanged { get; } = new(); public Event<IWindow, IWindow.FocusStateChangedArguments> OnFocusStateChanged { get; } = new();
@@ -19,26 +19,7 @@ public class Sdl3Window : Behaviour, IWindow, IUpdate, IDisposable, IEnterUniver
public Event<IWindow, IWindow.SizeChangedArguments> OnSizeChanged { get; } = new(); public Event<IWindow, IWindow.SizeChangedArguments> OnSizeChanged { get; } = new();
public Event<IWindow, IWindow.BackgroundColorChangedArguments> OnBackgroundColorChanged { get; } = new(); public Event<IWindow, IWindow.BackgroundColorChangedArguments> OnBackgroundColorChanged { get; } = new();
private IntPtr window = IntPtr.Zero; public IntPtr Window { get; private set; } = IntPtr.Zero;
private IntPtr renderer = IntPtr.Zero;
private ulong nextRenderTick = 0;
private ulong renderInterval = (ulong)(1f / 60f * 1000f);
public uint TargetFps
{
get => field;
set
{
if (value == field)
return;
uint previousTargetFps = field;
field = value;
renderInterval = value == 0 ? 0 : (ulong)(1f / value * 1000f);
OnTargetFpsChanged.Invoke(this, new(previousTargetFps));
}
} = 0;
public WindowState State public WindowState State
{ {
@@ -63,7 +44,7 @@ public class Sdl3Window : Behaviour, IWindow, IUpdate, IDisposable, IEnterUniver
public WindowFocusState FocusState public WindowFocusState FocusState
{ {
get => field; get => field;
private set internal set
{ {
if (value == field) if (value == field)
return; return;
@@ -84,26 +65,26 @@ public class Sdl3Window : Behaviour, IWindow, IUpdate, IDisposable, IEnterUniver
if (value is WindowMode.Windowed or WindowMode.BorderlessWindowed) if (value is WindowMode.Windowed or WindowMode.BorderlessWindowed)
{ {
SDL.SetWindowFullscreen(window, false); SDL.SetWindowFullscreen(Window, false);
SDL.SyncWindow(window); SDL.SyncWindow(Window);
} }
if (window != IntPtr.Zero) if (Window != IntPtr.Zero)
switch (value) switch (value)
{ {
case WindowMode.Windowed: SDL.SetWindowBordered(window, true); break; case WindowMode.Windowed: SDL.SetWindowBordered(Window, true); break;
case WindowMode.BorderlessWindowed: SDL.SetWindowBordered(window, false); break; case WindowMode.BorderlessWindowed: SDL.SetWindowBordered(Window, false); break;
case WindowMode.BorderlessFullscreen: SDL.SetWindowFullscreen(window, true); break; case WindowMode.BorderlessFullscreen: SDL.SetWindowFullscreen(Window, true); break;
case WindowMode.Fullscreen: SDL.SetWindowFullscreen(window, true); break; case WindowMode.Fullscreen: SDL.SetWindowFullscreen(Window, true); break;
} }
SDL.SyncWindow(window); SDL.SyncWindow(Window);
WindowMode previousMode = field; WindowMode previousMode = field;
field = value; field = value;
OnModeChanged.Invoke(this, new(previousMode)); OnModeChanged.Invoke(this, new(previousMode));
} }
} = WindowMode.Fullscreen; } = WindowMode.Windowed;
public WindowShowState ShowState public WindowShowState ShowState
{ {
@@ -113,12 +94,12 @@ public class Sdl3Window : Behaviour, IWindow, IUpdate, IDisposable, IEnterUniver
if (value == field) if (value == field)
return; return;
if (window != IntPtr.Zero) if (Window != IntPtr.Zero)
switch (value) switch (value)
{ {
case WindowShowState.Normal: SDL.RestoreWindow(window); break; case WindowShowState.Normal: SDL.RestoreWindow(Window); break;
case WindowShowState.Minimized: SDL.MinimizeWindow(window); break; case WindowShowState.Minimized: SDL.MinimizeWindow(Window); break;
case WindowShowState.Maximized: SDL.MaximizeWindow(window); break; case WindowShowState.Maximized: SDL.MaximizeWindow(Window); break;
} }
WindowShowState previousShowState = field; WindowShowState previousShowState = field;
@@ -135,15 +116,15 @@ public class Sdl3Window : Behaviour, IWindow, IUpdate, IDisposable, IEnterUniver
if (value == field) if (value == field)
return; return;
if (window != IntPtr.Zero) if (Window != IntPtr.Zero)
switch (value) switch (value)
{ {
case WindowResizeMode.Fixed: case WindowResizeMode.Fixed:
SDL.SetWindowResizable(window, false); SDL.SetWindowResizable(Window, false);
break; break;
case WindowResizeMode.Resizable: case WindowResizeMode.Resizable:
case WindowResizeMode.AspectLocked: case WindowResizeMode.AspectLocked:
SDL.SetWindowResizable(window, true); SDL.SetWindowResizable(Window, true);
break; break;
} }
@@ -198,26 +179,6 @@ public class Sdl3Window : Behaviour, IWindow, IUpdate, IDisposable, IEnterUniver
} }
} = Vector2DInt.Zero; } = Vector2DInt.Zero;
public void Update()
{
while (SDL.PollEvent(out SDL.Event e))
ProcessInternalEvents((SDL.EventType)e.Type);
ulong currentTick = SDL.GetTicks();
if (currentTick >= nextRenderTick)
{
nextRenderTick = currentTick + renderInterval;
SDL.SetRenderDrawColor(renderer, BackgroundColor.R, BackgroundColor.G, BackgroundColor.B, 255);
SDL.RenderClear(renderer);
// TODO Render
SDL.RenderPresent(renderer);
}
}
public void ExitUniverse(IUniverse universe) => CloseWindow(); public void ExitUniverse(IUniverse universe) => CloseWindow();
public void EnterUniverse(IUniverse universe) public void EnterUniverse(IUniverse universe)
{ {
@@ -242,43 +203,22 @@ public class Sdl3Window : Behaviour, IWindow, IUpdate, IDisposable, IEnterUniver
if (!SDL.Init(SDL.InitFlags.Video)) if (!SDL.Init(SDL.InitFlags.Video))
throw new Exception($"SDL_Init failed: {SDL.GetError()}"); throw new Exception($"SDL_Init failed: {SDL.GetError()}");
if (!SDL.CreateWindowAndRenderer(Title, Size.X, Size.Y, windowFlags, out window, out renderer)) Window = SDL.CreateWindow(Title, Size.X, Size.Y, windowFlags | SDL.WindowFlags.OpenGL);
if (Window == IntPtr.Zero)
throw new Exception($"SDL_CreateWindowAndRenderer failed: {SDL.GetError()}"); throw new Exception($"SDL_CreateWindowAndRenderer failed: {SDL.GetError()}");
} }
private void CloseWindow() private void CloseWindow()
{ {
if (renderer != IntPtr.Zero) SDL.DestroyRenderer(renderer); if (Window != IntPtr.Zero) SDL.DestroyWindow(Window);
if (window != IntPtr.Zero) SDL.DestroyWindow(window);
renderer = IntPtr.Zero; Window = IntPtr.Zero;
window = IntPtr.Zero;
}
private void ProcessInternalEvents(SDL.EventType eventType)
{
switch (eventType)
{
case SDL.EventType.WindowRestored: ShowState = WindowShowState.Normal; break;
case SDL.EventType.WindowMaximized: ShowState = WindowShowState.Maximized; break;
case SDL.EventType.WindowMinimized: ShowState = WindowShowState.Minimized; break;
case SDL.EventType.WindowFocusGained: FocusState = WindowFocusState.Focused; break;
case SDL.EventType.WindowFocusLost: FocusState = WindowFocusState.Unfocused; break;
case SDL.EventType.Quit:
State = WindowState.Closed;
BehaviourController.RemoveBehaviour(this);
break;
}
} }
public void Dispose() public void Dispose()
{ {
CloseWindow(); CloseWindow();
// SDL.Quit(); // TODO This possibly prevents multi window
GC.SuppressFinalize(this); GC.SuppressFinalize(this);
} }
} }

View File

@@ -0,0 +1,71 @@
using System.Collections.Generic;
using Engine.Core;
using SDL3;
namespace Engine.Integration.SDL3;
public class Sdl3WindowManager : Behaviour, IUpdate, IEnterUniverse, IExitUniverse
{
private readonly BehaviourCollector<Sdl3Window> windows = new();
private readonly BehaviourCollector<ISdl3EventProcessor> eventProcessors = new();
private readonly Dictionary<nint, Sdl3Window> windowsDictionary = [];
public Sdl3WindowManager()
{
windows.OnCollected.AddListener(OnWindowCollected);
windows.OnRemoved.AddListener(OnWindowRemoved);
}
private void OnWindowCollected(IBehaviourCollector<Sdl3Window> sender, IBehaviourCollector<Sdl3Window>.BehaviourCollectedArguments args)
=> windowsDictionary.Add(args.BehaviourCollected.Window, args.BehaviourCollected);
private void OnWindowRemoved(IBehaviourCollector<Sdl3Window> sender, IBehaviourCollector<Sdl3Window>.BehaviourRemovedArguments args)
{
windowsDictionary.Remove(args.BehaviourRemoved.Window);
if (windowsDictionary.Count == 0)
SDL.Quit();
}
public void EnterUniverse(IUniverse universe)
{
windows.Assign(universe);
eventProcessors.Assign(universe);
}
public void ExitUniverse(IUniverse universe)
{
windows.Unassign();
eventProcessors.Unassign();
}
public void Update()
{
while (SDL.PollEvent(out SDL.Event e))
ProcessInternalEvents(e);
}
private void ProcessInternalEvents(SDL.Event sdlEvent)
{
if (!windowsDictionary.TryGetValue(SDL.GetWindowFromID(sdlEvent.Window.WindowID), out Sdl3Window? window))
return;
for (int i = 0; i < eventProcessors.Count; i++)
eventProcessors[i].Process(sdlEvent);
SDL.EventType eventType = (SDL.EventType)sdlEvent.Type;
switch (eventType)
{
case SDL.EventType.WindowRestored: window.ShowState = WindowShowState.Normal; break;
case SDL.EventType.WindowMaximized: window.ShowState = WindowShowState.Maximized; break;
case SDL.EventType.WindowMinimized: window.ShowState = WindowShowState.Minimized; break;
case SDL.EventType.WindowFocusGained: window.FocusState = WindowFocusState.Focused; break;
case SDL.EventType.WindowFocusLost: window.FocusState = WindowFocusState.Unfocused; break;
case SDL.EventType.WindowCloseRequested:
case SDL.EventType.Quit: Universe.Remove(window.UniverseObject); break;
}
}
}

View File

@@ -22,7 +22,6 @@ public interface IWindow : IBehaviour
WindowResizeMode ResizeMode { get; set; } WindowResizeMode ResizeMode { get; set; }
string Title { get; set; } string Title { get; set; }
uint TargetFps { get; set; }
Vector2DInt Size { get; set; } Vector2DInt Size { get; set; }
ColorRGB BackgroundColor { get; set; } ColorRGB BackgroundColor { get; set; }

View File

@@ -7,6 +7,7 @@ using Engine.Integration.SDL3;
using Engine.Serializers.Yaml; using Engine.Serializers.Yaml;
using Engine.Systems.Network; using Engine.Systems.Network;
using Engine.Systems.Time; using Engine.Systems.Time;
using MyUniverse.Shared.Behaviours;
Universe universe = new(); Universe universe = new();
@@ -43,22 +44,51 @@ desktopParent.AddChild(universe.InstantiateUniverseObject().SetUniverseObject("T
Sdl3Window window = Engine.Core.Factory.BehaviourFactory.Instantiate<Sdl3Window>(); Sdl3Window window = Engine.Core.Factory.BehaviourFactory.Instantiate<Sdl3Window>();
window.Title = "MyUniverse"; window.Title = "MyUniverse";
window.Size = new(800, 600); window.Size = new(800, 600);
window.Mode = WindowMode.BorderlessWindowed; window.Mode = WindowMode.Windowed;
Sdl3Window window2 = Engine.Core.Factory.BehaviourFactory.Instantiate<Sdl3Window>();
window2.Title = "MyUniverse 2";
window2.Size = new(400, 300);
window2.Mode = WindowMode.Windowed;
universe.InstantiateUniverseObject().SetUniverseObject("SDL3 Manager", desktopParent)
.BehaviourController.AddBehaviour<Sdl3WindowManager>()
.BehaviourController.AddBehaviour<Sdl3KeyboardInputs>()
.BehaviourController.AddBehaviour<Sdl3GameInputs>();
IUniverseObject windowUO = universe.InstantiateUniverseObject().SetUniverseObject("SDL3 Window", desktopParent); IUniverseObject windowUO = universe.InstantiateUniverseObject().SetUniverseObject("SDL3 Window", desktopParent);
windowUO.BehaviourController.AddBehaviour<Transform2D>();
windowUO.BehaviourController.AddBehaviour<Engine.Systems.Graphics.DrawableShape2D>(Shape2D.Square, new ColorRGB(0, 0, 128));
windowUO.BehaviourController.AddBehaviour(window); windowUO.BehaviourController.AddBehaviour(window);
windowUO.BehaviourController.AddBehaviour<Sdl3Camera2D>().Zoom = 20f;
windowUO.BehaviourController.AddBehaviour<Sdl3TriangleBatch>();
windowUO.BehaviourController.AddBehaviour<CameraController2D>();
IUniverseObject windowUO2 = universe.InstantiateUniverseObject().SetUniverseObject("SDL3 Window 2", desktopParent);
windowUO2.BehaviourController.AddBehaviour<Transform2D>().Rotation = 45f;
windowUO2.BehaviourController.AddBehaviour<Engine.Systems.Graphics.DrawableShape2D>(Shape2D.Square, new ColorRGB(128, 0, 0));
windowUO2.BehaviourController.AddBehaviour(window2);
windowUO2.BehaviourController.AddBehaviour<Sdl3Camera2D>().Zoom = 20f;
windowUO2.BehaviourController.AddBehaviour<Sdl3TriangleBatch>();
windowUO2.BehaviourController.AddBehaviour<LinearRotator>();
// Testing Start // Testing Start
TickerStopwatch tickerStopwatch = windowUO.BehaviourController.AddBehaviour<TickerStopwatch>(); // TickerStopwatch tickerStopwatch = windowUO.BehaviourController.AddBehaviour<TickerStopwatch>();
tickerStopwatch.Period = 5f; // tickerStopwatch.Period = 5f;
tickerStopwatch.OnTick.AddListener(sender => // tickerStopwatch.OnTick.AddListener(sender =>
{ // {
if (window.IsInitialized) // if (window.IsInitialized)
windowUO.BehaviourController.RemoveBehaviour(window); // windowUO.BehaviourController.RemoveBehaviour(window);
else // else
windowUO.BehaviourController.AddBehaviour(window); // windowUO.BehaviourController.AddBehaviour(window);
}); // });
tickerStopwatch.Start(); // tickerStopwatch.Start();
// universe.InstantiateUniverseObject()
// .SetUniverseObject("Square")
// .BehaviourController.AddBehaviour<Transform2D>()
// .BehaviourController.AddBehaviour<Engine.Systems.Graphics.DrawableShape2D>(Shape2D.Square, new ColorRGB(255, 255, 0));
// Testing End // Testing End
universe.Initialize(); universe.Initialize();
@@ -82,6 +112,7 @@ while (true)
timeSinceStart += updateTimeSpan; timeSinceStart += updateTimeSpan;
universe.Update(new(timeSinceStart, updateTimeSpan)); universe.Update(new(timeSinceStart, updateTimeSpan));
universe.Draw();
lastRun = now; lastRun = now;
System.Threading.Thread.Sleep(1); System.Threading.Thread.Sleep(1);