Copperfield Engine 0.1
C++ Game Engine
Loading...
Searching...
No Matches
Tutorials

Creating a Window

The following code demonstrates the minimal setup required to create a window:

#include "engine.hpp"
#include "window.hpp"
int main(int, char**) {
// Initialize the game engine, which sets up internal components.
Engine engine;
// Create a window
Window window = Window::Make(engine, 640, 480, "ventana");
// Enter the main loop to keep the window open.
while (!window.isDone()) {}
return 0;
}
Intiliazes the ComponentManager and the ResourceManager and ofers functions to retrieve them.
Definition: engine.hpp:12
Creates and updates a window.
Definition: window.hpp:20
bool isDone()
Checks if the user wants to close the window and performs internal frame updates.
static Window Make(Engine &engine, int width, int height, const std::string &title)
Creates a window.

Here, we create an Engine object. This class initializes internal components and serves as your gateway to the ResourceManager and the ComponentManager. Then, we need to create the Window where the scene will be displayed. Lastly, we loop to ensure the window is not automatically closed.



Drawing to the Window


In this section, we will explore the process of rendering objects to the window. To achieve this, we'll create a Renderer, set up a camera using the Entity-Component System (ECS), and visualize a simple red triangle.

Creating the Renderer

Creating the Renderer is pretty straightforward. Since we are not using lights, we will set the ambient light to 1 so that we display the real color of the object:

// Create a renderer with ambient light set to 1.0.
Renderer renderer = Renderer(engine, window, 1.0f);
Class used to render the scene.
Definition: renderer.hpp:49

Creating the Camera

To create a camera, we need to make use of the ComponentManager, which can be retrieved from the Engine. With that, we can create an entity with no parent, a TransformCmp, and a CameraCmp.

// Get the ComponentManager from the engine to manage entities and components.
ComponentManager& component_manager = engine.getComponentManager();
// Create a Camera entity with specified transformations.
component_manager.addEntity(
nullptr,
"Camera",
TransformCmp(Vec3(0.0f, 0.0f, 5.0f), Vec3(0.0f), Vec3(1.0f)),
);
Handles anything related to entities and components in the engine.
Definition: component_manager.hpp:29
Entity addEntity(Entity *parent, const std::string &editor_name, const T &... args)
Creates an entity and assigns the specified components to it.
Definition: component_manager.hpp:58
ComponentManager & getComponentManager() const
Retrieves the ResourceManager.
Definition: engine.hpp:43
@ kOrtho
Renders a 3D scene as 2D without depth.
Component used to render the scene. There must be one and only one of this component on the scene.
Definition: default_components.hpp:161
Component used to give a position, rotation, and scale to an entity.
Definition: default_components.hpp:284

Components in depth

Creating the Triangle

We now need to create something to visualize. In this case, we will draw a red triangle. To do so, we will need an entity with a RenderCmp and a TransformCmp. But first, we will need to use the ResourceManager to get the mesh of the triangle.

// Get the ResourceManager from the engine to manage game assets.
ResourceManager& resource_manager = engine.getResourceManager();
// Create an entity with a RenderCmp, specifying a triangle mesh, no texture, and red color.
component_manager.addEntity(
nullptr,
"Triangle",
TransformCmp(Vec3::zero, Vec3::zero, Vec3::unit),
&resource_manager.getMesh(BasicMesh::kTriangle),
&resource_manager.getEmptyTexture(),
Vec3(1.0f, 0.0f, 0.0f)
)
);
ResourceManager & getResourceManager() const
Retrieves the ResourceManager.
Definition: engine.hpp:37
Stores and offers access to all meshes, textures, and sounds of the engine.
Definition: resource_manager.hpp:27
const Texture & getEmptyTexture() const
Retrieve a white texture.
const Mesh & getMesh(const std::string &id) const
Retrieve a mesh.
Component used to render an object.
Definition: default_components.hpp:237

One of the good things about ECS is that we now just need to call the system, and all the entities will be updated. So, we just need to call Renderer::render in the main loop, and our triangle should appear on the screen. Here is the complete code of the demo:

image

