Sign in to follow this  
cranberryq

What's a room?

Recommended Posts

Hi.

 

For the purposes of this question: Assume a bog-standard ECS system. Entities contain a set of references to components, components are pure data, and systems are (possibly stateful) functions that register/deregister compatible entities on entity creation/deletion/update, and are evaluated in a fixed order at a fixed rate. I say this just so that we know we're all talking about the same thing, as ECS means different things to different people.

 

Let's assume a game somewhat like the old Atari Berzerk:

 

berzerk2600Screen.jpg

 

The world is an undirected graph of rooms containing the player and monsters. When the player moves into one of the room's doors, the player is moved to the connected room in the graph. Let's assume that monsters and the player can move between rooms, and that each room contains some sort of room-specific physics context for performing collision detection and the like (perhaps one Box2D world per room). One physics context per room because entities in different rooms cannot collide or interact physically in any manner. Let's also assume that the rooms themselves can be created and destroyed at run-time (perhaps procedurally generated to some extent).

 

So, we probably need:

  • a system that's responsible for performing physics updates and collision detection for entities in all rooms, and publishing events when collisions occur
  • a system that's responsible for managing the graph of rooms, keeping track of which entities are in which rooms, and publishing events when entities move from room to room
  • a system that's responsible for running AI
  • a system that renders the room containing whatever is the current camera

Additonally, the camera, player, and all monsterlike entities should probably hold a reference to the room to which they currently belong.

 

This raises a lot of questions! It seems as though the four systems will end up fairly tightly coupled and need to share long-lived data that is not tied to any particular entity (and therefore won't be in components and is not appropriate to be shared via events).

  • The system managing physics needs to know about the current set of rooms, because it has to run physics updates for those rooms that it considers active.
  • The AI system needs to know about the graph of rooms when planning paths that span multiple rooms.
  • The rendering system needs to know about rooms to some extent in order to render the contents of a room.
  • The room system obviously needs to know about rooms because it is the authority for the graph of rooms and is responsible for tracking entities and creating/destroying rooms.

Each of these four aspects are fairly distinct, however. Are the rooms themselves entities? They have a similar lifecycle to entities, multiple systems need to update their own internal state when rooms are created/deleted, and it seems like at least some of the above could be implemented by giving entities that represent rooms a RoomComponent (that may be completely empty) and having systems maintain their own state keyed on the ID of each registered entity that has a RoomComponent.

 

 

Share this post


Link to post
Share on other sites
Why do entities need to know which room they're in? As I see it, the only systems that care about which room an entity is in are the collision system (the AI system can pull collision data from the collision system without knowing where it comes from - that's part of the point of a collision system, to wrangle collision data) and the rendering/"presentation" system. I'm not even sure if you really need a separate room component with the architecture you've laid out.

What if you did it like this?

- Entity positioning is done through some sort of global coordinate system that applies across all rooms.
- When loading/generating the level, build a set of rooms that partition the play space. This will just be a set of rectangles, possibly with some metadata.
- For each room, register the room in the collision and rendering systems, which will use the room data to sort entity "colliders" and "renderables" into buckets corresponding to the rooms.
- Have the collision and rendering systems update which room buckets their respective components are sorted into when entities move.

With this approach, "rooms" exist solely as sorting keys for colliders and renderables. They don't have behaviour or even a real existence of their own after level load unless you're dynamically creating new rooms as the game is running. Edited by Oberon_Command

Share this post


Link to post
Share on other sites

Why do entities need to know which room they're in? As I see it, the only systems that care about which room an entity is in are the collision system (the AI system can pull collision data from the collision system without knowing where it comes from - that's part of the point of a collision system, to wrangle collision data) and the rendering/"presentation" system. I'm not even sure if you really need a separate room component with the architecture you've laid out.


I suppose the behavior as described doesn't require entities themselves to have references to the rooms they're in. Note that the AI system does require access to room data both for attacking entities in the current room, chasing entities across room boundaries, and for plotting paths that may span multiple rooms.
 
