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.