#include "window.hpp"
#include "ecs/component_manager.hpp"
#include "engine.hpp"
#include "renderer.hpp"
#include "globals.hpp"
using namespace coma;
int main(int, char**) {
Engine engine;
Window window = Window::Make(engine, 640, 480, "ventana");
Renderer renderer = Renderer(engine, window, 1.0f);
ComponentManager& component_manager = engine.getComponentManager();
ResourceManager& resource_manager = engine.getResourceManager();
component_manager.addEntity(
nullptr,
"Camera",
TransformCmp(Vec3(0.0f, 0.0f, 5.0f), Vec3(0.0f), Vec3(1.0f)),
);
component_manager.addEntity(
nullptr,
"Triangle",
TransformCmp(Vec3::zero, Vec3::zero, Vec3::unit),
&resource_manager.getMesh(BasicMesh::kTriangle),
&resource_manager.getEmptyTexture(),
Vec3(1.0f, 0.0f, 0.0f)
)
);
while (!window.isDone()) {
renderer.render();
}
return 0;
}
void render()
Used to render the entire scene with all the entities.
represents mathematical vector with 3 components
Definition: vector_3.h:12
contains globally accessible enums
copperdielf Math Library
Definition: buffer.hpp:5
Handles external assets.



Loading Resources


Now, instead of using a red triangle, we are going to be loading our own meshes and textures.

For that, we will first need to get a mesh and a texture. You can use the ones that come with the demo project, but feel free to use the ones you like. Meshes must be in .obj format, and textures in .png or .jpg.

// Starts loading a mesh asynchronously
resource_manager.loadResource(
"../assets/obj/pollo.obj",
"Shield"
);
// Starts loading a texture asynchronously
resource_manager.loadResource(
"../assets/textures/box.png",
"Crate"
);
// Waits for all meshes to load
resource_manager.WaitResourceLoad();
void loadResource(const ResourceType &resource_type, const std::string &file_path, const std::string &identifier)
Load an external asset from a file using multithreading.
bool WaitResourceLoad()
Stops the program, waiting for resource loading to complete.
@ kTexture
Texture accepts .png and .jpg.
@ kMesh
Mesh accepts .obj.

When loading one or multiple assets, **WaitResourceLoad must be called once before using any of the loaded assets.**

To use the resource, we will replace the triangle we made in the previous code with a new entity:

component_manager.addEntity(
nullptr,
"Triangle",
TransformCmp(Vec3::zero, Vec3::zero, Vec3::unit),
&resource_manager.getMesh("Shield"),
&resource_manager.getTexture("Crate")
)
);
const Texture & getTexture(const std::string &id) const
Retrieve a texture.

Lastly, we will change our camera from ortho to perspective so that we can properly visualize the object.

Just with that, we should be able to see our mesh with the texture.

image

#include "window.hpp"
#include "ecs/component_manager.hpp"
#include "engine.hpp"
#include "renderer.hpp"
#include "globals.hpp"
using namespace coma;
int main(int, char**) {
Engine engine;
Window window = Window::Make(engine, 640, 480, "ventana");
Renderer renderer = Renderer(engine, window, 1.0f);
ComponentManager& component_manager = engine.getComponentManager();
ResourceManager& resource_manager = engine.getResourceManager();
resource_manager.loadResource(
"../assets/obj/sword_shield.obj",
"Shield"
);
resource_manager.loadResource(
"../assets/texture/box.png",
"Crate"
);
resource_manager.WaitResourceLoad();
component_manager.addEntity(
nullptr,
"Camera",
TransformCmp(Vec3(0.0f, 0.0f, 5.0f), Vec3(0.0f), Vec3(1.0f)),
);
component_manager.addEntity(
nullptr,
"Triangle",
TransformCmp(Vec3::zero, Vec3::zero, Vec3::unit),
&resource_manager.getMesh("Shield"),
&resource_manager.getTexture("Crate")
)
);
while (!window.isDone()) {
renderer.render();
}
return 0;
}
@ kPerspective
Renders regular 3D using depth.



Adding Lights


Adding lights is pretty straightforward once you understand entities and components. The only thing we need is to create an entity with a LightCmp.

