Flecs v4.0
A fast entity component system (ECS) for C & C++
|
This tutorial hasn't been updated for v4 yet! Use the Script Manual for now.
This tutorial shows you how to use Flecs script, which is a declarative language for creating entities without having to write and compile code. Flecs script can be used for different things, such as building scenes, assets, or as a language for configuration files.
In this tutorial we will be designing a simple fence asset from scratch, and make it parameterizable so it can be easily reused across scenes. By the end of the tutorial we'll have an asset that looks like this:
Note: in order to use Flecs script an app needs to be built with the FLECS_SCRIPT
addon.
The Flecs explorer is a web-based application that lets us write scripts and see the results directly. In the tutorial we will use the explorer in combination with the Flecs playground, which has a rendering canvas and comes preloaded with a number of modules and assets.
Go to this URL to open the explorer with the playground: https://www.flecs.dev/explorer/?wasm=https://www.flecs.dev/explorer/playground.js
The page should look similar to this:
The panel on the left is the entity treeview, which shows all of the entities in our scene. The center view is the canvas, which shows us the renderable entities of our scene (more on that later). Finally, the pane on the right is the editor, where we can write flecs scripts.
At any point in time you can disable panels by clicking on the "x" in the top-right corner. Panels can be brought back by pressing on their button in the menu bar on the left.
One other important thing is the link button in the top-right of the screen. You can use that button to obtain a link to your content, which is a good way to save progress, or to share what you've created with other people.
Currently the explorer is showing the default scene. Let's clear it by removing all code from the editor. You should now see an empty canvas:
Lets create an entity by typing its name into the editor:
Notice that as we are typing the entity shows up in the treeview:
Entities are automatically created if they did not exist yet. Try entering the same entity twice:
Only one entity shows up in the treeview. The second time my_entity
was parsed it already existed, so nothing needed to be done.
Now that we have an entity, let's add a few components and tags to it. Change the text in the editor to this, to create an entity with a tag called SpaceShip
:
Note that we didn't have to explicitly declare SpaceShip
in advance, and that it also shows up as entity in the treeview. We can add multiple things to an entity this way:
To avoid repeating the entity name many times, we can use the {}
operators to open a scope for the entity. Inside the scope we can list components for the entity by prefixing them with a dash (-
):
We can inspect the entity and its contents by opening it in the entity inspector. To do this, click on the entity in the treeview. You should now see this:
Note how the SpaceShip
and FasterThanLight
tags show up in the editor. There is also a Script: main
tag, which exists for Flecs to keep track of which entities got created by our script.
Adding a component is similar to adding tag with a value. Let's add the Position3
component from the flecs.components.transform
module which comes preloaded with the playground. Note how it also shows up in the inspector when we add this code:
Having to type the full module name each time we want to use the Position3
component would be annoying. Add this line to the top of the script:
We can now use the component without the module name, which looks much cleaner:
If all went well, the playground should now look like this:
Note how after we added the Position3
component, the inspector also shows the Transform
and WorldCell
components. This happens because the playground imports modules that implement world partitioning and transforms, which we get for free just by using flecs.components.transform.Position3
component.
In addition to components and tags we can also add relationship pairs to entities. To add a pair, add this line to the scope of the entity, and note how it shows up in the inspector:
Entities can be created in hierarchies. A child entity is created in the scope of an entity just like the components and tags, but without the preceding -
. Add this to the scope of the entity:
You can see the hierarchy this created in the treeview by expanding my_entity
:
Congratulations! You now know how to create entities, hierarchies, and how to add components and tags. None of the entities we created so far are visible in the canvas however, so lets do something about that.
We will be building a fence asset from just primitive shapes, where each entity is a single shape (note that in a real game you would likely want to use actual meshes).
The renderer uses regular ECS queries to find the entities to render. For our entities to show up in these queries, they need to have at least three components:
Position3
Rgb
(a color)Box
or Rectangle
Let's start by drawing a plane. First remove all code from the editor except for this line:
Now add these lines into the editor to create our ground plane
:
Something happened! But it doesn't look quite right:
The rectangle is rotated the wrong way for our plane. To fix this we need to rotate it 90 degrees or π/2
radians on the x axis. First lets define π
as a constant value in our script:
Now add this line to the scope of plane
:
That looks better:
Let's increase the sides of the plane to 10000
so that the fog effect makes it blends in with the background, which gives the illusion of a horizon:
Note that the PI
variable does not show up in the treeview. Variables do not create entities, and only exist within the context of a script.
Let's now add a cube to the scene. The code for this looks similar to the plane:
The box is showing up, but it's intersecting with our plane:
To fix this, we can move it up by setting the y
member of Position3
to half its size:
Now the entire cube is visible, and should look like this:
We now have all of the basic knowledge to start drawing a fence! Note that if you want to move the camera around, first click on the canvas to give it focus. You can now move the camera around. To release focus from the canvas, click on it again (the green border should disappear).
To draw a fence we just need to combine differently sized boxes together. First remove the code for the cube from the editor.
Before drawing the boxes, lets first make sure they're all created with the same color. We could add a color component with the same values to each entity, but that gets tedious if we want to change the color. Instead, we can use the with
statement:
Inside the with
statement we can now create two box entities for the left and right pillar of the fence:
The playground should now look like this:
That works, but the code is starting to look a bit unwieldy. There are lots of magic numbers which will become difficult to maintain as our asset gets more complex. Let's see if we can clean this up a bit.
First lets define two variables for the color and box shape of the pillars. We already saw an example of a variable when we defined PI
. These looks similar, except that because they are composite values, we also define their type:
This is better, but pillar_box
still contains values that we have to update each time we want to change the shape of our fence. Instead we can do this, which is a bit more typing now but will be easier to maintain later:
Let's also add an additional width
variable which stores the width of the fence (or the distance between two pillars):
We can now update the code that creates the pillars to this:
This will reproduce the exact same scene, but we can now tweak the variables to change the shape of our fence. Try playing around with the values of the different variables to see their effect!
This is no fence yet. We're still missing the crossbars. Let's add another entity to see what it looks like:
Let's add a second entity and space the two entities out a bit so they don't overlap. Also make sure to give both entities a different name, or else we'll end up overwriting the previous values:
That starts to look more like a fence! Just like before however, we have a bunch of magic values and redundancies, but we now know how to fix that. First, let's define a bar_sep
variable to specify how far apart the bars should be separated:
Let's also create a variable for the bar shape and bar height, similar to what we did before. Let's make the bar thickness scale with the width of a pillar:
With that in place, we can cleanup the entity code for the bars:
We now have our fence, which we can fully tweak with by changing the values of the variables! There are a few problems with the current code however:
The code is also growing. To make sure you don't lose track of what you're doing you can add comments:
We have the basic structure of a fence, but what if we wanted two fences? We could of course duplicate all of the code, but that would be impractical. Instead we can package up what we have in a prefab.
Turning the existing code into a prefab is easy. Simply add these lines around the fence code:
This creates an entity Fence
with the tag Prefab
, and stores all entities we created so far as children of the Fence
entity. This is the same as running the following code, but a bit more convenient:
Once you put the fence entities inside the prefab scope, you'll notice a few things:
Fence
in the treeviewThe reason that the fence disappears is that Prefab
is a builtin tag that is by default ignored by queries, and as a result also by the renderer. Children of a prefab also become prefabs, so they also get ignored.
That seems a bit underwhelming, but what we can now do is create an entity that inherits from the Fence
prefab, by adding this line to the bottom of the script:
Great, the fence is back! Even better, we can now create two fences simply by instantiating the prefab twice. Just make sure to give both instances different positions:
We could save this script as is, load it into our game, and instantiate the prefab from regular C/C++ code. This is what that would look like:
We still have some problems, which we'll address in the next section:
Prefabs are static collections of entities and components that we can instantiate multiple times. Templates are prefab compositions that can be parameterized. In other words, templates let us to create our Fence, while also specifying the width and height.
Changing the above code into an template is easy. Just change this line:
into this:
The editor will throw the following error:
What does this mean? Templates, unlike prefabs, need properties, which are like the inputs to the template. For our fence, these could be width
and height
values.
To add properties to the template, we can take some of our existing const
variables, and change const
to prop
. This will make the variables visible to the outside. Another thing we must do for properties is explicitly specify their type.
Let's take the existing width
, height
and color
variables, and change them to properties. It is also good practice to put the props at the top of the template code, so it's easy to see which inputs it has:
We can get rid of the flecs.meta.
prefix by adding this to the top of our script:
The code can now be changed to this:
Note that while we were doing this, the fence disappeared again from the canvas. This happened because while we inherit a prefab, we assign an template. To make the fences visible again, change the code that creates the fences to this:
The fences are back, with the default values we provided to our props. But we now have a new power! Try changing it to this:
We are now well on our way to define a procedural fence. Note how similar assigning an template looks to assigning a component. This is no coincidence: an template is translated to a component, where each property becomes a member of that component.
This is something we can exploit in C/C++ code to make assigning templates to entities as easy as assigning a component:
For this to work, we have to make sure that the types and ordering of the properties in the script match up with the types and ordering of members in the component type.
Let's now see if we can change the number of pillars and bars depending on the fence width and height.
At first glance this sounds like something that would require loops, and those are not supported in Flecs script. There is a way around this however, which is to use a Grid
component that does the instantiating for us.
The Grid
component is provided by the flecs.game
module, so before using it lets add this to the top of the script:
The implementation for the Grid
component can be found here. We only use it in the tutorial for demonstration purposes. In practice we could create many different components that handle layout for us.
The grid component lets us create multiple instances of an entity that are laid out in a 1, 2 or 3 dimensional grid. Let's apply this to the number of pillars, which we want to scale with the width of our fence. For this we need two things:
We already have the fence width. Let's create another variable for the spacing:
We can now calculate the number of pillars we want:
The Grid
component can't consume the pillar code directly. Instead it accepts a prefab, so lets turn the existing pillars code into a prefab:
Note how we left out Position3
, as this will be set by the Grid
component. Without further ado, let's create a row of pillars:
Let's dissect what this code does:
pillars
entityy: $height/2
so pillars don't sink into the ground$pillar_count
pillars on the x axis$pillar_spacing
apartPillar
as prefab to instantiate the entitiesLet's also change the code that instantiates our two fences to just one:
And increase the width
of the fence to 60
:
We now get a number of pillars that matches the fence length:
There's still something off though, which is that the pillars don't exactly line up with the ends up of the fence. We can fix this with a simple calculation that takes the number of pillars, and uses that to recompute the grid spacing.
Note that this takes into account that there is one more pillar than there are spaces between pillars. When we replace pillar_spacing
in the grid with grid_spacing
, we get the correct pillar alignment:
We can do the same thing for the bars. I'll skip right to the result since it involves very similar steps:
The values got tweaked a bit to get a more aesthetically pleasing result when scaling up. Feel free to tune the variables to values that produce something you like!
We're almost done! There is only one thing left, which is to combine the fence template in a nested template so we can create an enclosing.
Now that we have our Fence template ready to go, we can build a higher level templates which reuses existing the Fence
template. One typical thing we could do is instantiate a Fence
four times, so that it creates an enclosed space.
The code for this doesn't introduce anything beyond what we've already learned. Let's first setup a new template at the bottom of our script. Inside the template, lets create width
and depth
properties, so we can define a rectangular area. Let's also add a color
and height
property that we can passthrough to our Fence
template.
When put together, this is what it looks like:
Now we need to instantiate Fence
four times, for each side of the rectangle. We'll also add a few convenience variables so we don't end up writing the same divisions multiple times.
Note how the passthrough parameters are specified as height:$
. This is a shorthand notation for height: $height
, and can save a lot of typing when working with nested templates.
Let's now use our new Enclosing
template by changing
to
Here is what that looks like. You might need to zoom out a bit with the camera to see the full fence. Remember that you can do this by first clicking on the canvas to give it focus, then using the WASD keys to move the camera around. Don't forget to click on the canvas again to give focus back to the explorer.
We can now easily modify our enclosing by passing in parameters for width and height:
Congratulations! You now know how to create assets in Flecs Script! If you'd like to see more examples of how templates can be used to create complex compositions of assets, take a look at the templates in the Flecs playground project:
https://github.com/flecs-hub/playground/tree/main/etc/assets
Additionally you can also try instantiating one of the assets that come preloaded in the playground. Try replacing the editor content with:
That's all for this tutorial. Have fun creating, and don't hesitate to share the results in the #showcase
channel on Discord! (link: https://discord.gg/caR2WmY)