Skip to main content
GameDev.net gamedev.net
🔒 Locked

Component Entity Design

Started by Airtower Nov 27, 2018 at 8:34 AM 11 replies 4.6k views
Original Post
Airtower
Airtower

Hello,

Im new here but am finally trying to switch from Inheritance Based Game design to Component/Entity System Design.

Im starting off with a simple Checkers game and can't seem to figure out how to design this game.

I know I have the Board and Checker Pieces for the Entities. I know I'll need a Visual Component, Move Component and an AI Component. Is this right?

Im not sure how to layout this type of game. I don't know if the Board should be an entity or not. Im not sure where the drawing of the board should be taking place and where the majority of the game logic would go.

In case it matters, Im programming in Swift.


Thank you in Advance for all your help.


Tom Sloper
Tom Sloper
1 hour ago, Airtower said:

...Im programming...

Moving this to an appropriate forum.

-- Tom Sloper    --      sloperama.com
Hodgman
Hodgman
1 hour ago, Airtower said:

Im new here but am finally trying to switch from Inheritance Based Game design to Component/Entity System Design.

If you've got an existing inheritance based design, start by replacing all of your is-a relationships with has-a relationships.
e.g. instead of Monster is a Sprite, use Monster has a Sprite.

IMHO, it's worth studying some basic OOP design theory as it's supposed to answer these questions. Building software out of components isn't new - the composite reuse principle was coined in the early 90's to warn people off of an over-use of inheritance ?

Airtower
Airtower

I see.

Yea, I know it isn't new but it is something Ive just recently decided to change my thinking when approaching design.

Thank you for your help and hopefully this will help enough to get me going.


Airtower
Airtower

Ok,

Ive been reading more articles on Composite Reuse Principles and still can't come to a solution. I don't have a checkers game to change from OOP to Composite Reuse design. I am only doing this to learn how to use this (new to me) design pattern.

Mainly, I am having problems with the Board Entity. Here is my setup...

I have 2 entities, a GameBoard and a Checker.

I have 3 Components, Sprite(visual), MoveCompont and the Artificial Intelligence Component.

All 3 components connect to the Checker piece but I can't determine how to fit the GameBoard into this design. I know it should draw the board on the screen, keep track of each square but I can't figure out if I should have a manager to keep track of all of the pieces and check the logic to allow a move or check for wins and so on.


Thank you

Zakwayda
Zakwayda

There's probably no one right answer here, but my initial thought is that a 'piece' entity is the wrong place for a 'movement' or 'AI' component (with the possible exception of a 'movement' component that handles cosmetic animation effects).

I think for something like checkers, things like AI and movement - in fact perhaps all game logic - might better be made the responsibility of a single module that has a "bird's-eye" view of the game. Individual pieces don't have this bird's-eye view (conceptually speaking), and so are probably not the best place for this functionality.

Here's an idea of how you might arrange this in an entity-component system (I'll stick with a strict entity-component description here, since that's what you're using, although other terminology or levels of granularity can be used as well):

- A 'game logic' component (and associated container entity) that does the bulk of the work (AI, movement, etc.). This would include a grid representation of the board (separate from any world-space visual representation) that's used to track game state.

- A 'board' entity. Just a visual element with no behavior.

- The 'piece' entities. These would also just be visual elements, more or less. The 'game logic' component would have access to the transforms of these entities in order to move them. (Animated movement could also be handled by a per-piece component, as mentioned earlier.)

Rendering can be handled in different ways, but a simple approach is to have 'renderable' components (these would likely be the 'Sprite' components in your system), and (one way or another) to iterate over these and render them individually each frame. Here, the 'board' and 'piece' entities would each have a renderable component. (Note that this is just one, fairly simple approach, and that pursuit of things like elegance and efficiency could take you in other directions. But for many games I think this simple approach should be sufficient.)

I think you hit on some of these ideas in your post, but maybe the above suggestions will help clarify things a bit.

Note that these are just my thoughts on the matter (others might recommend something completely different).

Strewya
Strewya
4 hours ago, Zakwayda said:

- A 'game logic' component (and associated container entity) that does the bulk of the work (AI, movement, etc.). This would include a grid representation of the board (separate from any world-space visual representation) that's used to track game state.

I don't think you need an entity for the game logic. Also, game logic doesn't have to be a component. You can put the game logic into main(). Process the input, update the placement of units according to game rules, and then render them at their positions.


