From 177c47e8f82dd4a13adfd24a12819fef168be1d7 Mon Sep 17 00:00:00 2001 From: MDietermann Date: Thu, 28 May 2026 12:22:19 +0200 Subject: [PATCH] feat: wire game window, render loop, and fly-camera input --- Core/Game.cs | 110 +++++++++++++++++++++++++++++++++++++++++++++++++++ Program.cs | 23 ++++++----- 2 files changed, 124 insertions(+), 9 deletions(-) create mode 100644 Core/Game.cs diff --git a/Core/Game.cs b/Core/Game.cs new file mode 100644 index 0000000..963d78b --- /dev/null +++ b/Core/Game.cs @@ -0,0 +1,110 @@ +using OpenTK.Graphics.OpenGL4; +using OpenTK.Mathematics; +using OpenTK.Windowing.Common; +using OpenTK.Windowing.Desktop; +using OpenTK.Windowing.GraphicsLibraryFramework; +using VoxelEngine.Rendering; +using VoxelEngine.World; + +namespace VoxelEngine.Core; + +public sealed class Game : GameWindow +{ + private const float MoveSpeed = 8f; // units per second + private const float Sensitivity = 0.15f; // degrees per pixel + + private readonly BlockRegistry _registry = new(); + private Shader _shader = null!; + private Texture _texture = null!; + private ChunkRenderer _renderer = null!; + private Camera _camera = null!; + + public Game(GameWindowSettings gameSettings, NativeWindowSettings nativeSettings) + : base(gameSettings, nativeSettings) { } + + protected override void OnLoad() + { + base.OnLoad(); + + GL.ClearColor(0.45f, 0.65f, 0.95f, 1.0f); + GL.Enable(EnableCap.DepthTest); + GL.Enable(EnableCap.CullFace); + + _shader = new Shader("Assets/shaders/chunk.vert", "Assets/shaders/chunk.frag"); + _texture = new Texture("Assets/textures/atlas.png"); + + Chunk chunk = BuildChunk(); + float[] mesh = new ChunkMesher().Build(chunk, _registry); + _renderer = new ChunkRenderer(mesh); + + _camera = new Camera(new Vector3(8f, 20f, 40f), ClientSize.X / (float)ClientSize.Y); + + CursorState = CursorState.Grabbed; + } + + private Chunk BuildChunk() + { + var chunk = new Chunk(); + for (int x = 0; x < Chunk.Size; x++) + for (int z = 0; z < Chunk.Size; z++) + for (int y = 0; y < Chunk.Size; y++) + { + if (y == Chunk.Size - 1) chunk[x, y, z] = _registry.Grass.Id; + else if (y >= Chunk.Size - 4) chunk[x, y, z] = _registry.Dirt.Id; + else chunk[x, y, z] = _registry.Stone.Id; + } + return chunk; + } + + protected override void OnUpdateFrame(FrameEventArgs args) + { + base.OnUpdateFrame(args); + if (!IsFocused) return; + + KeyboardState keyboard = KeyboardState; + if (keyboard.IsKeyDown(Keys.Escape)) { Close(); return; } + + float distance = MoveSpeed * (float)args.Time; + if (keyboard.IsKeyDown(Keys.W)) _camera.Move(_camera.Front * distance); + if (keyboard.IsKeyDown(Keys.S)) _camera.Move(-_camera.Front * distance); + if (keyboard.IsKeyDown(Keys.A)) _camera.Move(-_camera.Right * distance); + if (keyboard.IsKeyDown(Keys.D)) _camera.Move(_camera.Right * distance); + if (keyboard.IsKeyDown(Keys.Space)) _camera.Move(Vector3.UnitY * distance); + if (keyboard.IsKeyDown(Keys.LeftShift)) _camera.Move(-Vector3.UnitY * distance); + + Vector2 delta = MouseState.Delta; + _camera.AddRotation(delta.X * Sensitivity, -delta.Y * Sensitivity); + } + + protected override void OnRenderFrame(FrameEventArgs args) + { + base.OnRenderFrame(args); + + GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit); + + _shader.Use(); + _shader.SetMatrix4("model", Matrix4.Identity); + _shader.SetMatrix4("view", _camera.GetViewMatrix()); + _shader.SetMatrix4("projection", _camera.GetProjectionMatrix()); + _shader.SetInt("atlas", 0); + _texture.Use(TextureUnit.Texture0); + _renderer.Draw(); + + SwapBuffers(); + } + + protected override void OnResize(ResizeEventArgs e) + { + base.OnResize(e); + GL.Viewport(0, 0, e.Width, e.Height); + if (_camera is not null) _camera.AspectRatio = e.Width / (float)e.Height; + } + + protected override void OnUnload() + { + _renderer.Dispose(); + _texture.Dispose(); + _shader.Dispose(); + base.OnUnload(); + } +} diff --git a/Program.cs b/Program.cs index 7bbafb5..0e771f4 100644 --- a/Program.cs +++ b/Program.cs @@ -1,11 +1,16 @@ -using OpenTK; +using OpenTK.Mathematics; +using OpenTK.Windowing.Common; +using OpenTK.Windowing.Desktop; +using VoxelEngine.Core; -namespace VoxelEngine; - -class Program +var nativeSettings = new NativeWindowSettings { - static void Main(string[] args) - { - Console.WriteLine("Hello, World!"); - } -} \ No newline at end of file + ClientSize = new Vector2i(1280, 720), + Title = "VoxelEngine", + APIVersion = new Version(3, 3), + Profile = ContextProfile.Core, + Flags = ContextFlags.ForwardCompatible +}; + +using var game = new Game(GameWindowSettings.Default, nativeSettings); +game.Run();