I'm not really convinced, however, that your proposal really addresses what I asked. I phrased the question along the lines of "should rooms be entities?" because it highlighted something that I've not seen addressed in any of the literature on ECS systems online. That is, long-lived data shared between systems that isn't associated with any particular entity. In particular, I've read that the idea is to eliminate this as much as possible in the interest of reducing coupling between systems.
 

- Entity positioning is done through some sort of global coordinate system that applies across all rooms. - When loading/generating the level, build a set of rooms that partition the play space. This will just be a set of rectangles, possibly with some metadata.

 
Regardless of how the rooms are represented, I think we agree that they at least do have a representation...
 

For each room, register the room in the collision and rendering systems, which will use the room data to sort entity "colliders" and "renderables" into buckets corresponding to the rooms. - Have the collision and rendering systems update which room buckets their respective components are sorted into when entities move.

 
So the individual rooms may have unique identifiers? Because, presumably, to try to reduce coupling via sharing data, the systems don't actually get full access to the room data structures, just some small aspect of them.
 

With this approach, "rooms" exist solely as sorting keys for colliders and renderables. They don't have behaviour or even a real existence of their own after level load unless you're dynamically creating new rooms as the game is running.

 

Let's also assume that the rooms themselves can be created and destroyed at run-time (perhaps procedurally generated to some extent).

 
I feel like you may have made my point for me:

  • Rooms do seem to require some run-time representation
  • The full room data structure probably shouldn't be shared between systems, in the interest of reducing coupling and in the interests of good software engineering (data hiding). Therefore, each system that deals with rooms in any form should only be exposed to some small aspect of them
  • Rooms likely need unique identifiers if the full room data is not going to be shared between systems
  • Rooms are created and destroyed at run-time
  • To avoid storing room data globally, the room graph should ideally be encapsulated in a system who's only responsibility is to track entity movements between rooms, and to manage the creation/deletion of rooms.

They seem to have all of the characteristics of entities.

Share this post


Link to post
Share on other sites

It is not as bad as you're making it out to be.

 

 

 

As shown in the picture, a room is a set of walls, which are impenetrable navigation objects or physics objects. Most physics engines make this easy, walls are just a rectangle or box added to the level/room.

 

Moving between rooms is a trigger area in the doorway. It does not have a visual component, just a collision area. Collision with the trigger causes the next room event.

 

As for them needing identifiers, EVERYTHING needs identifiers. As for them needing to be created/destroyed or loaded/unloaded, EVERYTHING needs to be loaded at some point. You will need to load the room, but that includes everything: the walls, the monsters, the keys/items, and whatever else you've got in your room. Level loading is bog-standard functionality you'll need in everything with a level, from what bricks to display in breakout, to the blocks and platforms in classic Mario games, to all the rocks and obstacles in an MMO. 

Share this post


Link to post
Share on other sites
"Spaces" are pretty important to good engine design. Engines that lack them (e.g. Unity, and a few other popular ones) really screwed the pooch by releasing without. You often find some pretty grotesque hacks in real shipping games made on those engines that try to work around the lack of spaces.

What is a space? It's just a collection of game objects and related geospatial systems. A space contains a physics world. A space contains a scene graph. A space would contain data like AI maps. A space contains a set of entities. Most of your message queues/buffers would likely be per-space, but some may well be cross-space depending on need. In an ECS sense, this means that some systems would exist per-space and some would not. Your game object (i.e. entity) creation/loading code needs to know which space a game object is being created in so it knows which per-space systems to request/notify wrt components.

That's pretty much it. There are separate physics worlds so the objects cannot interact. There are separate scene graphs so you can have an object at the origin in two spaces and still be able to render only one of the spaces. You could render both with different viewports/cameras/render-targets. Entities can interact cross-space via systems/messages that are intentionally designed to interact cross-space and are limited to the space that owns their components/systems for systems that are not designed to be cross-space.

This handles room. It handles UI separate from game worlds. It handles overland maps vs tactical maps. It handles switching scenes like going from a character selection scene to an in-game selection scene. It allows background scenes that can't interact with foreground scenes, e.g. in fighting or other 2D layered games. It enables multi-tab tools to work efficiently as each object/scene can be loaded into an independent isolated space.

