What's a room?

Started by
17 comments, last by Isvar Arthur 7 years, 7 months ago

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.

Advertisement
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.

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.

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.

"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.

Sean Middleditch – Game Systems Engineer – Join my team!

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.

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.

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.

Sean Middleditch – Game Systems Engineer – Join my team!

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.

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.

This topic is closed to new replies.

Advertisement