In our specific example, we will also need to modify the way we create the renderer because we have the ambient light set to one, so adding light will only add reflections.

Renderer renderer = Renderer(engine, window, 0.2f);
component_manager.addEntity(
nullptr,
"Point Light",
TransformCmp(Vec3(2.0f, 3.0f, 3.0f)),
LightCmp(Vec3::unit, 1.0f, 50.0f)
);
Component used to simulate different types of lights.
Definition: default_components.hpp:69

image

#include "window.hpp"
#include "ecs/component_manager.hpp"
#include "engine.hpp"
#include "renderer.hpp"
#include "globals.hpp"
using namespace coma;
int main(int, char**) {
Engine engine;
Window window = Window::Make(engine, 640, 480, "ventana");
Renderer renderer = Renderer(engine, window, 0.2f);
ComponentManager& component_manager = engine.getComponentManager();
ResourceManager& resource_manager = engine.getResourceManager();
resource_manager.loadResource(
"../assets/obj/sword_shield.obj",
"Shield"
);
resource_manager.loadResource(
"../assets/texture/box.png",
"Crate"
);
resource_manager.WaitResourceLoad();
component_manager.addEntity(
nullptr,
"Camera",
TransformCmp(Vec3(0.0f, 0.0f, 5.0f), Vec3(0.0f), Vec3(1.0f)),
);
component_manager.addEntity(
nullptr,
"Triangle",
TransformCmp(Vec3::zero, Vec3::zero, Vec3::unit),
&resource_manager.getMesh("Shield"),
&resource_manager.getTexture("Crate")
)
);
component_manager.addEntity(
nullptr,
"Point Light",
TransformCmp(Vec3(2.0f, 3.0f, 3.0f)),
LightCmp(Vec3::unit, 1.0f, 50.0f)
);
while (!window.isDone()) {
renderer.render();
}
return 0;
}



Input


To add input, the first thing is to define an InputButtonMap and create an InputMap with it. All the InputManager must be declared after the Renderer input wont work otherwise.

InputButtonMap g_input_map{
{"Up",{InputButton::W,InputButton::UP}},
{"Down",{InputButton::S,InputButton::DOWN}},
{"Left",{InputButton::A,InputButton::LEFT}},
{"Right",{InputButton::D,InputButton::RIGHT}},
};
InputManager input = InputManager(window, g_input_map);
Interface to retrieve keyboard and mouse input.
Definition: input.hpp:180
std::unordered_map< std::string, std::vector< InputButton > > InputButtonMap
Structure that binds user-created actions and physical input.
Definition: input.hpp:175

Once we have that in the main loop, we can detect the input using the input manager we created:

if (input.buttonPressed("Up")) {
}
if (input.buttonPressed("Down")) {
}
if (input.buttonPressed("Left")) {
}
if (input.buttonPressed("Right")) {
}
bool buttonPressed(const std::string &action) const
Checks if any of the buttons assigned to an action is currently held down

But to actually update an entity with that, we will need to have its components. To retrieve them, we will first go to the creation of our shield entity and retrieve its value. and then cache its transform cmp:

Entity player = component_manager.addEntity(
nullptr,
"Shield",
TransformCmp(Vec3::zero, Vec3::zero, Vec3::unit),
&resource_manager.getMesh("Shield"),
&resource_manager.getTexture("Crate")
)
);
TransformCmp* player_transform =
component_manager.getComponent<TransformCmp>(player);
T * getComponent(Entity entity) const
Retrieves a component for a specific entity.
Definition: component_manager.hpp:117
Identifies an entity. This class can only be modified by the engine.
Definition: component_manager.hpp:16

Now, in the main loop we had before, we can update the position of the component. We use Time::DeltaTime to get the time elapsed on the last frame and make the movement framerate independent. You can multiply it by any amount to adjust the movement speed:

if (input.buttonPressed("Up")) {
player_transform->position.y += Time::DeltaTime();
}
if (input.buttonPressed("Down")) {
player_transform->position.y -= Time::DeltaTime();
}
if (input.buttonPressed("Left")) {
player_transform->position.x -= Time::DeltaTime();
}
if (input.buttonPressed("Right")) {
player_transform->position.x += Time::DeltaTime();
}
static float DeltaTime()
Retrieves the duration of the last frame.