A high-level game logic layer deals with creating and destroying spaces. The cross-space systems can manage this ownership if you're in a strict ECS where everything lives in a system, or it can be a script file with ad-hoc user code, or whatever else you think will work.

TL;DR: have spaces where a space is a collection of game objects and all geospatially-relevant systems to make rooms and a ton of other high-value features trivial to implement.

Share this post


Link to post
Share on other sites

It is not as bad as you're making it out to be.


I wasn't passing judgement.
 

As shown in the picture, a room is a set of walls...


I'm well aware what constitutes a room.

My problem is that I've never implemented anything using an ECS before and am trying to find conventional solutions to specific problems of data sharing and communication when dealing with a strict ECS.

Share this post


Link to post
Share on other sites

What is a space? It's just a collection of game objects and related geospatial systems. A space contains a physics world. A space contains a scene graph. A space would contain data like AI maps. A space contains a set of entities.


I see. This is certainly something I've been considering.

I have a fair amount of experience in programming language design, type systems, formal verification, etc. In those fields, you generally start from the smallest possible formal model that you can get away with and only add something if there is absolutely no possible way you can avoid it. As such, I've been thinking about an ECS implementation in the strictest possible terms. That is, the system has entities, components (data-only), and systems. Communication between systems is achieved via an event bus, and the assumption is that systems automatically have compatible entities registered and deregistered on creation/destruction/component changes. Sharing of data between systems is discouraged. If you think extending this model with this kind of partitioning is worth it, then I'll certainly look into it. Edited by cranberryq

Share this post


Link to post
Share on other sites

If you think extending this model with this kind of partitioning is worth it, then I'll certainly look into it.


I'll state what I said a bit more simply: not having spaces is just crazy. There are just so many things you can't reasonably do without a space system. So yes, it's worth it.

It's also trivial in an ECS design because it's just a matter of defining whether a system lives within a space or is shared amongst all spaces. Generally, your systems that are spatially-relevant will be per-space (physics, graphics, triggers, AI visualization, path-finding, targeting, streaming, audio sources/listeners, some scripts, etc.). Anything else can just be a "global" system (player progression, input, level transition, some scripts, networking, high level game state, etc.).

