Sign in to follow this  

Component-Creation within Entity-Component-Systems

Recommended Posts

Hey forum : )

I have an entity-component-system running here that I made but I'm still struggling with a few things.

Entities are being used for the following cases:

1. Entities as play-objects within a grid-based level.

2. Used as GUI-widgets.

Entities are being loaded in two ways:

1. From a file, which is pretty much a Lua-evel-file, representing the grid's objects.

2. Automatically generated (e.g. scanning for play-objects in a file-path).

I have two loader-classes, one for the GUI and one for grid-levels. One component-store and a Lua-table-deserialiser.

I gave my graphics-component coordinates-variables, as every visible component will also be at some spot on the screen. The issue is how to apply such to the component, when they have to be generated. They also contain a string as file-path (and reference to the actual loaded texture within a texture-store) and a scaling-value.

The issue is the following, I heard one shall keep constant values out of the level-file. I saw that some games, using XML, have one index-table with IDs to object-files containing constant characteristics (images, mesh-paths, ...). Now, where would I save coordinates? While file-paths will stay constantly the same, coordinates might change, especially of the player-entity. Putting them into the graphics-component seems not a wise-choice.

Should I go with a position-component?

Another problem is, that level-grids use coordinates-variables differently compared to the overall GUI.

x = 1 and y = 1 within the grid mean, column 1 and row 1, while as GUI-widget, they mean x-coordinate 1 and y-coordinate 1. Also, some of these values are not known upon creation - e.g. coordinates. E.g. GUI-loader asks the component-store to create one component and obtains the specific component-object. Then, depending on the type, the loader calls the Lua-deserialiser and obtains values such as image-file-path. But what about coordinates - for automatically generated GUIs, they are pretty much based on available files within a folder. Who shall provide them? Let the GUI-Loader count and add the position-component? It feels weird to change a components values after it has been created and obtained deserialised information.

Having a position-component feels a bit cleaner, either it exists or it does not. If it exists, nothing else has to be done, but if it does not, the GUI-loader will provide one based on counting.

And what class shall require the texture-reference for the graphics-component? And what class adds the graphics-component to the scene-graph? At the moment, whenever my level-loader gets a graphics-component from the component-store returned, it simply adds that component to the scene-graph.

When should this graphics-component acquire its reference from the texture-store? Should the scene-graph do this when a new component gets added to its structure? This seems to be the most reasonable.

In the end, I'm not sure if a position-component is common-practice, I bet people prefer to use a physics-component, that also adds a hit-box-field.

I assume, differentiating between constant components, as a base characteristic of one special enemy/unit/tile/... is common? And using a list of references to files that contain them too?

I hope my post is somewhat understandable, thanks a lot for your time, though : )

 

 

Share this post


Link to post
Share on other sites
Posted (edited)
The issue is the following, I heard one shall keep constant values out of the level-file. I saw that some games, using XML, have one index-table with IDs to object-files containing constant characteristics (images, mesh-paths, ...). Now, where would I save coordinates? While file-paths will stay constantly the same, coordinates might change, especially of the player-entity. Putting them into the graphics-component seems not a wise-choice.

Should I go with a position-component?

Positions for objects in the level belong in the level file, inside the entities definition there. A Position-Component seems like a good idea, though I'd even say a combined Transform-component that has position, rotation and scale (if used) is even better.

Also, regarding not storing stuff in the level file. I think its perfectly fine to store constant values inside the level file, as long as they are unique. For example, if you have a waysign with a unique text, there's little use of storing that in a separate file, outside of having to update a little less information to your repository on a change. Ideally, your system should allow both - storing information directly inside the level file, which probably should be the default mode. Then, you'd support a system on top of that for generating those exeternal object descriptors - see Unity's prefabs, or Unreals blueprint classes. The point being, storing entities/objects in separate files should be a conscious choice if you need to share object descriptions or not want to embedd a complex character in the level (like a boss-enemy), but splitting up every single object in its own file seems odd, and can make the level files harder to maintain.

 

In the end, I'm not sure if a position-component is common-practice, I bet people prefer to use a physics-component, that also adds a hit-box-field.

