The Ultra Engine entity component system allows you to easily add behavior to game objects. This class can be extended to add behavior and properties to an Entity.
Property | Type | Description |
---|---|---|
CallMethod | Method | allows a method to be called by name, for use with the flowgraph |
Collide | Method | called whenever a physics collision occurs |
Copy | Method | makes a copy of the component, for copying entities |
GetEntity | Method | retrieves the entity this component is attached to |
Load | Method | called when an actor is loaded or copied |
Save | Method | called when an actor is saved or copied |
Start | Method | called when a component is added |
Update | Method | called once each time World::Update is called |
You can override these methods or add your own in your component class. To add a new component, just create a new .hpp file in any subfolder in your "Source\Components" folder. You can use separate header and code files if you want, but it is more convenient to put everything in a single file that automatically gets included into your project.
Open the file "Source\ComponentSystem.h" and add your component into the list of includes, as well as in the RegisterComponents function:
#pragma once
#include "UltraEngine.h"
#include "Components/Motion/Mover.hpp"
#include "Components/Motion/SlidingDoor.hpp"
#include "Components/Player/CameraControls.hpp"
#include "Components/Player/FirstPersonControls.hpp"
#include "Components/Player/ThirdPersonControls.hpp"
#include "Components/Player/VRPlayer.hpp"
#include "Components/Triggers/CollisionTrigger.hpp"
//Include user-defined component
#include "Components/Custom/MyComponent.hpp"
void RegisterComponents()
{
RegisterComponent<Mover>();
RegisterComponent<SlidingDoor>();
RegisterComponent<CameraControls>();
RegisterComponent<FirstPersonControls>();
RegisterComponent<ThirdPersonControls>();
RegisterComponent<VRPlayer>();
RegisterComponent<CollisionTrigger>();
//Register user-defined component
RegisterComponent<MyComponent>();
}
To use components in C++, include the component system header file and add the component to an entity with Entity::AddComponent. Call the RegisterComponents function at the start of your program to make it so the map loader is able to add components to entities that are loaded in a map.
#include "UltraEngine.h"
#include "ComponentSystem.h"
using namespace UltraEngine;
int main(int argc, const char* argv[])
{
RegisterComponents();
//Get the displays
auto displays = GetDisplays();
//Create a window
auto window = CreateWindow("Ultra Engine", 0, 0, 1280, 720, displays[0], WINDOW_CENTER | WINDOW_TITLEBAR);
//Create a world
auto world = CreateWorld();
//Create a framebuffer
auto framebuffer = CreateFramebuffer(window);
//Create a camera
auto camera = CreateCamera(world);
camera->SetClearColor(0.125);
camera->SetFov(70);
camera->SetPosition(0, 0, -3);
//Create a light
auto light = CreateBoxLight(world);
light->SetRotation(35, 45, 0);
light->SetRange(-10, 10);
//Create a box
auto box = CreateBox(world);
box->SetColor(0,0,1);
//Entity component system
auto component = box->AddComponent<Mover>();
component->rotationspeed.y = 45;
//Main loop
while (window->Closed() == false and window->KeyDown(KEY_ESCAPE) == false)
{
world->Update();
world->Render(framebuffer);
}
return 0;
}
The Mover component is about as simple as it gets. It just stores some motion parameters and moves or turns the entity each time Update is called:
#pragma once
#include "UltraEngine.h"
class Mover : public Component
{
public:
Vec3 movementspeed;
Vec3 rotationspeed = Vec3(0, 10, 0);
bool globalcoords = false;
//Update method, called once per loop
virtual void Update()
{
if (globalcoords)
{
this->entity->Translate(movementspeed / 60.0f, true);
}
else
{
this->entity->Move(movementspeed / 60.0f);
}
this->entity->Turn(rotationspeed / 60.0f, globalcoords);
}
//This method will work with simple components
virtual shared_ptr<Component> Copy()
{
return std::make_shared<Mover>(*this);
}
};
To call a component method or get a value, first check if a component of the desired type is attached to the entity, and then call the method:
auto component = entity->GetComponent<HealthManager>();
if (component) component->TakeDamage(10);
To display components in the editor, each component must have a JSON file with the same base name as the code file. The format of the JSON data is the same for every supported programming language. Here is the contents of the Mover.json file:
{
"component":
{
"properties":
[
{
"name": "movementspeed",
"label": "Movement",
"value": [0.0,0.0,0.0]
},
{
"name": "rotationspeed",
"label": "Rotation",
"value": [0.0,0.0,0.0]
},
{
"name": "globalcoords",
"label": "Global",
"value": false
}
]
}
}
Each property entry represents an editable value that will be displayed in the component properties when it is attached to an entity. The default value of the property determines what type of interface element will be used to control the value.
We can add input and output functions to the component definition.
{
"component":
{
"outputs":
[
{
"name": "Open"
},
{
"name": "Close"
}
],
"inputs":
[
{
"name": "Open"
},
{
"name": "Close"
},
{
"name": "Enable"
},
{
"name": "Disable"
}
]
}
}
This will control which component methods can be connected in the flowgraph editor.