I currently work in an engine that lacks spaces for my day job and it blows. There are so many hacks, so many bugs, and so many complicated scene transitions that could be heavily simplified with spaces (and the same was true with several other engines I've used that lack spaces), and that doesn't even get into the crazy stuff the tools have to do because we lack them. Unfortunately, retro-fitting spaces into a big engine is a _massive_ undertaking, which is why it's pants-on-head retarded to start a new game/engine without them. Spaces are something you need to architect for from the very beginning.

The trickiest part people run into is the question "how do game objects move between spaces" for which the answer is "they don't, ever, and if you think you need them to then your game objects are over-complicated." Example: a player has a physical representation (avatar) that might exist in a space, but that object doesn't need to be the same in every space. The player system can track which avatar object is in use and create/destroy/rebind them as necessary when a player "moves" between spaces. An ECS approach simplifies this as you can just create the components you need on demand in a new space and reuse player's entity ID, but even then you can run into some management problems and it's best to just use two different entities for the in-space portions and the out-of-space portions, in my experience.

Share this post


Link to post
Share on other sites

What is a space? It's just a collection of game objects and related geospatial systems. A space contains a physics world. A space contains a scene graph. A space would contain data like AI maps. A space contains a set of entities.

 

So - what exactly distinguishes a "space" from just a "scene"? If all of your systems, for example, only operate on things in a scene that you pass in as a parameter to the system's work functions?

 

I have sort of run in to this issue lately - but it seems like using "scenes" works the same as spaces, where all entities contain an id indicating which scene they are in and they cannot actively be in more than one at a time - but they actually live in memory in some higher world object.

Share this post


Link to post
Share on other sites

 

For each room, register the room in the collision and rendering systems, which will use the room data to sort entity "colliders" and "renderables" into buckets corresponding to the rooms. - Have the collision and rendering systems update which room buckets their respective components are sorted into when entities move.

 
So the individual rooms may have unique identifiers? Because, presumably, to try to reduce coupling via sharing data, the systems don't actually get full access to the room data structures, just some small aspect of them.

 


No. What I'm suggesting is that the individual rooms ARE identifiers - they are arbitrary rectangles that identify sorting buckets in specific systems.
 

Let's also assume that the rooms themselves can be created and destroyed at run-time (perhaps procedurally generated to some extent).


This doesn't change the general approach I described. Presumably, you have some system that generates rooms. You have some criteria for destroying them, so presumably you must have one of those, too. Otherwise, why have a requirement to manipulate rooms at runtime? So, give the systems that sort data by room methods to react to creating or destroying a room.
 

In those fields, you generally start from the smallest possible formal model that you can get away with and only add something if there is absolutely no possible way you can avoid it.
...
That is, the system has entities, components (data-only), and systems. Communication between systems is achieved via an event bus, and the assumption is that systems automatically have compatible entities registered and deregistered on creation/destruction/component changes. Sharing of data between systems is discouraged.


Ironically, what you're describing is one of the most (over-)complicated forms of ECS. The original, simplest, and I would argue most useful form of ECS puts the components inside the systems, has systems directly communicate with one another by calling each other's methods, and makes "entities" be identifiers that allow systems to know which component instances on other systems represent the same object in the world. If you really want to start with a "strict ECS", then I submit that you use the one I describe and not the one you are currently building. The one you're doing is based on an exaggeration (and of the wrong parts, IMO!) to the point of misinterpretation of the original idea. I'm not sure how that particular formulation got so widely-known, but it has some problems.

Edited by Oberon_Command

Share this post


Link to post
Share on other sites

No. What I'm suggesting is that the individual rooms ARE identifiers - they are arbitrary rectangles that identify sorting buckets in specific systems.


OK, got it.
 

Ironically, what you're describing is one of the most (over-)complicated forms of ECS. The original, simplest, and I would argue most useful form of ECS puts the components inside the systems, has systems directly communicate with one another by calling each other's methods, and makes "entities" be identifiers that allow systems to know which component instances on other systems represent the same object in the world.


Is there any documentation on this formulation of ECS? I realize it's basically a different way of doing the same thing, but there are always important subtleties that emerge sooner or later.

One question that arises is that if the components themselves are inside systems... Is it actually possible to create entities without knowledge of specific systems? In the "components live outside and are just data" formulation, the components are directly specified by whoever's creating the entity, without any specific knowledge of which systems will eventually process the components.

Share this post


Link to post
Share on other sites

Is there any documentation on this formulation of ECS? I realize it's basically a different way of doing the same thing, but there are always important subtleties that emerge sooner or later.


That depends greatly on what you mean by "documentation." There are various GDC talks, blog posts and forum posts that explain the pattern. From a quick google search, it looks like they're being drowned out by articles based on the current Wiki article on ECS (or whatever that's based on), which advocates the approach where the components are stored separately from systems - and in all fairness, that IS more or less the only difference, but one that has big consequences on design.I'm not aware of any journal papers or the like. :)

This article has a brief overview of the different ECS variants. The one I'm advocating here is closest to Adam Martin's formulation as described therein (but weirdly not as Martin writes?).
Mike Acton's CppCon talk touches on some related concepts, but doesn't advocate "strict" ECS.
 
It's hard to find good sources on an arbitrary implementation of ECS because ECS is not a well-defined term. It's a group of related design patterns with subtle differences between them, some of which are more popular than others. I might be mistaken, so my formulation may not be the original, either, but it's the first one I remember seeing.
 

One question that arises is that if the components themselves are inside systems... Is it actually possible to create entities without knowledge of specific systems? In the "components live outside and are just data" formulation, the components are directly specified by whoever's creating the entity, without any specific knowledge of which systems will eventually process the components.


