feat: Step 3 Flecs.NET ECS integration with animated triangle
- Add Transform component and ECS world in Engine.Core. - TriangleRenderer.RenderWorld queries entities and applies transforms. - Animate a single triangle entity in Program.cs. - Pin ppy.SDL3-CS to 2026.520.0 and add linux-x64 RID for bundled native SDL3. - Add Flecs.NET.Debug/Release 4.0.4-build.546 per configuration. - Update SDL3 API usage for the 2026.520.0 binding. - Update architecture file to Silk.NET.Vulkan and Flecs 4.0.4.
This commit is contained in:
@@ -15,7 +15,7 @@ Cortex Engine is a 3D game engine built from scratch to provide a Unity-like dev
|
||||
- **Read** the complete ECS world state through native JSON serialization.
|
||||
- **Modify** the running engine via declarative JSON commands and, in Development Mode, via hot-reloaded C# scripts.
|
||||
|
||||
The architecture prioritizes **production maturity** over experimental technologies: Vulkan (via Vortice.Vulkan), Flecs.NET (C# bindings for the C-based Flecs ECS), SDL3-cs (ppy.SDL3-CS), and ImGui.NET/Hexa.NET.ImGui with a native Vulkan backend.
|
||||
The architecture prioritizes **production maturity** over experimental technologies: Vulkan (via Silk.NET.Vulkan), Flecs.NET (C# bindings for the C-based Flecs ECS), SDL3-cs (ppy.SDL3-CS), and ImGui.NET/Hexa.NET.ImGui with a native Vulkan backend.
|
||||
|
||||
---
|
||||
|
||||
@@ -137,7 +137,7 @@ All Roslyn and `AssemblyLoadContext` code is wrapped in `#if DEV_MODE`.
|
||||
|
||||
**Flecs.NET**
|
||||
|
||||
- NuGet: `Flecs.NET.Release` 4.0.3
|
||||
- NuGet: `Flecs.NET.Debug` (Debug) / `Flecs.NET.Release` (Release) 4.0.4-build.546
|
||||
- High-level C# wrapper over the C-based Flecs ECS
|
||||
- Supports .NET Standard 2.1, .NET 5/6/7/8
|
||||
- NativeAOT-compatible via static linking: `<FlecsStaticLink>true</FlecsStaticLink>`
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
|
||||
<DefineConstants>DEV_MODE</DefineConstants>
|
||||
<RuntimeIdentifier>linux-x64</RuntimeIdentifier>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)' == 'ReleaseAOT'">
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Engine.Core;
|
||||
using Engine.Core.Components;
|
||||
using Engine.Graphics;
|
||||
using Flecs.NET.Core;
|
||||
|
||||
namespace CortexEngine.App;
|
||||
|
||||
@@ -8,17 +11,21 @@ class Program
|
||||
{
|
||||
static void Main(string[] args)
|
||||
{
|
||||
Console.WriteLine("Cortex Engine Step 2 — Starting up...");
|
||||
Console.WriteLine("Cortex Engine Step 3 — Starting up...");
|
||||
|
||||
try
|
||||
{
|
||||
using var window = new Sdl3Window("Cortex Engine — Step 2", 1280, 720);
|
||||
using var world = World.Create();
|
||||
using var window = new Sdl3Window("Cortex Engine — Step 3", 1280, 720);
|
||||
var timing = new Timing();
|
||||
var input = new InputMapping();
|
||||
using var vulkan = new VulkanContext(window, enableValidation: false);
|
||||
using var swapchain = new Swapchain(vulkan);
|
||||
using var renderer = new TriangleRenderer(vulkan, swapchain);
|
||||
|
||||
var triangle = world.Entity("Triangle")
|
||||
.Set(new Transform(new Vector3(0.0f, 0.0f, 0.0f)));
|
||||
|
||||
var frames = 0;
|
||||
var lastFpsTime = 0.0;
|
||||
var lastWidth = window.Width;
|
||||
@@ -39,7 +46,16 @@ class Program
|
||||
swapchain.Recreate(lastWidth, lastHeight);
|
||||
}
|
||||
|
||||
renderer.RenderFrame();
|
||||
// Animate the entity transform.
|
||||
ref var transform = ref triangle.Ensure<Transform>();
|
||||
transform.Position = new Vector3(
|
||||
MathF.Sin((float)timing.TotalTime) * 0.5f,
|
||||
0.0f,
|
||||
0.0f);
|
||||
transform.Rotation = Quaternion.CreateFromAxisAngle(Vector3.UnitZ, (float)timing.TotalTime);
|
||||
transform.Scale = Vector3.One * (1.0f + MathF.Sin((float)timing.TotalTime * 2.0f) * 0.2f);
|
||||
|
||||
renderer.RenderWorld(world);
|
||||
|
||||
frames++;
|
||||
if (timing.TotalTime - lastFpsTime >= 1.0)
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
using System.Numerics;
|
||||
|
||||
namespace Engine.Core.Components;
|
||||
|
||||
/// <summary>
|
||||
/// Basic 3D transform component for the ECS.
|
||||
/// Uses SIMD-friendly System.Numerics types.
|
||||
/// </summary>
|
||||
public record struct Transform
|
||||
{
|
||||
public Vector3 Position;
|
||||
public Quaternion Rotation;
|
||||
public Vector3 Scale;
|
||||
|
||||
public Transform(Vector3? position = null, Quaternion? rotation = null, Vector3? scale = null)
|
||||
{
|
||||
Position = position ?? Vector3.Zero;
|
||||
Rotation = rotation ?? Quaternion.Identity;
|
||||
Scale = scale ?? Vector3.One;
|
||||
}
|
||||
|
||||
public static Transform Identity => new(Vector3.Zero, Quaternion.Identity, Vector3.One);
|
||||
|
||||
public Matrix4x4 GetMatrix()
|
||||
{
|
||||
return Matrix4x4.CreateScale(Scale)
|
||||
* Matrix4x4.CreateFromQuaternion(Rotation)
|
||||
* Matrix4x4.CreateTranslation(Position);
|
||||
}
|
||||
}
|
||||
@@ -19,6 +19,8 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.SDL3-CS" Version="2026.520.0" />
|
||||
<PackageReference Include="Flecs.NET.Debug" Version="4.0.4-build.546" Condition="'$(Configuration)' == 'Debug'" />
|
||||
<PackageReference Include="Flecs.NET.Release" Version="4.0.4-build.546" Condition="'$(Configuration)' == 'Release'" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -27,7 +27,7 @@ public sealed class InputMapping
|
||||
|
||||
public void ProcessEvent(SDL_Event evt)
|
||||
{
|
||||
switch ((SDL_EventType)evt.Type)
|
||||
switch ((SDL_EventType)evt.type)
|
||||
{
|
||||
case SDL_EventType.SDL_EVENT_KEY_DOWN:
|
||||
if (!_keysDown.Contains((SDL_Keycode)evt.key.key))
|
||||
|
||||
@@ -50,7 +50,7 @@ public sealed unsafe class Sdl3Window : IDisposable
|
||||
SDL_Event evt;
|
||||
while (SDL3.SDL_PollEvent(&evt))
|
||||
{
|
||||
switch ((SDL_EventType)evt.Type)
|
||||
switch ((SDL_EventType)evt.type)
|
||||
{
|
||||
case SDL_EventType.SDL_EVENT_QUIT:
|
||||
ShouldClose = true;
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Silk.NET.Vulkan" Version="2.21.0" />
|
||||
<PackageReference Include="Silk.NET.Vulkan.Extensions.KHR" Version="2.21.0" />
|
||||
<PackageReference Include="Flecs.NET.Debug" Version="4.0.4-build.546" Condition="'$(Configuration)' == 'Debug'" />
|
||||
<PackageReference Include="Flecs.NET.Release" Version="4.0.4-build.546" Condition="'$(Configuration)' == 'Release'" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
using System;
|
||||
using System.Numerics;
|
||||
using Flecs.NET.Core;
|
||||
using Silk.NET.Core;
|
||||
using Silk.NET.Vulkan;
|
||||
using Engine.Core.Components;
|
||||
|
||||
namespace Engine.Graphics;
|
||||
|
||||
/// <summary>
|
||||
/// Renders a colored triangle using a vertex buffer and a simple graphics pipeline.
|
||||
/// Uses Silk.NET.Vulkan.
|
||||
/// Uses Silk.NET.Vulkan and reads entity transforms from the ECS world.
|
||||
/// </summary>
|
||||
public sealed unsafe class TriangleRenderer : IDisposable
|
||||
{
|
||||
@@ -115,7 +118,7 @@ public sealed unsafe class TriangleRenderer : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
public void RenderFrame()
|
||||
public void RenderWorld(World world)
|
||||
{
|
||||
var frame = _currentFrame % 2;
|
||||
|
||||
@@ -160,7 +163,14 @@ public sealed unsafe class TriangleRenderer : IDisposable
|
||||
var vertexBuffer = _vertexBuffer.Buffer;
|
||||
var offset = 0ul;
|
||||
_context.Vk.CmdBindVertexBuffers(cmd, 0, 1, &vertexBuffer, &offset);
|
||||
_context.Vk.CmdDraw(cmd, 3, 1, 0, 0);
|
||||
|
||||
var drawCmd = cmd;
|
||||
world.Each((Entity e, ref Transform transform) =>
|
||||
{
|
||||
var bytes = BuildTriangleVertices(transform);
|
||||
_vertexBuffer.Update(bytes);
|
||||
_context.Vk.CmdDraw(drawCmd, 3, 1, 0, 0);
|
||||
});
|
||||
|
||||
_context.Vk.CmdEndRenderPass(cmd);
|
||||
_context.Vk.EndCommandBuffer(cmd);
|
||||
@@ -197,6 +207,39 @@ public sealed unsafe class TriangleRenderer : IDisposable
|
||||
_currentFrame++;
|
||||
}
|
||||
|
||||
private byte[] BuildTriangleVertices(Transform transform)
|
||||
{
|
||||
var matrix = transform.GetMatrix();
|
||||
var positions = new Vector3[]
|
||||
{
|
||||
new Vector3(0.0f, -0.5f, 0.0f),
|
||||
new Vector3(0.5f, 0.5f, 0.0f),
|
||||
new Vector3(-0.5f, 0.5f, 0.0f)
|
||||
};
|
||||
var colors = new[]
|
||||
{
|
||||
new Vector3(1.0f, 0.0f, 0.0f),
|
||||
new Vector3(0.0f, 1.0f, 0.0f),
|
||||
new Vector3(0.0f, 0.0f, 1.0f)
|
||||
};
|
||||
|
||||
var bytes = new byte[3 * 5 * sizeof(float)];
|
||||
fixed (byte* p = bytes)
|
||||
{
|
||||
var dst = (float*)p;
|
||||
for (var i = 0; i < 3; i++)
|
||||
{
|
||||
var transformed = Vector3.Transform(positions[i], matrix);
|
||||
dst[i * 5 + 0] = transformed.X;
|
||||
dst[i * 5 + 1] = transformed.Y;
|
||||
dst[i * 5 + 2] = colors[i].X;
|
||||
dst[i * 5 + 3] = colors[i].Y;
|
||||
dst[i * 5 + 4] = colors[i].Z;
|
||||
}
|
||||
}
|
||||
return bytes;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_context.Vk.DeviceWaitIdle(_context.Device);
|
||||
|
||||
@@ -102,6 +102,14 @@ public sealed unsafe class VertexBuffer : IDisposable
|
||||
_context.Vk.UnmapMemory(_context.Device, Memory);
|
||||
}
|
||||
|
||||
public void Update(ReadOnlySpan<byte> data)
|
||||
{
|
||||
if ((ulong)data.Length != Size)
|
||||
throw new ArgumentException($"Vertex buffer update size mismatch: {data.Length} != {Size}");
|
||||
|
||||
CopyData(data);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_context.Vk.DeviceWaitIdle(_context.Device);
|
||||
|
||||
Reference in New Issue
Block a user