Skip to Content

C++

The two main ways of writing game logic is either in C++ or through scripting. Scripting is an easy way of iterating quickly while modules written in C++ is more suitable for computationally heavy tasks.

Hiber3D modules are written following the ECS pattern and the Hiber3D implementation is based off the entt library.

main.cpp

After creating a new project, there should be a file called main.cpp that looks something like this:

class MainModule : public Hiber3D::Module { public: void onRegister(Hiber3D::InitContext& context) override { // Registers all Hiber3D modules context.registerModule<Hiber3D::Hiber3DModule>(); // Extends a Hiber3D module (with a Hiber3D type) context.getModule<Hiber3D::JavaScriptScriptingModule>().registerComponent<Hiber3D::ExternalImpulse>(context); // Registers a custom module context.registerModule<MyModule>(); } };

Hiber3D modules

Hiber3D supplies a wide range of optional modules, available for you to pick-and-choose from. They can be found under the Hiber3D/ directory, such as <Hiber3D/Physics/PhysicsModule.hpp>. By default, the Hiber3DModule bundle registers the most commonly used Hiber3D modules.

If you want full control of what Hiber3D modules to include in your project, you can replace this universal Hiber3DModule with explicitly listing what modules you want to register. This allows you to register the modules with customized settings like so:

context.registerModule<Hiber3D::LogModule>(Hiber3D::LogSettings{.logLevel = Hiber3D::LogLevel::INFO}); context.registerModule<Hiber3D::SceneManagerModule>(Hiber3D::SceneManagerSettings{.defaultScene = "scenes/RoboRun.scene"});

Custom modules

To register a custom module, you need to:

  1. Add your C++ files to the CMakeList.txt
  2. Register the new module in an already registered module (like main.cpp)

C++ types

Conceptually, there are three main C++ types: components, events, and singletons. These are at their core just normal C++ structs: they are not mutually exlusive and can be used interchangeably.

Components

A component represent a modular characteristic that can be added to entities in your scene. Components can be emplaced, mutated, or removed either through the editor using the entity inspector or at runtime in code.

E.g. Health, Jumping, Player, Gun, Npc, Particle, etc.

Events

The event bus is the way to access the interop layer: the main way of communicating between web and game code, see Web Integration - Interop events.

E.g. GunFired, HealthChanged, PlayerDied, GameOver, etc.

Singletons

Singletons are globally accessable data that does not exist tied to any specific entity.

E.g. Player, Inventory, GameState, ScreenState, Weather, etc.

Note: Singletons always need to be registered:

context.registerSingleton<MySingleton>();

Systems

Writing systems is the main way of creating your game loop.

The same system can be added multiple times, and with different schedules.

System schedules

Systems can be configured to run at different system schedules.

Some of the most common ones are:

