Examples » Engine » Hello Cubos

Using Cubos to create a simple program.

This example shows the basics of how cubos::engine::Cubos is used, by making a simple "Hello World" program.

#include <cubos/engine/prelude.hpp>

using namespace cubos::engine;

First we'll need to get a Cubos object.

int main()
{
    Cubos cubos{};

The Cubos class represents the engine. We'll need it to add functionality to our program.

Let's start by defining what functionality we want to add, by adding our first system.

    cubos.startupSystem("say Hello Cubos").call([]() { CUBOS_INFO("Hello Cubos"); });

This startup system simply prints Hello Cubos to the console, using one of Cubos's logging macros. You can find more about them here. Startup systems run only once when the engine is loaded.

Now let's make things more interesting. Let's print Hello World, but split it over two different systems.

    cubos.system("say Hello").tagged(helloTag).call([]() { CUBOS_INFO("Hello"); });
    cubos.system("say World").tagged(worldTag).call([]() { CUBOS_INFO("World"); });

Instead of using startupSystem, we'll use Cubos::system. This means the systems will be called after the startup systems and repeat every cycle, instead of just once at startup.

Notice that we can't just do as we did for Hello Cubos and call it a day. We want Hello to come before World, so we'll have to explicitly tell that to the engine, or else we risk having them in the wrong order. To do that we use tags.

    cubos.tag(helloTag);
    cubos.tag(worldTag).after(helloTag);

Cubos::tag can be used to apply properties to all systems with a given tag, and after makes any systems tagged with it come after systems tagged with the one given as parameter. There's also an before that has the inverse effect.

Now let's see a bit about entities, components and resources. First we are going to need to use a few more things. We'll go over what each does as it comes up.

Lets define a new component type, which stores a single integer, which we can use to identify the entity.

#include <cubos/core/ecs/reflection.hpp>
#include <cubos/core/reflection/external/primitives.hpp>

struct Num
{
    CUBOS_REFLECT;

    int value;
};

CUBOS_REFLECT_IMPL(Num)
{
    return cubos::core::ecs::TypeBuilder<Num>("Num").withField("value", &Num::value).build();
}

Notice that not only we define the type, but we also define reflection for it. This is a way to let Cubos know what our data type is made of, making it compatible with serialization, UI debug tools and others automatically.

We also need to register the component type with the Cubos object.

    cubos.component<Num>();

Now, lets create our own resource.

struct Pop
{
    CUBOS_REFLECT;

    int count = 0;
};

CUBOS_REFLECT_IMPL(Pop)
{
    return cubos::core::ecs::TypeBuilder<Pop>("Pop").withField("count", &Pop::count).build();
}

This resource will store the total number of spawned entities, a population counter of sorts. It too needs to be registered.

    cubos.resource<Pop>();

Now let's create a startup system that spawns some entities.

    cubos.startupSystem("spawn entities").call([](Commands cmds, Pop& pop) {
        for (int i = 0; i < 10; i++)
        {
            cmds.create().add(Num{i});
            pop.count += 1;
        }
    });

Commands is a system argument that allows us to interact with the world, in this case, by creating entities that have a Num component.

To access the resource Pop, we just add a reference to it as a system argument (Pop&).

Finally, we'll want a system that prints our entities.

    cubos.system("check entities").call([](Query<const Num&> query, const Pop& pop) {
        for (auto [num] : query)
        {
            CUBOS_INFO("Entity {} of {}", num.value, pop.count);
        }
    });

In this case, we don't change Pop, and only read its value. Thus, we use const Pop&. This allows Cubos to make some optimizations behind the scenes.

Query allows us to access all entities with a given configuration of components. In this case, it will give us all entities with the Num component.

With everything properly set up, all that remains is to run the engine.

    cubos.run();
}

Try running the sample yourself, and you should see both the hello world messages and the list of entities we spawned!