Scene
Using the Scene plugin.
This example shows how the Scene plugin can be used to create scene assets and spawn them on the world.
The plugin function is included from the engine/
cubos.plugin(settingsPlugin); cubos.plugin(assetsPlugin); cubos.plugin(transformPlugin); cubos.plugin(scenePlugin);
Let's start by taking a look at a scene file.
{ "Num": 3, "#child": { "Num": 4, "OwnedBy#": {} } }
Scene files are JSON files with the extension .cubos
. A scene file describes an entity, and, optionally, its components, relations, children, grandchildren, and so on.
Components can be added to an entity by simply adding a field in their JSON object. For example, in this scene, the root entity has a single component, Num
, with a value of 3. Additionally, it has a child entity, with name #child
- sub-entity names must always start with #
.
This child entity has its own component Num
, this time with a value of 4
, and a relation OwnedBy
with the root entity of the scene as a target. Relations are identified by their type name, followed by #
and the name of the entity they target. If the name is empty, then, they target the root.
Let's look at a different scene file now, this time with inheritance. Inheritance allows us to instantiate scenes within other scenes.
{ "Num": 0, "#foo": { "Num": 1 }, "#bar": { "Num": 2, "OwnedBy#foo": {} }, "#sub1": { "inherit": "cd007ba2-ee0d-44fd-bf36-85c829dbe66f", "DistanceTo#sub2": 5, "OwnedBy#foo": {} }, "#sub2": { "inherit": "cd007ba2-ee0d-44fd-bf36-85c829dbe66f", "OwnedBy#": {} } }
In this scene, as before, we have a root entity, this time with Num
set to 0
. This entity has four child entities, #foo
, #bar
, #sub1
and #sub2
, similarly to the previous scene. The big difference here is that we use inheritance in the #sub1
and #sub2
entities, by adding the inherit
field.
When the scene is loaded, all properties defined in the inherited scenes are added to their respective entities. In this case, both entities refer to the scene with id cd007ba2-ee0d-44fd-bf36-85c829dbe66f
, which is the scene we've previously looked that.
The entities in that scene are instantiated in this new scene, with their names prepended by #sub1
and #sub2
respectively. So, the #sub1
and #sub2
entities both represent the roots of the sub-scene we imported, and #sub1#child
and #sub2#child
to the #child
entity we looked at before.
Also take a look at the DistanceTo
relation: it is a symmetric relation, so it doesn't make a different whether we put it in #sub1
or #sub2
.
Now that we have our scene file, let's get our application to load it. The first thing we're going to need is a reference to the scene asset. For the purposes of this sample we can simply use an hardcoded reference to the asset.
static const Asset<Scene> SceneAsset = AnyAsset("f0d86ba8-5f34-440f-a180-d9d12c8e8b91");
Then we'll need a system that spawns that scene. To do this we simply get the Scene object from the asset, and then spawn its entities. Commands::
We use the named
method to give the name "scene"
to the root entity of the scene. This will cause all child entities to also have their name prepended by this name.
cubos.startupSystem("spawn the scene") .tagged(spawnTag) .tagged(assetsTag) .call([](Commands commands, const Assets& assets) { commands.spawn(*assets.read(SceneAsset)).named("scene"); });
In this case, we'll run this system at startup, since we want to spawn it a single time. Since it's a startup system, we'll have to tag it with cubos.assets
to make sure it runs only after the scene bridge has been registered. On a real game, you could have, for example, a scene for an enemy which you spawn multiple times, instead of just once at startup.
cubos.startupSystem("print the scene") .after(spawnTag) .call([](Query<Entity, const Name&> nameQuery, Query<const Num&> numQuery, Query<const OwnedBy&, Entity> ownedByQuery, Query<const DistanceTo&, Entity> distanceToQuery, Query<const ChildOf&, Entity> childOfQuery) { using cubos::core::data::DebugSerializer; using cubos::core::memory::Stream; DebugSerializer ser{Stream::stdOut}; for (auto [entity, name] : nameQuery) { Stream::stdOut.print("Entity "); ser.write(entity); Stream::stdOut.printf(":\n- Name = {}\n", name.value); for (auto [num] : numQuery.pin(0, entity)) { Stream::stdOut.printf("- Num = {}\n", num.value); } for (auto [distanceTo, what] : distanceToQuery.pin(0, entity)) { Stream::stdOut.print("- DistanceTo("); ser.write(what); Stream::stdOut.printf(") = {}\n", distanceTo.value); } for (auto [ownedBy, owner] : ownedByQuery.pin(0, entity)) { Stream::stdOut.print("- OwnedBy("); ser.write(owner); Stream::stdOut.print(")\n"); } for (auto [childOf, parent] : childOfQuery.pin(0, entity)) { Stream::stdOut.print("- ChildOf("); ser.write(parent); Stream::stdOut.print(")\n"); } } });
This sample also contains a system which prints the components and relations of the spawned entities. If you run it, it should give you the same information we defined in the files.