Physics-components should not have a/the position value of the object. Physics-objects should live in their own representation of the world, and the component only ties the physics-object together with the entity, by synching the positions. So by having a Position/Transform-Component, you can ensure that different systems can communicate together, like graphics, physics, etc... without being dependend on one another.

 

 

Another problem is, that level-grids use coordinates-variables differently compared to the overall GUI.

Yeah, I'd say the case is pretty simple, as you can see by the issues you mentioned: UI should not be handled by your entity-component system. UI is a vastly different system then Entity/Component, and while it might seems tempting to put them together, you are better off representing your UI differently. For example, you have a HUD-component which displays an entire UI-setup. You can also have Widget-Components, where you put a reference to a widget from your UI-system, which can then be displayed somewhere in the world (ie. Health bar). However, you should not go about putting your Widgets together as components and representing them as entities.

 

Thats at least my experience working with an ECS for several years. ECS are great for tying together different systems in the game-world, but you should not try to squeeze everything into it, like building your UI or physics-system of components.

Edited by Juliean

Share this post


Link to post
Share on other sites

So, I reworked my code - added a transform-component, but the drawing process is still a hurdle.

Since my component-factory returns a base-type, knowing whether a graphics-component has been created is not that easy. But how would I add my graphics-component to the scene-graph?

I heard some games simply delete the "objects to draw"-list every frame and add all available objects from the level-structure that fit the viewing-frustum. But not sure if I really need that, there is a set of objects that won't change (unless one is using the level-editor-mode of course).

Additionally, my transform-component is also required for the scene-graph. Should I use std::tuple to put both components together into the "objects to draw"-list within the scene-graph? Or bundle them as a new wrapping object?

Share this post


Link to post
Share on other sites

Do not use a base class.  What is the point exactly?  The component should live in a vector or array of the system that handles that type of component.  Entity then can be very simple, a std::bitset the size of the number of components you support and checking is instant.  Component type A is 0, B is 1, etc.  Do we have A & B?  if ( component_list[0] && component_list[1] ) carry on.  My entity has a handle which is a unique ID, component list and a bool is_alive for cleanup after a frame is over.  And in debug mode i have a std::string name since the name is just hashed and hash's are not fun to read :)

My game components and GUI ones are in completely different systems with no cross over at this time.

Share this post


Link to post
Share on other sites
Posted (edited)
Do not use a base class.  What is the point exactly?

A base class allows you to work with abstractions, it allows you to keep the interface and the implementation different, it allows you to use a unified interface for the general case.

Read a bit about SOLID development principles. Dependency inversion (the D in SOLID) is a powerful enabling technique.