This isn't an important difference. Knowing about the components is functionally equivalent to knowing about the systems that own the components. This is true regardless - even if the components are stored outside the systems, they're still stored somewhere, and your creation logic needs to know about that place.

The important difference is that having the components be owned by the system allows other systems to not know about other systems' components directly. Remember, in a "pure ECS" of this type, all access to a component is gated through the system that owns the components of that type. All behaviour associated directly with the component is on the system that owns it. It gets a little funky when you have behaviour that affects two components that doesn't belong on a specific system, but this problem happens with "regular" OOP, too - and it's solved much the same way, by using free functions or creating another component type.

In my own code, the component structure definitions are usually private to the system that owns them. Other systems don't even know that those systems' component types exist, never mind being able to access them. On the other hand, I don't use strict ECS, and I actually think putting yourself in a box with strict ECS is counter-productive if you want to write clean code that is also shippable. If you really want strict ECS, it's probably best not to overarchitect it. Edited by Oberon_Command

Share this post


Link to post
Share on other sites

A room as a compound (not necessarily an entity as in an ECS) may have

 

* a NavigationComponent instance (navigation graph or mesh), to be installed with the NavigationServices;

 

* a set of ColliderComponent instances, perhaps tagged for specific usages, to be installed with the CollisionServices;

 

* a set of PortalComponent instances (of the entree or exit kind, interconnecting rooms inclusive positional mapping), to be installed with the NavigationServices, used as another level of navigation;

 

* perhaps a LocationComponent that gives the room a name, a location on a map, or such a thing

 

* ...

 

Not a RoomComponent makes the entity a room, but the collection of more generic components. We are not speaking of a strict and explicit typing here, but of a kind of duck typing.

Share this post


Link to post
Share on other sites

That depends greatly on what you mean by "documentation."


Thanks for the sources!
 

In my own code, the component structure definitions are usually private to the system that owns them. Other systems don't even know that those systems' component types exist, never mind being able to access them.


I'm curious... Can you show me an example of entity construction under this system? In the T-machine variant of ECS (which is the one I'm looking at right now), construction ends up being something like:
 
auto e = Entities.create();
e.addComponent(Position(...));
e.addComponent(Renderable(...));
 
Typically with some kind of internal logic in addComponent that registers the entity with interested systems.

The idea of components remaining private to systems appeals to me far more than the basically duck-typed, expose-everything, share-everything T-machines approach.

Share this post


Link to post
Share on other sites