Complete code:

#include "window.hpp"
#include "ecs/component_manager.hpp"
#include "engine.hpp"
#include "renderer.hpp"
#include "globals.hpp"
#include "Input.hpp"
#include "time.hpp"
using namespace coma;
InputButtonMap g_input_map{
{"Up",{InputButton::W,InputButton::UP}},
{"Down",{InputButton::S,InputButton::DOWN}},
{"Left",{InputButton::A,InputButton::LEFT}},
{"Right",{InputButton::D,InputButton::RIGHT}},
};
int main(int, char**) {
Engine engine;
Window window = Window::Make(engine, 640, 480, "ventana");
Renderer renderer = Renderer(engine, window, 0.2f);
InputManager input = InputManager(window, g_input_map);
ComponentManager& component_manager = engine.getComponentManager();
ResourceManager& resource_manager = engine.getResourceManager();
resource_manager.loadResource(
"../assets/obj/sword_shield.obj",
"Shield"
);
resource_manager.loadResource(
"../assets/texture/box.png",
"Crate"
);
resource_manager.WaitResourceLoad();
component_manager.addEntity(
nullptr,
"Camera",
TransformCmp(Vec3(0.0f, 0.0f, 5.0f), Vec3(0.0f), Vec3(1.0f)),
);
Entity player = component_manager.addEntity(
nullptr,
"Shield",
TransformCmp(Vec3::zero, Vec3::zero, Vec3::unit),
&resource_manager.getMesh("Shield"),
&resource_manager.getTexture("Crate")
)
);
component_manager.addEntity(
nullptr,
"Point Light",
TransformCmp(Vec3(2.0f, 3.0f, 3.0f)),
LightCmp(Vec3::unit, 1.0f, 50.0f)
);
TransformCmp* player_transform =
component_manager.getComponent<TransformCmp>(player);
while (!window.isDone()) {
if (input.buttonPressed("Up")) {
player_transform->position_.y += Time::DeltaTime();
}
if (input.buttonPressed("Down")) {
player_transform->position_.y -= Time::DeltaTime();
}
if (input.buttonPressed("Left")) {
player_transform->position_.x -= Time::DeltaTime();
}
if (input.buttonPressed("Right")) {
player_transform->position_.x += Time::DeltaTime();
}
renderer.render();
}
return 0;
}
Input management.
coma::Vec3 position_
Position in the world relative to the parent's position.
Definition: default_components.hpp:290



Custom Components and Systems


The result of this tutorial will be very similar to the last one, but it's important to learn how to create custom components and systems to unlock the full potential of the engine. Instead of updating the transform directly, we will create a MovementCmp that will hold a direction and a speed, and then we will use a system to update the transform based on the direction and speed stored in the component.

// Define component
struct MovementCmp {
coma::Vec3 direction = Vec3::zero;
float speed = 0;
};
// Define system
void MovementSystem(ComponentManager& component_manager) {
auto& movement_list = component_manager.getComponentList<MovementCmp>();
auto& transforms_list = component_manager.getSparseList<TransformCmp>();
ComponentIterator iterator(movement_list, transforms_list);
while (iterator.next()) {
auto [move_cmp, transform_cmp] = iterator.get();
transform_cmp.position_ +=
move_cmp.direction * move_cmp.speed * Time::DeltaTime();
}
}
Helper to iterate several component lists simultaneously.
Definition: lists_iterator.hpp:9
ComponentListCompact< T > & getComponentList() const
Retrieves the list of all the components of the same type.
Definition: component_manager.hpp:161
ComponentListSparse< T > & getSparseList() const
Retrieves the list of all the components of the same type.
Definition: component_manager.hpp:178

With the component and system created, we first need to register the component in the component manager. Then, we will update our player entity to contain a MovementCmp, and instead of caching the TransformCmp, we will now cache this component.