Also, a bit of a rant and personal opinion here, i think ECS is overkill for something like a checkers game. The ECS style architecture would be better suited when you have so many entities it becomes unwieldy to handle each of their behaviours individually (because you either duplicate a lot of code, or make large inheritance hierarchies where you can't reason about the code quickly anymore), so you split them into a smaller subset of pieces which you can easily [reason about it's] process/update, and compose the higher concept entities from those pieces, relying on standardized piece-wise functionality. And you can even continue the process further, and split any of those pieces (L1 pieces?) into another subset of pieces (L2 pieces) with their own update procedures, in order to compose the higher level piece, out of which you compose the entities. This is what you'd be doing anyway when you're coding for the first time, or not following ECS rules: embedding structs into structs into structs until you have a good data organization scheme that you can easily follow.

Anyway, to follow the thread question, what i would do is something like the following (pseudo code):


EMPTY = 0
RED = 1
BLACK = 2
board {
  Tiles[8][8] = {EMPTY}
  sprite BoardSprite
  sprite RedPiece
  sprite BlackPiece
}

int main() {
	// .. initialization of the window, device, state, whatever
    board Board
    GameRunning = true
    while(GameRunning) {
    	Input = grabInput(InputDevice)
        GameRunning = gameLogic(Input, Board)
        render(GraphicDevice, Board)
    }
}

"grabInput" returns a struct with the inputs you want to handle (either raw device input you're interested in, or remapped logical commands, whatever suits your needs or framework).

"gameLogic" receives the board and the input, and internally, checks if it's the red or black player turn, and either processes the user input into board actions, or lets the AI produce board actions, then executes the generated board actions for the active player (which at this point, the logic doesn't care if they were produced by the user or AI, you could use this to pit two AIs against each other).

"render" just draw the board and the pieces, taking the visuals from the board.

Since this is a very simple game, an ECS (as i said above) is overkill and too rigid in it's traditional definition. Chess expanded to 4 times the board size and piece count, with several new piece types, and done in real time, could maybe be a better candidate :P

devstropo.blogspot.com - Random stuff about my gamedev hobby
Zakwayda
Zakwayda
Quote

I don't think you need an entity for the game logic. Also, game logic doesn't have to be a component. You can put the game logic into main(). Process the input, update the placement of units according to game rules, and then render them at their positions.

My answer was offered in the context of a strict entity-component system where all behavior is implemented in terms of components contained in entities (this seems to be what the OP is doing, and I think Unity's system works that way as well, although I may be wrong about that). Obviously nothing 'needs' to be an entity or component, but I think it's fairly common in such systems to implement all behavior via components.

One argument for doing it this way is that a component system will likely already have all the needed architecture in place for updating, logic, and so forth. For example, there will likely already be a main loop implemented somewhere, with all behavioral components iterated over and given an opportunity to do their work. Behavioral components may also have environmental information (like time deltas and input events) passed in to the update function (although this of course can be handled in other ways).

My thinking is, as long as you have such architecture in place, why duplicate it by placing game logic somewhere else, such as in main()? Implementing the game logic as a component seems more sensible to me.

Of course there's the question of whether to use an entity-component system at all. Regarding that:

Quote

Also, a bit of a rant and personal opinion here, i think ECS is overkill for something like a checkers game.

I agree, more or less, but the OP specifically said:

Quote

I am only doing this to learn how to use this (new to me) design pattern.

Personally, I don't think there's anything wrong with using a relatively simple project as an opportunity to familiarize oneself with a specific design pattern, even if, strictly speaking, that pattern may be unnecessarily complex for the use case in question.

LorenzoGatti
LorenzoGatti
28 minutes ago, Zakwayda said:

I agree, more or less, but the OP specifically said:

Quote

I am only doing this to learn how to use this (new to me) design pattern.

Personally, I don't think there's anything wrong with using a relatively simple project as an opportunity to familiarize oneself with a specific design pattern, even if, strictly speaking, that pattern may be unnecessarily complex for the use case in question.

Using a design pattern as an exercise is the worst way to learn about it: it's guaranteed that one will misapply the pattern, deform it into something trivial and/or abnormal, damage the overall quality of the software with inappropriate (maybe knowingly inappropriate) design decisions, and fail to understand how and why it is useful (because in that context it isn't useful).

I strongly suggest finding issues of any kind with iteration N-1 of the checkers game (redundant and convoluted code, pointless complications, lack of needed flexibility, hacky special cases, unnatural names, inefficiencies...) and trying to do something different and better in iteration N.

Predicting what kind and what degree of Component-Entity orientation is best is a fool's errand; even Hodgman's wise recommendation to replace inheritance with ownership (and delegation) can have exceptions and should be regarded as a test to think about (would these specific classes be improved?).

Omae Wa Mou Shindeiru
Zakwayda
Zakwayda
Quote

Using a design pattern as an exercise is the worst way to learn about it: it's guaranteed that one will misapply the pattern, deform it into something trivial and/or abnormal, damage the overall quality of the software with inappropriate (maybe knowingly inappropriate) design decisions, and fail to understand how and why it is useful (because in that context it isn't useful).