Often in component systems there is generally a base that implements the universal functionality and provides some hooks for functions that are meant for implementation. There will also likely be a few subclasses that serve as a base for additional functionality. The trees should be shallow and wide, with many items derived from the base class.  (In the project on my screen right now, there are 97 component classes in the main game system. 

These also follow the CTRP to eliminate the virtual functions:

/*In base class header file:*/

template <typename ComponentTypeClass> 
class ComponentTypeBaseClass
{ ... /* A bunch of interfaces common to all components, registering, cloning, object IDs, serialization interfaces, etc */ }

template <typename ComponentClass, typename ComponentTypeClass>
class ComponentBaseClass
{ ... /* Implementation to the common interfaces for registering, cloning, IDs, startup, shutdown, etc */ }


/*In the component's header file:*/
 
class FooComponentTypeClass : public ComponentTypeBaseClass<FooComponentTypeClass> 

{... /* the public interface for dependency inversion */ }

class FooComponent : public ComponentBaseClass< FooComponent, FooComponentTypeClass>

{ ... /* This is the implementation of the component, implementing the interface specified above */ }

This enables all kinds of functionality.  We can write automated tests based on the common interface (FooComponentTypeClass), we can swap out with debug versions when trying to resolve tricky bugs, we can experiment with new functionality with easy A/B switches, and more.

 

There are many wonderful reasons to derive components from a base class, and to derive them both from additional bases classes.

Edited by frob
Fix formatting of pasted code.

Share this post


Link to post
Share on other sites

A base class allows you to work with abstractions, it allows you to keep the interface and the implementation different, it allows you to use a unified interface for the general case. Read a bit about SOLID development principles. Dependency inversion (the D in SOLID) is a powerful enabling technique.

I'm quite aware of SOLID development principles.  All my components are struct's with data only.  The system (class) that handles the component (with it's associated struct that is only used internally in the system) only handles that component.  I subscript to a component being 100% data with no functions at all.  I went a little further and made an entity a struct also with it's handle (ID) and a bitset to say what components it contains along with is_alive flag for cleanup two frames after it dies.  My system is very robust and easy to debug honestly, I'll let you have your far more complex looking one, I'll stick to my KISS one :)

Share this post


Link to post
Share on other sites

That will depend on the scale of the game.  The one on my screen is a large commercial game, an update for a product with over a million players globally. So yes, it is far more complex because the game does an enormous amount of work.

The one being discussed should obviously be kept much simpler, but even so, using a base class and following dependency inversion and the other SOLID principles is always a good idea.

Share this post


Link to post
Share on other sites
Posted (edited)

It would be easier, if I have one class per component for adding them to the drawing-scene/scene-graph.

But there is probably another way of how people commonly deal with this. My stated assumption was, that most games are iterating over the vector of loaded entities and add them to the scene-graph, when they are within the viewing-frustum. But that is a pretty wasteful procedure for my game, as the level-field is limited. It also sounds really weird, to iterate once through the object-list and then iterate through the scene-graph. Instead of adding them to the scene-graph and solely iterating over the scene-graph and checking whether each object is within the viewing-frustum. Of course, some games have tons of objects, work in chunks and everything, but my game is far from that.

My simple issue is, that when I construct a component - I'm not able to to add the component to the scene-graph without doing the following:

void load_level(std::string some_file_path)
{

// Extract entities from level-file, here.
// Extract their components as std::vector<const std::string>, here.
// Iterate over every entity, and their components.

Component created_component = component_factory(some_component_key);
scene_graph.add_new_drawable(component); // not possible, since I have a base-class
currently_iterated_entity.add_component(created_component);
}

Should I simply iterate over the entire level-file once done loading and add the components to the scene-graph?

Would std::tuple be a good fit for bundling a transform- and graphics-component within the scene-graph?

 

Edit: I could attempt a get<T> on the entity once all objects have been added, it would return the graphics- and transform-component, if it exists and then add them to the scene-graph.

As in:

void load_level(std::string some_file_path)
{
// Do all the steps mentioned as comments in the last code-sample

for (const auto& component_name : components_to_add)
{
// Acquire component_key
Component created_component = component_factory(component_key);
scene_graph.add_new_drawable(component); // not possible, since I have a base-class
currently_iterated_entity.add_component(created_component);
}
// All components have been added.
if (auto graphics_component = currently_iterated_entity.get<Graphics_Component>())
{
  // check for a transform-component and then add them to the scene-graph
}
else
{
  // whatever.
}
}
Edited by Angelic Ice

Share this post


Link to post
Share on other sites
void SceneGraph::CheckInsert(Entity& entity)
{
     if(entity.HasComponent<Transform>() && entity.HasComponent<Mesh>())
     {
           // insert here
     }
}

This is what you would call, after having loaded all components of an entity. Also gives you a nice interface that multiple systems can share, instead of having to hack every system that requires your components in a different way.

On a more broad note, your scene-graph seems to be a misfit entirely. Have a (mandatory) read:

http://lspiroengine.com/?p=566

You seem to be using a scene-graph to render the scene, which is not how it should be used.

Share this post


Link to post
Share on other sites
Posted (edited)

Thanks a lot. This makes the term "scene-graph" a bit confusing to me though, haha. It rather seems to apply changes to children instead of having to do with a scene at all (scene, especially being word that conveys a visual-relationship to me).

While it does not render the scene (the frustum check is be done by another graphics system instance), but simply owns a list of objects that shall be drawn in a specific order.

I assume, what I mean by "scene-graph" is more of a render-queue then : )

Edited by Angelic Ice

Share this post


Link to post
Share on other sites

The system (class) that handles the component (with it's associated struct that is only used internally in the system) only handles that component.  I subscript to a component being 100% data with no functions at all.

How do you deal with the case when a system requires more than one component to do its job?

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this