// Register the component
component_manager.addComponentClass<MovementCmp>();
// Entity holding the new component
Entity player = component_manager.addEntity(
nullptr,
"Shield",
TransformCmp(Vec3::zero, Vec3::zero, Vec3::unit),
&resource_manager.getMesh("Shield"),
&resource_manager.getTexture("Crate")
),
MovementCmp{Vec3::zero, 1.0f}
);
// Cache MovementCmp
MovementCmp* player_move =
component_manager.getComponent<MovementCmp>(player);
void addComponentClass(ComponentListType t=ComponentListType::kCompact)
Defines a list for a custom component and makes it ready to use with the engine.
Definition: component_manager.hpp:82

Lastly, we will modify the main loop so that it uses the system.

player_move->direction = coma::Vec3::zero;
if (input.buttonPressed("Up")) {
player_move->direction.y++;
}
if (input.buttonPressed("Down")) {
player_move->direction.y--;
}
if (input.buttonPressed("Left")) {
player_move->direction.x--;
}
if (input.buttonPressed("Right")) {
player_move->direction.x++;
}
MovementSystem(component_manager);
static const Vec3 zero
Vec3(0, 0, 0)
Definition: vector_3.h:129

complete code

#include "Input.hpp"
#include "ecs/component_manager.hpp"
#include "engine.hpp"
#include "globals.hpp"
#include "renderer.hpp"
#include "window.hpp"
#include "time.hpp"
#include "ecs/lists_iterator.hpp"
// input mappings
InputButtonMap g_input_map{
{"Up", {InputButton::W, InputButton::UP}},
{"Down", {InputButton::S, InputButton::DOWN}},
{"Left", {InputButton::A, InputButton::LEFT}},
{"Right", {InputButton::D, InputButton::RIGHT}},
};
// define component
struct MovementCmp {
float speed = 0;
};
// custom system
void MovementSystem(ComponentManager& compoent_manager) {
auto& movement_list = compoent_manager.getComponentList<MovementCmp>();
auto& transforms_list = compoent_manager.getSparseList<TransformCmp>();
ComponentIterator iterator(movement_list, transforms_list);
while (iterator.next()) {
auto [move_cmp, transform_cmp] = iterator.get();
transform_cmp.position_ +=
move_cmp.direction * move_cmp.speed * Time::DeltaTime();
}
}
int main(int, char**) {
//required engine initializations
Engine engine;
Window window = Window::Make(engine, 640, 480, "ventana");
Renderer renderer = Renderer(engine, window, 0.2f);
InputManager input = InputManager(window, g_input_map);
ComponentManager& component_manager = engine.getComponentManager();
ResourceManager& resource_manager = engine.getResourceManager();
//register custom component
component_manager.addComponentClass<MovementCmp>();
//load external resources
"../assets/obj/sword_shield.obj", "Shield");
"../assets/texture/box.png", "Crate");
resource_manager.WaitResourceLoad();
//create camera
component_manager.addEntity(
nullptr, "Camera",
TransformCmp(coma::Vec3(0.0f, 0.0f, 5.0f), coma::Vec3(0.0f), coma::Vec3(1.0f)),
//create main object
Entity player = component_manager.addEntity(
RenderCmp(&resource_manager.getMesh("Shield"),
&resource_manager.getTexture("Crate")),
MovementCmp{coma::Vec3::zero, 1.0f});
//create light
component_manager.addEntity(nullptr, "Point Light",
TransformCmp(coma::Vec3(2.0f, 3.0f, 3.0f)),
LightCmp(coma::Vec3::unit, 1.0f, 50.0f));
//cache component
auto* player_move = component_manager.getComponent<MovementCmp>(player);
//main loop
while (!window.isDone()) {
//update input
player_move->direction = coma::Vec3::zero;
if (input.buttonPressed("Up")) {
player_move->direction.y++;
}
if (input.buttonPressed("Down")) {
player_move->direction.y--;
}
if (input.buttonPressed("Left")) {
player_move->direction.x--;
}
if (input.buttonPressed("Right")) {
player_move->direction.x++;
}
//update position
MovementSystem(component_manager);
//render scene
renderer.render();
}
return 0;
}
static const Vec3 unit
Vec3(1, 1, 1)
Definition: vector_3.h:131