Some pointers please (Entity component systems).

Started by
11 comments, last by phil_t 10 years, 6 months ago

So, I am not sure if this is too much to ask, but I was hoping that maybe someone would be interested in looking over a bit of code I have been working on recently to hopefully give me some pointers and just a general code review sort of thing. The project isn't very large but I want to build an actual game out of it, so I wanted to see if anyone could point out potential issues that would be easier to fix earlier rather than later.

As an overview of the inner workings:

I created a base object called GameNode, this is the highest level game state controller, similiar to how Xna provides an update and draw function, GameNodes must implement an Update, Draw, and ConsumeEvent function. Each game node has a parent and child game and the main game loop always has a pointer to the current game node (or root game node).

When drawing, the root is drawn first, followed by it's child and so on down the chain (I.E a start menu may be a child gamenode to a gameplay gamenode)

When updating the child is updated first and the root updated last

when consuming events the child has first dibs on the event and it bubbles up the chain.

As far as the component/Entity system:

This is the part that I am most concerned with as I have never implemented this type of system before and the one in the article was simplified for ease of explanation. One major question is that the Set of Entities live in the game node... so leaving a game node destroys all entity information... I suppose this would be the correct way to handle it, but I am not 100% certain. Imagine a game node that constitues Level-1, it would be correct for all level1 entities to be destroyed when transitioning to level2, but at level2 certain entities, most notably the player would need re-initialzied, which means they'd need to be serialized/stored/retreived/deserialzed <*Note* none of that is implemented>, but I suppose that would just be the trade-off and since it only happens when loading a level it wouldn't even be extreamly noticeable right?

The components are nearly identical to that in the article, merely structs containing only data... as far as I can tell this is the standard, correct way to do this?

The systems are where it seems to get tricky... the theory is pretty strait forward, check to see if a system is applicable to an entity, if so apply the system to the entity... but realizing the theory has all sorts of twists. For example, lets say I have an InputComponent, which allows an entity to react to user input... I might want to apply this system to both a player sprite to control the sprite X,Y cords and also a menu to control which selection is made... but while entity that has the players sprite has a position or velocity component that the input system should modify, the entity containing the menu does not...

I think I have it developed to an interesting point (architecturally) and there are no obvious bugs.

I have set up three entities which use, 3 components and two systems.

The first entity has a position and velocity component.

The second entity has only a position component.

The third entity has a position component and an InputComponent.

There are two systems acting on the entity. The MovementSystem acts of the first entity, no system acts on the second entity and the InputSystem acts on the third entity.

The output is sparse and non-graphical. printing out only the Updates per second, draws per second, elapsed time and position of each entity.

The input responds to the up/down/left/right arrows and the escape key

I am sorry for the long post, but I am really hoping for some constructive criticism and maybe some pointers on where to go from here.

The project is written in Visual C++ using Visual studio 2012 express (A Visual studio 2010 solution file is also included)

I have been reading a bunch of the articles on this site, most notably "Implementing Component-Entity-Systems".

I mainly use C# and have some XNA experience, but for this one I wanted to dive back into C++.

