![]() |
VOOZH | about |
dotnet add package 3DEngine.Debug --version 1.0.1
NuGet\Install-Package 3DEngine.Debug -Version 1.0.1
<PackageReference Include="3DEngine.Debug" Version="1.0.1" />
<PackageVersion Include="3DEngine.Debug" Version="1.0.1" />Directory.Packages.props
<PackageReference Include="3DEngine.Debug" />Project file
paket add 3DEngine.Debug --version 1.0.1
#r "nuget: 3DEngine.Debug, 1.0.1"
#:package 3DEngine.Debug@1.0.1
#addin nuget:?package=3DEngine.Debug&version=1.0.1Install as a Cake Addin
#tool nuget:?package=3DEngine.Debug&version=1.0.1Install as a Cake Tool
<p align="center" style="text-align:center"> <img src="Images/3DEngineIcon.png" alt="3D Engine Icon" width="256"/> </p>
<h1 align="center" style="text-align:center">3D Engine</h1> <h4 align="center" style="text-align:center">C# 3D Game Engine (Vulkan + SDL3).</h4> <p align="center" style="text-align:center">Editor planned with Avalonia UI.</p>
<p align="center" style="text-align:center"> <img alt=".NET" src="https://img.shields.io/badge/.NET-10.0-512BD4"> <img alt="Graphics API" src="https://img.shields.io/badge/Graphics-Vulkan-AC162C"> <img alt="Windowing" src="https://img.shields.io/badge/Windowing-SDL3-0B7BB2"> </p>
<p align="center" style="text-align:center"> <img alt="Status" src="https://img.shields.io/badge/Status-Early%20Preview-yellow"> <img alt="Platforms" src="https://img.shields.io/badge/Platforms-Linux%20%7C%20Windows%20%7C%20macOS-lightgrey"> </p>
This repository is the restart of a cross‑platform 3D engine written in C#. The runtime is being built on Vulkan for rendering and SDL3 for windowing/input. An editor built with Avalonia UI is planned but not implemented yet.
The runtime already includes a minimal ECS and a behavior system powered by a Roslyn source generator. You can author gameplay logic in two ways:
[Behavior], add methods with stage attributes like
[OnUpdate], and the generator wires them into the schedule.ECSWorld queries and ECSCommands.See Engine/Program.cs for usage examples.
Implemented (early preview):
DefaultPlugins) for window/time/input/ECS/ImGuiIn Progress / Planned (see roadmap for detail):
Prerequisites:
TargetFramework (see Engine/Engine.csproj, currently net10.0). Preview SDK may be required.Clone + build:
# Clone
git clone https://github.com/CanTalat-Yakan/3DEngine.git
cd 3DEngine
# Restore & build all projects
dotnet build
# Run the engine sample (runtime entry point)
dotnet run --project Engine
Open in an IDE (Rider/VS/VSCode) using 3DEngine.sln if you prefer.
Notes:
An automatic minimal behavior + system example:
[Behavior]
public struct Spinner
{
public float Angle;
[OnStartup]
public static void Spawn(BehaviorContext ctx)
{
var e = ctx.Ecs.Spawn();
ctx.Ecs.Add(e, new Spinner { Angle = 0f });
}
[OnUpdate]
public void Tick(BehaviorContext ctx)
{
Angle += (float)ctx.Time.DeltaSeconds * 90f; // 90 deg/s
Console.WriteLine($"Entity {ctx.EntityID} angle now {Angle:0.00}");
}
}
To add a manual system instead:
public sealed class Program
{
[STAThread]
private static void Main()
{
new App(Config.GetDefault())
.AddPlugin(new DefaultPlugins())
.AddPlugin(new SamplePlugin())
.Run();
}
}
public sealed class SamplePlugin : IPlugin
{
public void Build(App app)
{
app.AddSystem(Stage.Startup, (world) =>
{
var ecs = world.Resource<EcsWorld>();
var e = ecs.Spawn();
ecs.Add(e, new Spinner { Angle = 0f });
});
app.AddSystem(Stage.Update, (world) =>
{
var ecs = world.Resource<EcsWorld>();
foreach (var (e, spinner) in ecs.Query<Spinner>())
{
var newSpinner = spinner;
newSpinner.Angle += (float)world.Resource<Time>().DeltaSeconds * 45f;
ecs.Update(e, newSpinner);
}
});
}
}
The engine drives a Bevy-like staged loop (Source/App/Stage.cs):
Startup (once), then per frame: First → PreUpdate → Update → PostUpdate → Render → Last.SystemFn(World world) delegates added to stages via App.AddSystem(stage, system) and executed by
Schedule.Plugins configure the app and register systems. DefaultPlugins wires everything you typically need:
World is a simple resource container (Bevy-style). Insert and fetch singletons by type:
app.InsertResource(new MyService()) or world.InsertResource(value)world.Resource<T>() to retrieve; throws if missingBehaviorContext.Res<T>() is a shortcut for world.Resource<T>()Common resources used by systems/behaviors:
EcsWorld – entity/component storage and queriesEcsCommands – queued mutations applied after Update (at PostUpdate)AppWindow, Time, GUIRenderer, etc.int IDs only (no public handle type). Create with var id = ecs.Spawn(); and remove with ecs.Despawn(id);.Add<T>(id, comp), Update<T>(id, comp), TryGet<T>(id, out comp), Has<T>(id), Remove<T>(id).ecs.Query<T>(), ecs.Query<T1,T2>(), ecs.Query<T1,T2,T3>() iterate matching entities. Joins walk the smallest set for speed.EcsCommands (ID-only): Spawn, Add<T>, Despawn. Commands are applied in PostUpdate.Despawn, components that implement IDisposable are disposed.Beyond Query, the ECS exposes zero-allocation iteration and transforms for hot loops:
foreach (var rc in ecs.IterateRef<T>()) { rc.Component ... } (marks changed automatically).TransformEach<T>((id, c) => { /* mutate */ return c; })ParallelTransformEach<T>((id, c) => { /* mutate */ return c; })var span = ecs.GetSpan<T>(); returns entities/components spans for tight loops (manual marking recommended).See also: ECS Iteration Modes, Change Tracking, and Entity Generations.
Update after changing a local copy.(ReadOnlySpan<int> Entities, Span<T> Components) for manual indexed loops.TransformEach<T> (no-op transform) to mark, or call Update.Examples:
// Read-only query
foreach (var (e, comp) in ecs.Query<Position>())
{
// inspect comp
}
// In-place mutation with IterateRef (marks changed)
foreach (var rc in ecs.IterateRef<Velocity>())
{
rc.Component.dx += 1;
}
// Transform helper (marks changed)
ecs.TransformEach<Position>((e, p) => { p.x += 1; return p; });
// Parallel transform (marks changed)
ecs.ParallelTransformEach<Position>((e, p) => { p.x += 1; return p; });
// Spans (manual marking)
var span = ecs.GetSpan<Mass>();
for (int i = 0; i < span.Entities.Length; i++)
{
span.Components[i].value *= 2;
}
// mark all as changed without altering values
ecs.TransformEach<Mass>((e, m) => m);
Update, when a TransformEach writes, or as you iterate via IterateRef.Changed<T>(id) reads the bit; bits are cleared at BeginFrame() (stage First).Despawn(id), the generation for that ID is incremented and the ID is added to a free list for reuse.ecs.GetGeneration(id).EcsWorld.cs – entity lifecycle (spawn/despawn), frame management, counts, and entity listsEcsWorld.Components.cs – component storage, CRUD, spans, and single-type queryEcsWorld.Queries.cs – multi-type queries and predicatesEcsWorld.RefIterators.cs – ref iterators, transforms, and parallel transformsAuthor gameplay in a script-like way:
[Behavior].[OnStartup], [OnFirst], [OnPreUpdate], [OnUpdate], [OnPostUpdate], [OnRender], [OnLast].[With(typeof(Position), typeof(Velocity))][Without(typeof(Disabled))][Changed(typeof(Transform))]With joins of up to two component types. If more are specified, it falls back
to querying only the behavior component and applies Without/Changed checks inside the loop.Static vs Instance methods:
BehaviorContext.this. The
generator:
ecs.Query<YourBehavior>()ctx.EntityIDecs.UpdateCreating entities for instance behaviors:
[OnStartup] to
spawn and add the behavior component.Access to engine services:
ctx.Res<T>() for other resources (e.g., Time, Input, etc.).ctx.Ecs and ctx.Cmd provide ECS access.Reference types inside behavior structs:
[OnStartup] as needed.Examples:
using ImGuiNET;
[Behavior]
public struct HUDOverlay
{
[OnUpdate]
public static void Draw(BehaviorContext ctx)
{
ImGui.Begin("HUD");
ImGui.Text($"FPS: {(1.0 / ctx.Time.DeltaSeconds):0}");
ImGui.End();
}
}
[Behavior]
public struct Spawner
{
public float a;
private float b { get; set; }
[OnStartup]
public static void Init(BehaviorContext ctx)
{
var e = ctx.Ecs.Spawn();
ctx.Ecs.Add(e, new Spawner { a = 1.0f });
}
[OnUpdate]
public void Tick(BehaviorContext ctx)
{
b += (float)ctx.Time.DeltaSeconds;
Console.WriteLine($"Spawner running. a={a}, b={b}");
}
}
public class SomeDisposable : IDisposable
{
private float _num = 2;
public string Log() => _num.ToString();
public void Dispose()
{
// Cleanup resources
}
}
[Behavior]
public struct HeavyBehavior : IDisposable
{
private SomeDisposable _handle;
[OnStartup]
public static void Init(BehaviorContext ctx)
{
var e = ctx.Ecs.Spawn();
ctx.Ecs.Add(e, new HeavyBehavior { _handle = new SomeDisposable() });
}
[OnUpdate]
public void Tick(BehaviorContext ctx)
{
Console.WriteLine(_handle.Log());
}
public void Dispose()
{
_handle?.Dispose();
_handle = null;
}
}
Prefer writing systems directly? Use App.AddSystem and operate on EcsWorld:
app.AddSystem(Stage.Update, (World w) =>
{
var ecs = w.Resource<EcsWorld>();
foreach (var (e, comp) in ecs.Query<MyComponent>())
{
// mutate comp and write back
ecs.Update(e, comp);
}
});
You can mix and match: the behavior generator emits systems under the hood; you can still register hand-written systems alongside them.
Engine.SourceGen scans for [Behavior] structs and methods with stage attributes, then emits:
BehaviorsPlugin that registers those systems into the app.DefaultPlugins includes BehaviorsPlugin, so behaviors are picked up automatically at build time—no manual
registration required.
IDisposable on the struct and dispose those references in
Dispose(). The ECS will invoke Dispose() for components on Despawn.Items are aspirational and subject to change as the project evolves.
Early days! If you want to help:
By participating, you agree to abide by our .
A formal guideline will be added once the editor and initial subsystems land.
Code: MIT license. See for the full text.
Contributions: By submitting a contribution, you agree to license your contribution under the same license as this repository.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 net10.0 is compatible. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
Showing the top 1 NuGet packages that depend on 3DEngine.Debug:
| Package | Downloads |
|---|---|
|
3DEngine
3D Game Engine - Vulkan - SDL3 - .NET 10 - C# 14 |
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|