# Lecture 10 - Game Engines and ECM ### SET09121 - Games Engineering
Leni Le Goff
School of Computing. Edinburgh Napier University --- # Requirements of a Game --- # What does a game need? From a Programmer's POV: - **Content** - 3d Models, Shaders, Textures, Text, Fonts, Music, Video, Saves, Levels/Game State etc.. - **Processing & I/O** - Rendering, User input, Networking, Audio, Loading/Unloading/Streaming - **Logic and Mechanics** - Physics, AI, Gameplay rules. --- # Game Engine Architecture --- # Complexity

--- # Combating the Complexity - Game Codebases Get Big Fast - We've covered some Software Patterns that you can pull out of your toolbox to help. These help solve small isolated design problems. - Use standard approaches to deal with complexity: abstraction, decoupling, encapsulation - E.g. separating your gameplay logic (jump!) from the core engine logic (load a file!). --- # Build The Wall  --- # Abstraction --- # Abstraction - And so we build Games Engines Do we need them? After all we didn't always have them? Q: When do you think you need to separate engine code? - A: From the beginning - A: Later, when you identify things that can be abstracted - A: Never! Who's got the time for that Not all games need an 'engine' - Some are simplistic enough to not need it. - We already have an engine somewhat: SFML. - This is already isolated from our code. But it doesn't do everything we need. --- # Build The Wall  --- # Software Abstraction techniques --- # Object Orientation. For now, we used the Entity model based on inheritance. It does a good job ... ... But it's not perfect. Enter: The Evil Tree Problem --- # Object Orientation & the Evil Tree  --- # Possible Evil Tree Solutions To fix this we need to either: - Use multiple Inheritance (Danger Zone, bugs) - Use interfaces => C++ abstract class - Throw our design in the bin, unceremoniously, and... - **USE COMPOSITION, NOT INHERITANCE** --- # The Evil Tree Solution - The Entity Component Model   --- # ECM ECM enables Data Oriented design.  --- # ECM PseudoCode ```cpp class Entity { protected: List_of_components; public: update(delta_time); render(); add_component(Component); get_components(); remove_component(Component); }; class Component { private: Entity* _parent; public: update(delta_time); render(); }; ``` --- # ECM Code ```cpp class Entity { protected: std::vector<std::shared_ptr
> _components; public: virtual void update(const float &dt); virtual void render(); template <typename T, typename... Targs> std::shared_ptr
add_component(Targs... params); template < typename T > const std::vector<std::shared_ptr
>& get_components() const; void remove_component(std::shared_ptr
); }; class Component { private: Entity* const _parent;//link to the parent entity public: virtual void update(const float &dt) = 0; virtual void render() = 0; }; ``` --- # Component Class ```cpp class Component { protected: Entity* const _parent;//link to the parent entity public: virtual void update(const float &dt) = 0; //to update the component states virtual void render() = 0; //to display the component Component(Entity *const p); Component() = delete; }; ``` In the Component class we have: - *_parent* a link to the parent entity. - The default constructor is marked as `= delete` which means it is not usable. - A Component *needs* a link to a parent to be instantiated. - An *update* and *render* function that will be called by the entity update and render methods. --- # ECM Code ```cpp std::shared_ptr
player = std::make_shared
(); std::shared_ptr
s = player->add_component
(); s->set_shape<sf::CircleShape>(12.f); s->get_shape().setFillColor(Color::Yellow); player->add_component
(); // later on... player->get_components
()[0]->set_speed(150.f); ``` --- ## C++ TEMPLATES have arrived! `template < typename HELP >!` --- # Add component without templates Without template the code would look like this: ```cpp //To add a ShapeComponent std::shared_ptr
sp = std::make_shared
(this, params...)); player->_components.push_back(sp); //we would need to make _components public ``` - If we want to encapsulate this we will need to implement a specific `add_component` function for each type of components: `add_shape_component`, `add_player_movement_component`, `add_texture_component`, `add_chase_component` etc... - Or we can just copy paste this code every time. - In this situation, we cannot abstract this routine. --- # Get a particular component without templates ```cpp for(size_t i=0;i < player->_components.size(); ++i) { std::shared_ptr
& comp = player->_components[i]; if( std::dynamic_pointer_cast
(comp) != nullptr) { std::dynamic_pointer_cast
(comp)->set_speed(150.0f); break; } } ``` - This is impossible to encapsulate. - Then, you will have to use similar code everytime you want to access a component. --- # Generalising with Template Add a component ```cpp template <typename T, typename... Targs> std::shared_ptr
add_component(Targs... params) { std::shared_ptr
sp(std::make_shared
(this, params...)); _components.push_back(sp); return sp; } ``` Get the components of a certain type ```cpp template < typename T > const std::vector<std::shared_ptr
> get_components() const { std::vector<std::shared_ptr
> ret; for (const std::shared_ptr
& c : _components) { if (typeid(*c) == typeid(T)) { ret.push_back(std::dynamic_pointer_cast
(c)); } } return std::move(ret); } ``` --- # Generalising with Template (cont.) The compilator will do the substitution for us at compile time. For instance, with a `PickupComponent`. Add a component ```cpp std::shared_ptr
add_component(/*some params*/) { std::shared_ptr
sp(std::make_shared
(this, params...)); _components.push_back(sp); return sp; } ``` Get the components of a certain type ```cpp const std::vector<std::shared_ptr
> get_components() const { std::vector<std::shared_ptr
> ret; for (const std::shared_ptr
& c : _components) { if (typeid(*c) == typeid(PickupComponent)) { ret.push_back(std::dynamic_pointer_cast
(c)); } } return std::move(ret); } ``` --- # Things to know about templates - It is a powerful tool for abstraction but can get tricky to use. - There are class template and function template ```cpp template< typename T > class A{}; ``` - Template classes and functions has to be defined in headers. - All code from templates will be duplicated everywhere it is used at compile time. - A heavily templated code will need long compile time (because of generation of code) and can be very slow unless compiled with optimisation options (-O3) --- # How the templates are compiled ```cpp //defined in headers template <typename T, typename... Targs> std::shared_ptr
add_component(Targs... params) { std::shared_ptr
sp(std::make_shared
(this, params...)); _components.push_back(sp); return sp; } ``` We call this function ```cpp //in the source file std::shared_ptr
s = player->add_component
(); s->set_shape<sf::CircleShape>(12.f); s->get_shape().setFillColor(Color::Yellow); ``` The compilator will generate this code before compiling. ```cpp std::shared_ptr
add_component() { std::shared_ptr
sp(std::make_shared
(this, params...)); _components.push_back(sp); return sp; } ``` And use the generated function where we call it. --- # Summary - Games are complicated systems! - This is why we use clever tricks to help simplify the problem - ECM is a great way to reuse code - But we do have to use C++ templates to do these nicely