If that's the case, then I suppose the OP's aim is misguided to begin with and a different strategy should be preferred. I was just responding to the question as stated, but I'm happy to defer to your view that the endeavor is ill-advised (I don't have strong feelings about it myself).

Airtower
Airtower

Lorenzo,

i understand your point, and you are probably correct that it isn’t the best use of CES. However, I am trying to push/stretch my understanding of it for any future problems I may have in my development.


Furthermore, you are condemning this approach without any solutions or helpful ideas on the matter. I am purely trying to understand it better and both Zakwayda and strewya have both done just that.


I dont want want to turn this into any philosophical discussion on game design. It is much simpler. I am merely trying to understand this design pattern. In reality, I would probably not use this pattern for a checkers game but what if later I decided to make the checkers do more than just jump one another. What if they had characteristics like fighting, dice rolls or options other than standard checkers. That, I am sure would help to have CES as the design pattern.

thank you though for your time.

Hodgman
Hodgman
On 11/28/2018 at 2:01 PM, Airtower said:

I don't have a checkers game to change from OOP to Composite Reuse design.

Composite reuse is a core part of OOP theory. Using inheritance instead of composition for the purposes of code re-use is a violation of OOP - so your inheritance-based design is a non-OOP design. That's why I suggested studying OOP -- Inheritance Based Game game entity code is often called OO, yet it breaks one of OO's core rules...?

So, there's a difference between simply using composition (a core part of all software philosophies), the composite reuse principle (a core part of OOP), and "entity + component" frameworks (a common collection of different patterns in game engines).

That last group (entity frameworks), I'd categorise into three groups:

  1. Entity base class + inheritance. Usually something like virtual void Update in the base class, and every type of game object inherits from this base. As above, this is abuse/violation of OOP rules.
  2. Entity is a concrete class with a collection of "components" inside it. Components are basically the same as "entities" from #1. The entity class implements the service locator pattern so that "sibling" components can communicate with each other.
  3. "ECS" - this one is basically an abuse of the relational model instead of an abuse of OOP. Entities are just ID numbers. Components are linked to an entity using those IDs. Entity fulfill the service locator pattern as in #2. Instead of adding any logic/functions to entities/components like in #1/#2, all game logic goes into "Systems" and "Components" are just plain data.

These kinds of frameworks (and variations of them -- there's no standard version!) are popular in many different game engines, so yeah, it's worth learning how to write code within these kinds of constraints... However, it's worth noting that these frameworks aren't common software engineering practice outside of game engines, and for good reason - they typically trample decades of engineering/science ? They're basically replacing existing paradigms (e.g. OOP/etc) are inventing their own ad-hoc software paradigm instead.

One of the main goals of types #2 and #3 is to allow part of the job of game programming to be done outside of the game code itself. Instead of every type of entity having to be programmed in the main language, you can build new entity types by editing scripts/configuration files that specify a list of component types to be used. This is really useful on large scale projects, but is serious over-engineering for a checkers game (unless your goal is simply education, then over-engineer away :D).

IMHO if you want to practice working within frameworks such as #2 or #3, it would be better to build a checkers game in Unity or Unreal, which both implement style #2, and Unity has also recently added support for style #3. These kinds of frameworks aren't standard patterns across all of software engineering, and their limitations and pro/con trade-offs are arbitrary. Every game engine will have a completely different style of "entity framework" (if it has one at all).

So, you could make your own entity framework with your own choice of arbitrary limitations, and then try to make checkers within that framework... but that feels a little too arbitrary :) So I'd go with practicing on an existing framework in this case.

The other practice exercise that you could do is implementing checkers within several different existing programming paradigms and design methodologies.
º Design the data layout as you would for a RDBMS with relational model, and then write procedural style code to implement the logic.
º Design the data + logic relationships with UML and then write OO code. Make sure to only use has-a relationships and no is-a to practice composition!
º Implement the game logic with a functional language like Haskell, or in an imperative language (C/C++/Java/etc) but using immutable objects (after constructing an object, never modify any of its members).


As for a simple design... :) A board is made up of sprites and cells. A cell is made up of a sprite and zero or 1 pieces. A piece belongs to a player and has a sprite. There's absolutely no need for inheritance here and composition is fairly natural.


struct Piece
{
  enum Color { Red, Black };
  Color color;
  bool crowned;
  Sprite* sprite; // picture of a 
};
struct Cell
{
  Piece* piece;//which game piece is in this slot
  Sprite* sprite;//background image
};
struct Board
{
  Cell cells[8][8];//bit wasteful, only 32 cells are actually valid places to put pieces in checkers...
  List<Sprite*> background;//extra images for the borders of the board, etc...
};


Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.