ECS Game Programming Patterns

Hey guys! As someone relatively new to ECS, one thing that I find myself struggling with quite a bit is that I have almost zero patterns on which to fall back when I encounter familiar situations.

I’m sure a lot of you are familiar with https://www.gameprogrammingpatterns.com/, and while that website provides a great foundation for tackling real world scenarios, to me it doesn’t seem that many of these concepts map one-to-one with the ECS paradigm. I’m sure there are golden nuggets out there that I haven’t yet discovered, but almost all examples of how to use ECS in practice seem to be a bit on the simple side.

For example, in classic Unity, I can almost instantly visualize how I’d make a Pacman game. I have all sorts of tricks up my sleeve for managing the game’s state so that enemies and Pacman don’t spawn/start moving until the intro song has played, making sure Pacman’s and the ghosts’ movements respect the collision values in the level’s grid, making sure that that enemies correctly pathfind throughout the same grid, making sure that the player can’t move Pacman around while he’s in the middle of his death animation, and making sure that the whole game cleans itself up and does it all over again for the next level. At this point, I’m not sure how I’d achieve this sort of structured gameplay in an ECS-friendly manner. I could probably brute-force it, but there’s no way it’d be pretty.

On the other hand, ask me to make thirty-million birds fly around in the sky in a circle in ECS, and I’ve got it covered.

So I was hoping maybe some of the more advanced ECS users could drop by and share some of their favorite ECS patterns for dealing with common game behaviors. I’m a huge Overwatch player and I believe that’s a game that uses ECS, so personally I’d love to hear examples contextualized on what components they might be making and which systems they’d use to act upon them to create some of the behaviors that can be seen in that game.

Despite being as far from an “advanced ECS user” as one could get, I’ll share the one, single pattern that I use which might or might not even be good!

I like to represent “actions”, “verbs”, or essentially any situation wherein something is happening “to” an Entity as a Component added to a separate Entity referencing the target Entity, rather than adding that component directly to the Entity. For example: if my input system detects the input for “Jump”, rather than adding a “new DesiresJump()” component to the Player’s Entity and having my DesiresJumpSystem process and consume that component, I’d create a new Entity, add a “new DesiresJump() { TargetEntity = playerEntity }” to that Entity, and the DesiresJumpSystem would know to process that component against its TargetEntity, consume the Entity, and perhaps add a “new JumpingComponent()” to the playerEntity.

The advantage I’ve seen out of doing it this way is “a thing” can happen to an Entity multiple times within the same frame, and not only will I not get a Unity error from adding multiple components of the same type to my player (since I can’t always check for this from within a Job), but the DesiresJumpSystem can prevent the same action from being processed on the same Entity more than it needs to be. The only disadvantage I’ve seen to this is that I have to create a NativeArray each frame to keep track of which Entities have already been acted upon within the current frame.

I’d like to hear what you guys think of my example, but much more so I’d love to hear your own examples!

1 Like

Think about it this way:

  • you have entities like you player (in reality it’s not an object, it’s a group of components, similar to the gameobjects in the editor from this point of view)
  • you have components which have two distinct reasons to exist:
  • store some data about the component meaning (jump component: how high)
  • and/or tell the systems that this entity desires to jump
  • systems will make this action happen and can change the entities’ (the group of components) state (in other words can add or remove components)

So if you create a system which reacts the presence of the component “jump” what you will do?

  • read the “how high” data from the component
  • execute the action
  • when it ends you remove the component

Sometimes (most of the time) you need to go deeper. Which means you need to break up your action to fragments in order to be able to work with it across many frames.
So, you will have “BeginJump”, “Jumping” and “EndJump” components (forget that the example is not realistic, you get the idea, it’s a general example).
So when the player hit the space bar, you know (s)he desire to jump. You detect it with the input system.
You identify the player entity, put a BeginJump component on it.
The DesireJump system can read it, so it can calculate forces and whatnot. Also removes the BeginJump component and add a Jumping component.
The Jumping system detects the Jumping component, so it know that your entity began to jump. When it runs, adjust the forces/height/position/animation/whatever and calculate if it needs to stay in the next frame or the jump can be finished, if should stay, do not change the component. If the jump is about to finish, it removes the Jumping component and adds an EndJump component.
The EndJump system (if it even needed), tie up the jump, calculate anything should remain after the action and removes the EndJump component, so the entity finishes the jump action.

Again, this is a somewhat dumb example, but whatever. You can break down the Action into reusable pieces. If you then add a new entity (some enemies for example), which can jump, you just add the BeginJump to it along with the “how high” data and you’re good to go.

All of these mean that you will not design you’re application’s command flow, you will design your application’s data-flow. You will need to design how and what data will be present in any systems you add.

It’s good to get a whiteboard and draw it up, something like the flow-charts were back in the good ol’ days, when they OOD wasn’t a popular, hyped thing.

And one last thing: you cannot add multiple components of the same type to one entity. So you can only once BeginJump or Jumping.

1 Like

I feel like adding and removing components is the single most useful pattern right now.
As LurkingNinjaDev said: dataflow can be used to represent any structured gameplay progression.

Game programming patterns is really a solid book no doubt and some of the patterns are very helpful… in an OOP land at least.
But some of them feel like dancing around the convoluted OOP way of thinking and will therefore probably just hinder you in ECS land.

I find it is generally easiest to pick the simplest solution you can think of and model that with dataflow (adding and removing components).
That would be the closest to what I would call a pattern.

Edit: If you want multiple components on one Entity you could also look into DynamicBuffer.

1 Like

Any game can be described as a game-world state and changes to that state that happen over time

“state”(pure data) and “change”(pure logic) are 2 fundamental concepts
DOD app always clearly splits this two

Patterns in DOD are about organizing data structure, and ways it can be changed.