I'm trying to improve my game/engine design by the use of some clearly defined structuring of objects. My main project is an open world FPS with a lot of static objects, such as trees, rocks etc. In the meanwhile, I figured I could test some new programming ideas on a simple separate platformer game, trying to keep in mind how the ideas would work in the main project.
The ECS seems to be quite popular nowdays, but after the praising introductory, many issues seem to arise.
I have read
http://gameprogrammingpatterns.com/component.html
which give good, but simplified descriptions. I don't want to over-engineer my approach, but I don't want to end up writing a lot of repeated code either.
At the moment, I'm struggling with mainly two things:
1) Data locality: store components in contiguous arrays, so that entities only store indices to these arrays. Alternatively, use base component class and templates and store all component base object pointers in a single array.
2) Implementation of systems: write directly as methods to the class containing the components so that systems can have access to basicaly everything. Alternatively, implement systems as classes and store system base object pointers in some array.
At this point I add some simplified code that hopefully utilizes data locality:
// Components.
struct CTransform
{
vec3 pos;
float scale;
};
struct CBoundingShape
{
vec3 boxSize;
float radius;
};
struct CMesh
{
// Refers to some array where mesh data is stored.
int meshDataIndex;
};
// End of components.
enum EComponentType{
compTransform,
compBoundingShape,
compMesh,
numComp
};
struct CEntity
{
// Maps component type to index to component array.
// Index -1: entity doesn't have the component.
int componentIndices[numComp];
// For simple entity property tagging.
long int flags;
};
class CEngine
{
// Store components in contiguous arrays.
vector<CTransform> transforms;
vector<CBoundingShape> boundingShapes;
vector<CMesh> meshes;
vector<CEntity> entities;
// Add indices to entities here. Only requires transform and bounding shape info.
CQuadtree *quadtree;
// Component "getters".
CTransform *getTransform(int entityIndex);
// for each component type.
// Create component and return index to vector.
int createTransform();
// for each component type.
void drawMeshes()
{
vector<int> indicesToVisible = quadtree->frustumCull();
// Render visible entities that have transform and mesh components.
// These components are not necessarily contiguous in memory...
for(size_t i=0; i<indicesToVisible.size(); i++){
const CEntity &ent = entities[indicesToVisible[i]];
if(!getTransform(ent) || !getMesh(ent))
continue;
drawMesh(getTransform(ent), getMesh(ent));
}
}
};
There are several issues here.
a) Adding new type of components is a hassle and prone to error: for each new type, one has to implement createSomeComponent and getSomeComponent.
b) For drawing meshes, any realistic game will have some spatial partitioning. In my usual implementation, the quadtree/some other returns a vector of indices to visible entities. When using these indices, the entities and their components that are actually processed may not be contiguous anymore.
c) Here drawMeshes() can be seen as a system operating on certain components. By keeping up this way, the CEngine class will soon be bloated with system methods. However, drawing meshes needs access to a variety of the engine's internals, such as textureManager, shaderManager, vertexArrayManager etc. (not shown above). Thus separating systems into individual class objects could still lead to these objects being very tightly coupled with the engine class.
I guess one thing that affects whether (a) will be an issue, is the granularity of components. But I think that in order to take advantage of ECS, one should give a component just one type of thing to store.
I'm thinking if I should have more than one type of ECS. The engine could implement something similar to the above, trying to achieve data locality with the expense of more difficult component management. The game could implement its own ECS, which would be based on template based design, allowing the creation of game specific components easily (here performance might not be so critical). The motivation is that I would think that usually a game world may consist of thousands of entities, but only few of them would be equipped with actual game related components, whereas most would have a transform and a mesh component.