ECS design for a game with different location instances

Started by
3 comments, last by All8Up 3 years, 7 months ago

I am trying to create a game using an ECS but having trouble deciding how to design it. The game consists of a map of space, and contains a number of locations such as asteroids. You can “jump” in to an asteroid, which hides the map and displays the picture of the asteroid and any ships or stations that are in orbit of the asteroid. These cannot be seen directly from the map screen, nor can you scroll seamlessly from one asteroid to another - you must “jump” out back to the map.

Ships can be present at an asteroid, where they can be seen flying around, or they can be outside of a specific location but moving between locations on the main space map. In that case, the ships still exist and are displayed as icons, but they don't have a full graphical representation. Ships can be organized into fleets and move as a unit. Enemy fleets can intercept your fleet, either at an asteroid, or in space which creates a new location instance (with just a space background) to display the battle, and then ceases to exist.

How can I handle the case of ships being in a fleet? Would a fleet be a component as well, with a list of handles to the ships?

How can I handle the fact that ships sometimes have a full graphical representation and a location instance state (eg the location of where they are currently by the asteroid, as they will aimlessly fly around it just so not to be boring), but sometimes don't (when in a fleet outside of a location). Add/remove some components dynamically?

Should I have a single ECS which has every ship and building that exists? Or should each location instance have its own mini-ECS which contains only stuff specific to the location? For example, the ship's main component (type, health, etc) would be in global ECS. But the ship's graphical representation and local position data (if any) would exist in the location specific ECS and be removed when it leaves the location. Likewise for buildings which are constructed on particular asteroids.

Advertisement

u have reach the point where u need to slow down just a little a bit and need to go back to the drawing board… why ? because after your brainstorming session, part of mind is -for lack of better word- fighting with the idea of making an entity a component and a component an entity, hence the questions, hence the mix-up. Yet, the answer is on the drawing board but you are not reading from it.

So the drawing board says this:

  • look at the diagram on the right (with yellow boxes in it): https://en.wikipedia.org/wiki/Composition_over_inheritance
  • what do these boxes have in common?
  • they all talk about animals …. urrrm…no…. ok nearly all
  • ECS is kind of like this diagram but better in that it allows you -CzarKirk- to make every object in your game unique so that when you -CzarKirk- talk about your objects, we -The people- would know exactly which object you are talking about.
  • So, if ECS nearly follows the same principle about making everything or nearly all the same but at the same time permitting uniqueness, how can you code it in your game? Well…:
    • you can make every object in your game be a unique entity
    • you can make every entity have one or more components (many developers settle for 1 component per entity)
    • you can make this 1 component have data (which very often than not represents the state of this component, which means your game logic can alter the state of this component anytime necessary)
    • you can create systems which treat one or more entities based on the state of their components, for example, if Unique Entity A is about to fight with Unique Entity B, both A & B can be treated (processed) by the same system S because S cares about entities which have components in state ‘Fight’

And that's ECS in a nutshell for dummies' on the drawing board ?

So now that we know this, let's see if we can answer some of your questions:

How can I handle the case of ships being in a fleet? Would a fleet be a component as well, with a list of handles to the ships?

Reading our drawing board, we can make every object a unique entity:

// pseudo
fleet is a unique entity F
ship is a unique entity S
They're unique because F ≠ S (some people use unique integers instead, you can lookup GUID for example)


Reading our drawing board, we can make our entities have one component each:

// pseudo
F→has-ships: yes or no (the 1 component here is a bool called has-ships)
S→is-in-fleet: yes or no (the 1 component here is a bool called is-in-fleet)

Reading our drawing one last time, we can create systems, that will take care of these 2 entities:

// pseudo
System1→(treat E1 and E2, but only if E1 has ships and E2 is in a fleet)
System2→(treat E1 and E2, but only if E1 has ships and if one of them is E2 and if E2 is in a fleet and that fleet is E1)
System3→etc…etc…

Now, let's look at these systems a bit more…

If you didn't realise, you should know that System1 and System2 run continuously independently of each other:

Sys1 could be a system which in the game logic maybe paints all ships green and in all/any fleets. Importantly it doesn't matter in this system if F has that ship or not! All that matters in this system1 is "You're a ship and you're in a fleet, and you're a fleet that has ships and y'all in my system? Y'all gettin' green!"

System1→(treat(paint in green) F and S, but only if F has ships and S in a fleet)

Now let's look at System2, oh but hang on, we can't use system2 on F and S because this system implies that F keeps a list of ships and ships of fleets they're in!
So Sys2→(takes F and S but does nothing) because the state of their components is not enough or incomplete to have a meaning in this system. So if F and S are to be used in this system their components must be extended to include knowledge of ships Versus fleets. I'll leave that for you to workout as an exercise (but remember, many game developers have settled to work out 1 component per entity. Clue? can't just be having a bool then ?)

There's a lot more that can be said here, but this should give u an idea of how to progress…. for now as Jack says in Heartland:
“This conversation is o'er, we're dun he'e!”

That's it …. all the best ?

If it's just a bool, how is a ship supposed to know which fleet they are in or vice versa?

While ddlox makes a number of good points about Ecs in general, I'd approach this from a different perspective. From the top level, the key bit is how to handle two very different game states and from that the rest of the questions tend to be left more as a game design decision rather than an architecture decision. Basically the key bit is the map (or strategic) mode versus the instance (or tactical) mode of display. I'll just assume for the moment that you keep all entities in the same Ecs world and describe a potential solution. So, ignoring everything else, you have the following possible entities and their components:

Ship: Position, Orientation, Renderable
Fleet: Position, Orientation, Renderable
Asteroid: Position, Orientation, Renderable
Camera: Position, Orientation, Camera

Obviously if you have a system which takes Position, Orientation and Renderable it is going to render everything, something you don't want. Or will it? Thinking about this from the perspective of the rendering system architecture in general, a core element here is the culling mechanics. Simply removing items which are not on screen is just one thing that most culling systems perform, another fairly standard item is filtering based on layers and of course per renderable visible flags. So, for instance, the Renderable might have the following definition (or you could break this into individual components, up to you):

struct Renderable
{
	MeshId mesh; // The thing to render.
	uint64_t layers; // Mask which determines which layer this renders on.
	bool visible; // Simple entity controlled "render me/don't render me" flag.
};

So, when you create your entities, assign a unique bit in the layers member to each type of thing. So, for instance, individual ship layer=1, fleet layer=2 and asteroid layer=4. In the Camera component you have a matching mask but you set the mask appropriately for each state of the game (or switch between camera entities which have different masks). As such, the mask in the camera is going to be 6 (i.e. fleets and asteroids) when you are in the map mode and the culling system in the renderer just performs a bitwise ‘and’ between the renderable mask and the camera mask, if it is non-zero (and the visible flag is true), you let it process through the rest of the culling, otherwise just trivially reject it. The instance view could be as simple as focusing the camera on the asteroid and setting the layer mask to 5 (ships+asteroids) and relying on the culling system to trim out all the other ships and asteroids, but if this is a 3D view you could have other asteroids in front or behind, so you probably want to refine things to “just this asteroid”. Well, that is a bit more complicated.

In order to selectively cull things to an instance you need a bit more than just layers, you need some contextual information. Thankfully, this gives us a double duty solution for other issues you asked about such as fleets and such. The way I would do this is to supply each of the three types (ships, fleet and asteroid) with a “parent" entity id. Ships would always have a parent of either a fleet (yes a ship moving between locations would be a “fleet” of 1 if not assigned to a real fleet) or an asteroid. Fleets would have a parent of invalid when transiting between asteroids or an asteroid id when it arrives. With this, you can add a little more logic to the culling mechanism which checks the parent id against an id in the camera we'll call currentInstance. So, if you follow the parent links up till it becomes invalid, and none of them match currentInstance, don't render, otherwise fall through to normal culling. Recursion like this is generally a bad thing in an Ecs but unless you are dealing with millions of entities, it isn't a show stopper and there are ways to remove it later if it is problematic.

Now, breaking down each of your questions, putting it into context of the above:

CzarKirk said:
How can I handle the case of ships being in a fleet? Would a fleet be a component as well, with a list of handles to the ships?

I would make the fleet it's own entity with a specific fleet component. The fleet entity as mentioned above has a parent and it is set to either an instance (asteroid) meaning it is no longer moving or it is invalid and the fleet is in the process of going somewhere. The component specific to the fleet is just the data needed to show the fleet moving between locations, it doesn't know which entities are assigned to it directly, you can quickly query the ECS for that information at anytime though.

How can I handle the fact that ships sometimes have a full graphical representation and a location instance state (eg the location of where they are currently by the asteroid, as they will aimlessly fly around it just so not to be boring), but sometimes don't (when in a fleet outside of a location). Add/remove some components dynamically?

I avoid add/remove components as much as possible due to the nature of the ecs-like system I use, it is an archetype solution which means there is significant overhead possible when adding/removing components. The cost will depend on the specifics of your Ecs, archetype it is relatively expensive, sparse vector approaches it's fairly cheap. All the above description is based on the idea of not adding/removing components dynamically. It doesn't really matter which type of Ecs, you just use slightly modified variations of the above, i.e. in a sparse vector you could just add/remove the parent id rather than having an invalid check per entity, it's a trade off.

Should I have a single ECS which has every ship and building that exists? Or should each location instance have its own mini-ECS which contains only stuff specific to the location? For example, the ship's main component (type, health, etc) would be in global ECS. But the ship's graphical representation and local position data (if any) would exist in the location specific ECS and be removed when it leaves the location.

I would tend to keep everything in the single Ecs unless there is some good reason not to do so. The potential for bugs managing fleets comes to mind as a reason “not” to create multiple Ecs worlds, moving entities between worlds is not horrible but could lead to confusions and bugs.

Likewise for buildings which are constructed on particular asteroids.

I would do buildings just like ships such that they always have a parent entity. This would also tend to suggest you don't want separate Ecs worlds since the idea of buildings on various worlds tends to indicate you will have a concept of global economy between asteroids, ships etc. I.e. a ship in orbit around an asteroid with a repair bay gets a bonus to repairs and it's health goes up more quickly. A planet with a farming building might produce an excess of food which you put in the global pool and assume it will be shipped to other asteroids nearby that might have a deficit of food for the population. Etc etc, all these systems being on a single Ecs world makes the potential complexity of interactions much easier to deal with than having to query multiple Ecs world's for the information.

Just keep in mind, your mileage may vary based on the Ecs you are using, how complex your game economy might be, the tactical combat parameters, fleet behavior, etc etc…. All I can really say is that this is where I would start based on the game design elements you listed.

This topic is closed to new replies.

Advertisement