I used Allegro (The pre-buillt windows MSVC binaries: http://cdn.allegro.cc/file/library/allegro/5.0.10/allegro-5.0.10-msvc-11.0.7z)

If your on a windows machine, and create a folder C:\AA_Main\Dev\Allegro

then unzip the allegro files to allegro-5.0.10.-msvc-11.0 (default folder name)

and unzip the project to Project1

it should run pretty easily, otherwise you might have to change the project settings to point to allegro correctly...

ie. update the following configuration properties:

Project Properties:
Config properties->Debugging->Environment
Configuration properties->c/c++->General->Additional Include Directories
Configuration Properties->Linker->General->Additional Library Directories
(There is one other setting, Linker->Input->Additional Dependencies, but it's not an absolute path so there should be nothing to worry about)

http://www.paragon.net84.net/CodeReviewRequest/CodeReviewRequest.7z

Advertisement

Here are some pointers:


char* p = "A Pointer";
char** pp = &p;

Sorry, couldn't help myself.

I didn't feel like downloading the code, but from the text it seems you want to cram both something similar to a scenegraph and a crude entity component system into the same project and I dont think that is a good idea. Why not have some component types indicating an entity needs to be drawn in some way and then just draw everything on the update of the corresponding system?

Also in an ECS each system would better work only on all components of the corresponding type, not on whole entities and the components should be mostly independent of other components.

If the input gets differently used by a menu and a 3d object you may also consider having 2 component types indicating that.

Pro tip: If you want more people to look at your code, host it on GitHub or BitBucket. That way they can browse it online instead of downloading it completely. I recommend you version control all your projects regardless.

You should really put #pragma once at the top of your code, that way it doesn't get in the way and you can read the necessary code. This is more of a convention, but still. Also, I believe #ifndef/#define/#endif is more portable, so if you want portability use that instead of #pragma once. Although I think most modern compilers support the use of #pragma once.

In your InputComponent, I personally think boolean(*handleInput)(Entity* entity) should be typedef'd, as if you change it, you'll have to change it twice (this can cause annoying compiler errors) and plus you get more readability if you typedef it. e.g. typedef boolean(*HandleInputFn)(Entity* entity). Also what the f*ck is boolean? Did you typedef bool or is this a C++ .NET primitive? Just use bool.

Use initializer lists, don't assign in the constructor. e.g. in your PositionComponent


struct PositionComponent
{
     int X;
     int Y;
     PositionComponent(int x,int y) : X(x), Y(y) { /* this should be empty */}
};

And god dammit, use smart pointers. There is more than likely going to be some leaks in your program (I'm not going to check if there is, because I can't be bothered looking for any). Raw pointers should only be used for non-owning objects (i.e. objects that you don't allocate with new/delete).

Also, try to use the stack more often, this isn't Java. In Main.cpp you're allocating objects with new for no goddamn reason. A stack allocation could suffice. For example, your WorldState and GameNode objects are allocated with new when they don't need to be. WHY? You don't even delete them (good practice to, even known they will be deleted on exit of the program, but still), which brings me to the previous point I made: USE SMART POINTERS.

anax - An open source C++ entity system

Thank you for taking the time to look at it pinebanana. I'll definitely look into smart pointers and rethink stack vs heap allocation.. these are the sort of issues I was hoping would be brought up as the managed nature of C# really got me out of the habit of thinking about them.

Here are some pointers:


char* p = "A Pointer";
char** pp = &p;

Sorry, couldn't help myself.

pointers.png

There are a lot of things you can do differently in the ECS, but I'll continue down this path you've chosen. (I didn't look at your code, I don't feel like DL'ing, unzipping, etc).

From a high-level perspective, these are my thoughts:

#1, you need a RenderSystem, and it should act on entities with PositionComponents.

#2, you InputComponent should only be in one entity at a time. My suggestion would be, when changing game state (ie, playing to menu), move the Input Component from the Player Entity to the Menu Entity (and have the MenuInputSystem act on entities with MenuComponent and InputComponent, same with playerComponent).

#3, IMO, Components shouldn't have function pointers in them.

In my journal (linked in my sig) I have some articles on some different ECS systems I've worked on. Take a look at it to give you another perspective. Also, the entityx ECS looks well made.

EDIT: Also, if you add Entity Component System to your topic, you'll get more replies.

EDIT2: Oh, and Entities should not inherit a GameNode. Entities are simply a reference to a group of components, the Systems handle the drawing, updating, etc based on the components in the entities.

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)

In general, it seems that I must have been unclear about the GameNodes vs Entities. The entity system controls only the entities, the components and the systems that work on them. The game node is simply a way to control which set of entities/components and systems are currently active.

For example, there would be a Splash screen game node (no entities or components or systems), a main menu node (some kind of user entity, menu option entities?, and a user input system, and render system. Then Each level would be a game node, with a player entity, enemy entities etc... basically a game node is just a container for the active state of the game. The entity system has no concept of the game node as the game node is higher level. And the game nodes have no more concept of the ECS other than it exists, how it functions is not the game nodes concern.

@Beernuts:

If Components don't have function pointers, how do similar entities implement different logic. For example, were I to have enemy entities where one enemy might form a random movement pattern, another might form a zig-zag movement pattern where would the logic to implement those movements be? I was planing on creating an UpdateComponent which has a function pointer to an specific update function, how would I do this without a function pointer?

*EDIT*

I read the artical about ESC, and I looked at the artemis Entity engine, and read the artical by adam miller from which it was inspired... but it all talks about where the data lives, and such... I haven't seen much on where the logic lives... this blog http://t-machine.org/index.php/2007/09/03/entity-systems-are-the-future-of-mmog-development-part-1/ indicates that all logic lives in the systems... but I can't wrap my head around how that works if not all entities react to all situations the same way. Even if we were to assume that two entities made the same decision in every situation with the exception that one runs away at half health while the other does not... where would the if health<.5*maxHealth then <run away> logic live? You could say that there should be another piece of data, fleeHealthPercentage where one has the value set to 0 and the other at 50%, but that seems like it would be overkill b/c if you have 10 different bits of logic each being used by only 1/10th of the entities in the system you are increasing both data size (to store 9 pieces of data that basically say, ignore this) and processing time as the logic must be run on 9 sets of logic it is being told to ignore.

Of course, there article mentions scripts... but what is the difference between a piece of data defining a script that says run away at 50% health and a piece of data pointing to a function that says run away at 50% health?


@Beernuts:
If Components don't have function pointers, how do similar entities implement different logic. For example, were I to have enemy entities where one enemy might form a random movement pattern, another might form a zig-zag movement pattern where would the logic to implement those movements be? I was planing on creating an UpdateComponent which has a function pointer to an specific update function, how would I do this without a function pointer?

Either, have different Enemy Components that define the enemy's behavior (EnemyZigZagComponent, EnemyRandomComponent) and have specific systems to work on those component types, or in your EnemyComponent, you define what type of behavior the enemy will be using, and in the EnemyControllerSystem, it handles the specific types internally.

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)


indicates that all logic lives in the systems... but I can't wrap my head around how that works if not all entities react to all situations the same way. Even if we were to assume that two entities made the same decision in every situation with the exception that one runs away at half health while the other does not... where would the if health<.5*maxHealth then logic live?

How is that any different than having the entity use a function pointer that does a different thing at different health? Instead of assigning a different function in the entity, you would assign a different RunAway behavior definition in the the EnemyComponent (or BehaviorComponent if "Good" NPC will be in the game) (or as mentioned, use different behavior component's), and in the EnemyControllerSystem, the enemy acts depending on what the RunAway value has been set to in the EnemyComponent.

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)

This topic is closed to new replies.

Advertisement