If I were doing this (and I'm only a hobbyist, making small games), I'd create a full world (in physics-library terms) with all the rooms in it, and all the enemy's given their coordinates within the world, and the player has a coordinate within the world, starting in some "room."  A room in my world would just be the current camera view, given some specific dimensions in my world (maybe I have it setup as 20 meters x 18 meters, which would map to a full screen in a window).

 

The camera component needs to know about the rooms dimensions and the player's location, so when a player walks through an opening and hits the portal at the edge of a room, the camera would shift to the next room.

 

You can work it so enemies are asleep unless the camera enters the room they are in, or, if you want Monsters alive always, you can have them doing their thing in the other rooms, I don't think it would hurt anything.

 

IMO, this is the easiest solution.  There is no real "room" concept, except to the camera.  Either way, whatever you decide, good luck!

Share this post


Link to post
Share on other sites

I'm curious... Can you show me an example of entity construction under this system?


An NPC entity might get constructed like this:
 
// create the npc
if (auto npcDefinition = Definitions.NPCs.TryAndGet("elf"))
{
    auto e = Entities.create();
    Systems.Transforms.Create(e);
    Systems.Sprites.Create(e, npcDefinition->spriteDefinition);
    Systems.Characters.Create(e, npcDefinition->characterDefinition);
    Systems.Combat.Create(e, npcDefinition->characterDefinition);
    Systems.Monsters.Create(e, *npcDefinition);

    // create the weapon the NPC is holding
    if (auto weaponDefinition = Definitions.Weapons.TryAndGet(npcDefinition.weaponDefinitionName))
    {
        auto w = Entities.create();
        Systems.Transforms.Create(w);
        Systems.Sprites.Create(w, weaponDefinition->spriteDefinition);
        Systems.Weapons.Create(w, *weaponDefinition);
        Systems.Combat.RegisterHasWeapon(e, w);
    }
}
This isn't actually how I do this in my own code. I'll post a bit of my own code, but first a bit of context. I don't use a pure ECS in that I don't have one global system that represents entities. Some systems represent particular kinds of objects and other systems represent data that is temporarily or permanently affixed to them. So, for instance, sprite objects exist as simple sprites, while characters exist independently and maintain references to sprites that represent them. They aren't hard-tied to one sprite, either - a character could switch to use a different sprite object if wanted, though at this point I'm not taking advantage of that. Weapons and items work similarly. NPCs are a separate system that references characters and their associated sprites

Here is how I spawn NPCs:
// ---------------------------------------------------------------------------------------------
// GameplayLayer is the glue code that owns and connects gameplay-related systems
// "monster" == "NPC"
// "culling area" == "room" - culling areas live in their own system right now, but they don't have to
void GameplayLayer::RespawnMonsters()
{
    while (spawns.HasQueuedMonsters())
    {
        auto position = sf::Vector2f(spawns.NextQueuedMonster());
        auto cullingAreaIndex = culling.FindCurrentCullingAreaIndex(position);

        auto characterHandles = RespawnCharacter(position, "elf");
        monsters.Register(
            characters.GetCharacter(characterHandles.second).definition,
            sf::FloatRect(culling.cullingAreas[cullingAreaIndex]),
            characterHandles.first,
            characterHandles.second);
    }
}

// ---------------------------------------------------------------------------------------------
std::pair<TypedHandle, TypedHandle> GameplayLayer::RespawnCharacter(
    const sf::Vector2f position,
    const DatumString_t& characterName)
{
    auto characterDefinition = characters.FindDefinition(characterName);
    auto spriteDefinition = spriteObjects.FindDefinition(characterDefinition->spriteName);

    auto spriteHandle = spriteObjects.CreateSprite(
        spriteDefinition,
        position);

    auto characterHandle = characters.CreateCharacter(
        characterDefinition,
        spriteHandle,
        SpriteDirection::South,
        &spriteDefinition->idleAnimation.south);
    combat.RegisterCharacter(characterHandle, characterDefinition);

    if (characterDefinition->startingWeapon.length())
    {
        auto weaponDefinition = items.GetNamedWeaponDefinition(characterDefinition->startingWeapon);
        auto spriteDefinition = spriteObjects.FindDefinition(weaponDefinition->spriteName);
        auto spriteHandle = spriteObjects.CreateSprite(
            spriteDefinition,
            position);
        items.CreateBoundWeapon(characterHandle, weaponDefinition, spriteHandle);
        combat.RegisterWeapon(characterHandle, weaponDefinition);
    }

    return std::make_pair(spriteHandle, characterHandle);
}
This could use a bit of tidying, and again, it isn't quite ECS, but hopefully this should get the gist across. Edited by Oberon_Command

Share this post


Link to post
Share on other sites

So - what exactly distinguishes a "space" from just a "scene"? If all of your systems, for example, only operate on things in a scene that you pass in as a parameter to the system's work functions?   I have sort of run in to this issue lately - but it seems like using "scenes" works the same as spaces, where all entities contain an id indicating which scene they are in and they cannot actively be in more than one at a time - but they actually live in memory in some higher world object.


Space, scene, room, world, stage, etc. are all common names for the same thing*.

I prefer "space" because it means exactly what it says (it's a spatial collection), the concept was introduced to me that way, the engines I've used professionally that support the concept call them spaces, and Space is the Place.

* Mostly. Some engines come up with other creative applications of those nouns; Unity for instance calls it's levels "scenes" but doesn't let you load them into spatially distinct spaces. The engine I work in for my day job likewise calls it's levels "worlds" (and we also use them as level layers) but we had to hack in spaces support because it wasn't there originally.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

Sign in to follow this