  • ON_START - runs once, when entering play mode
  • ON_TICK - for “normal” game logic
  • ON_FRAME - for rendering specific systems

Each schedule also has a _EDIT version, e.g. ON_TICK_EDIT, for running in edit mode.

For a comprehensive list, see: <Hiber3D/Core/SystemTypes.hpp>.

Custom modules examples

Here are some C++ examples showing various ways of modelling gameplay:

.hpp

#pragma once #include <Hiber3D/Core/InitContext.hpp> #include <Hiber3D/Hiber3D.hpp> class MyModule : public Hiber3D::Module { public: void onRegister(Hiber3D::InitContext& context) override; };

.cpp - component view

// Components struct Rotate{ Hiber3D::float3 axis{0.0f, 1.0f, 0.0f}; float speed = PI * 0.5f; // radians per second }; // Systems void rotate( Hiber3D::View<const Rotate, Hiber3D::Transform> entityView){ for(auto& [entity, rotate, transform] : entityView.each()){ transform.rotation.rotateAroundAxis(axis, speed); } } void MyModule::onRegister(Hiber3D::InitContext& context) { context.addSystem(Hiber3D::Schedule::ON_TICK, rotate); }

.cpp - events

// Events struct HealthChanged{ Hiber3D::Entity entity; float healthChange; }; struct EntityDied{ Hiber3D::Entity entity; }; // Components struct Health{ float health = 100.0f; bool isAlive(){ return health > 0.0f; } }; // Systems void handleHealthChanges( Hiber3D::EventView<HealthChanged> healthChangedEvents, Hiber3D::View<Health> entityView, Hiber3D::EventWriter<EntityDied>& entityDiedWriter){ for(const auto& event : healthChangedEvents){ entityView.withComponent(event.entity, [&](Health& health) { const auto wasAlive = health.isAlive(); health.health += healthChange; const auto isAlive = health.isAlive(); if(wasAlive && !isAlive)){ entityDiedWriter.writeEvent({.entity = entity}); } }); } } void MyModule::onRegister(Hiber3D::InitContext& context) { context.addSystem(Hiber3D::Schedule::ON_TICK, handleHealthChanges); }

.cpp - Singleton, ViewWithExclude, ViewWithFilter

// Singletons struct MyKeys{ int jumpKey = 1; }; // Components struct StartJumpingOnInput{ }; struct Jumping{ Hiber3D::Time startTime; }; // Systems void handleStartJumpingOnInput( Hiber3D::Registry& registry, Hiber3D::Singleton<KeyboardState> myKeys, Hiber3D::Singleton<KeyboardState> keyboardState, Hiber3D::ViewWithExclude<Hiber3D::Get<const StartJumpingOnInput>, Hiber3D::Exclude<Jumping>> entityView){ for(const auto& entity : entityView){ if (keyboardState.justPressed(myKeys.jumpKey)) { registry.emplace<Jumping>(entity, Jumping{.startTime = Hiber3D::Time::now()}); } } } void handleJumpingAdded( Hiber3D::ViewWithFilter<Hiber3D::Get<Entity>, Hiber3D::Filter<Hiber3D::Added<Jumping>>> entityView){ // do something when starting to jump, like trigger animation or audio } void MyModule::onRegister(Hiber3D::InitContext& context) { context.registerSingleton<MyKeys>(); context.addSystem(Hiber3D::Schedule::ON_TICK, handleStartJumpingOnInput); context.addSystem(Hiber3D::Schedule::ON_TICK, handleJumpingAdded); }

C++ registration

You can extend any module yourself from your C++ code, allowing you to expose new types and functions.

Type registration (components, events, & singletons)

Here is an example of how you can register custom types to a module:

void MyModule::onRegister(Hiber3D::InitContext& context) { // Registering your types to the EditorModule makes them appear in the editor if (context.isModuleRegistered<Hiber3D::EditorModule>()) { context.getModule<Hiber3D::EditorModule>().registerComponent<MyComponent>(context); } // Registering your types to the JavaScriptScriptingModule makes them available while scripting if (context.isModuleRegistered<Hiber3D::JavaScriptScriptingModule>()) { context.getModule<Hiber3D::JavaScriptScriptingModule>().registerComponent<MyComponent>(context); context.getModule<Hiber3D::JavaScriptScriptingModule>().registerEvent<MyEvent>(context); context.getModule<Hiber3D::JavaScriptScriptingModule>().registerSingleton<MySingleton>(context); } // Registering your types to the SceneModule makes them savable in scenes if (context.isModuleRegistered<Hiber3D::SceneModule>()) { context.getModule<Hiber3D::SceneModule>().registerComponent<MyComponent>(context); } }

For the types to be serialized correctly, you need to ensure you reflect these properly like so:

#include <Hiber3D/Interop/Defines.hpp> struct MyType { Hiber3D::Entity entity; std::string name; bool loop = true; }; HIBER3D_REFLECT(HIBER3D_TYPE(MyType), HIBER3D_MEMBER(entity), HIBER3D_MEMBER(name), HIBER3D_MEMBER(loop));

Note: Nested types need to be reflected as well.

Function registration

You can register C++ functions as well to the JavaScriptScriptingModule, allowing you to re-use functionality across C++ and the scripting world. Additionally, C++ is more performant than scripting, allowing you to offload some of the more performance impacting computations.

context.getModule<Hiber3D::JavaScriptScriptingModule>().registerFunction<[](int myInput) { return myFunction(myInput); }>(context, "myFunction"); context.getModule<Hiber3D::JavaScriptScriptingModule>().registerFunction<[](const Hiber3D::Registry& registry, int someNumber) { return registry.singleton<const Hiber3D::SomeSingleton>().someFunction(someNumber); }>(context, "someFunction");

then later in your scripts:

const resultFromMyFunction = hiber3d.call("myFunction", 1); const resultFromSomeFunction = hiber3d.call("someFunction", 1);
Last updated on