Last active
January 22, 2025 01:08
-
-
Save Kiokiok/414fa2f0d83bc22bfdf8d6c66ceef0a8 to your computer and use it in GitHub Desktop.
Simple example on how to render flecs entities in Godot. Two ways : individual instances or multimesh.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Godot; | |
using Flecs.NET.Core; | |
using static Flecs.NET.Bindings.Native; | |
public partial class FlecsBootstrapper : Node3D | |
{ | |
[Export] | |
Mesh renderMesh; | |
[Export] | |
int renderAmount = 1000; | |
[Export] | |
bool useMultimesh = false; | |
World ecsWorld; | |
Rid multimesh; | |
Rid instance; | |
public override void _Ready() | |
{ | |
ecsWorld = World.Create(); | |
if(useMultimesh) | |
{ | |
multimesh = RenderingServer.MultimeshCreate(); | |
instance = RenderingServer.InstanceCreate(); | |
RenderingServer.InstanceSetBase(instance, multimesh); | |
RenderingServer.InstanceSetTransform(instance, Transform3D.Identity); | |
RenderingServer.InstanceSetScenario(instance, GetWorld3D().Scenario); | |
RenderingServer.MultimeshSetMesh(multimesh, renderMesh.GetRid()); | |
RenderingServer.MultimeshAllocateData(multimesh, renderAmount, RenderingServer.MultimeshTransformFormat.Transform3D); | |
} | |
for (int i = 0; i < renderAmount; i++) | |
{ | |
Vector3 randomPos = new Vector3(); | |
randomPos.X = GD.RandRange(-150, 150); | |
randomPos.Y = GD.RandRange(-150, 150); | |
randomPos.Z = GD.RandRange(-150, 150); | |
CreateEntity(renderMesh, new Transform3D(Basis.Identity, randomPos), i); | |
} | |
// support for the flecs explorer | |
ecsWorld.Import<Ecs.Units>(); | |
ecsWorld.Import<Ecs.Monitor>(); | |
ecsWorld.Set(default(EcsRest)); | |
// --- SYSTEMS AND OBSERVER | |
// Dummy rotation system | |
// Changes the entity rotation every frame | |
{ | |
FilterBuilder rotationRoutineFilterBuilder = ecsWorld.FilterBuilder() | |
.Term<TransformComponent>(); | |
ecsWorld.Routine(rotationRoutineFilterBuilder, callback: (Iter it) => | |
{ | |
Column<TransformComponent> tranformsCol = it.Field<TransformComponent>(1); | |
for (int i = 0; i < it.Count(); i++) | |
{ | |
// grab the Tranform3D | |
Transform3D trs = tranformsCol[i].value; | |
trs = trs.RotatedLocal(Vector3.Up, 2 * it.DeltaTime()); | |
tranformsCol[i].value = trs; | |
} | |
}); | |
} | |
// Rendering sync system | |
// Update the render instance using the RenderingServer | |
{ | |
FilterBuilder renderUpdateRoutineFilterBuilder = ecsWorld.FilterBuilder() | |
.Term<TransformComponent>() | |
.Term<RenderIDComponent>(); | |
ecsWorld.Routine(renderUpdateRoutineFilterBuilder, callback: (Iter it) => | |
{ | |
Column<TransformComponent> tranformsCol = it.Field<TransformComponent>(1); | |
Column<RenderIDComponent> renderIDCol = it.Field<RenderIDComponent>(2); | |
if (useMultimesh) | |
{ | |
for (int i = 0; i < it.Count(); i++) | |
{ | |
Transform3D trs = tranformsCol[i].value; | |
int myID = renderIDCol[i].id; | |
RenderingServer.MultimeshInstanceSetTransform(multimesh, myID, trs); | |
} | |
} | |
else | |
{ | |
for (int i = 0; i < it.Count(); i++) | |
{ | |
Transform3D trs = tranformsCol[i].value; | |
Rid myInstanceID = renderIDCol[i].reference; | |
RenderingServer.InstanceSetTransform(myInstanceID, trs); | |
} | |
} | |
}); | |
} | |
// Cleaning up observer | |
{ | |
FilterBuilder cleanupObserverFilterBuilder = ecsWorld.FilterBuilder() | |
.Term<RenderIDComponent>(); | |
ObserverBuilder cleanupObserverBuilder = ecsWorld.ObserverBuilder() | |
.Event(Ecs.OnRemove); | |
// Example of an observer that automatically cleans up Render Instances on the Rendering Server | |
ecsWorld.Observer(cleanupObserverFilterBuilder, cleanupObserverBuilder, callback: (Iter it) => | |
{ | |
Column<RenderIDComponent> renderIDCol = it.Field<RenderIDComponent>(1); | |
if (useMultimesh) | |
{ | |
// TODO : Implement Multimesh instance deletion | |
} | |
else | |
{ | |
for (int i = 0; i < it.Count(); i++) | |
{ | |
Rid instanceRid = renderIDCol[i].reference; | |
RenderingServer.FreeRid(instanceRid); | |
} | |
} | |
}); | |
} | |
} | |
void CreateEntity(Mesh _mesh, Transform3D _trs, int _id) | |
{ | |
Entity entity = ecsWorld.Entity(); | |
// create transform component | |
TransformComponent transformComponent = new TransformComponent(); | |
transformComponent.value = _trs; | |
entity.Set<TransformComponent>(transformComponent); | |
// create the render instance and set it up | |
if(useMultimesh) | |
{ | |
RenderingServer.MultimeshInstanceSetTransform(multimesh, _id, _trs); | |
RenderIDComponent renderID = new RenderIDComponent(); | |
renderID.id = _id; | |
entity.Set<RenderIDComponent>(renderID); | |
} | |
else | |
{ | |
Rid instance = RenderingServer.InstanceCreate(); | |
RenderingServer.InstanceSetBase(instance, _mesh.GetRid()); | |
RenderingServer.InstanceSetTransform(instance, transformComponent.value); | |
RenderingServer.InstanceSetScenario(instance, GetWorld3D().Scenario); | |
// set renderID component | |
RenderIDComponent renderID = new RenderIDComponent(); | |
renderID.reference = instance; | |
renderID.id = _id; | |
entity.Set<RenderIDComponent>(renderID); | |
} | |
} | |
public override void _Process(double delta) | |
{ | |
ecsWorld.Progress((float)delta); | |
} | |
} | |
// --- COMPONENTS | |
public struct TransformComponent | |
{ | |
public Transform3D value; | |
} | |
// For the sake of simplicity we reuse the same component | |
// In a real scenerio, we'd choose either individual Instances or multimesh instances | |
public struct RenderIDComponent | |
{ | |
public Rid reference; | |
public int id; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment