Organizing Games

At this point we have lots of different moving parts of different types: textures, animations, sprites, tiles, tilesets, tilemaps, collision bodies, contacts, and so on. How can we organize this in a useful, usable way? You may have encountered this question already in working on your own 2D games.

With our skeleton from last time as a starting point, let's first make a distinction between three types of data:

  1. Static game properties like level layouts, entity definitions, and so on.
  2. Resources like textures and tilesets.
  3. Dynamic game state which can change from frame to frame.

If you're making a game engine, you probably want to provide types and functions for defining and storing these types of data. It might make sense to define traits for them and write engine functions which are polymorphic with respect to the specific game state type, or it might make sense to ask game authors to use a given set of data structures and map their own game-specific concepts to and from your engine's types. In any event, we rarely want the game author to be handling all of these types of data simultaneously. Part of the advantage of using an engine is trading away some complexity by opting in to the engine's management of resources, state, and properties. For this reason, you probably want the engine to give out some type of reference—maybe a Rust &reference or an opaque struct like TileID—to resources and properties, and possibly even wrap state and provide a query interface ("give me the colliders of the entities within 16 units of this one", "iterate through every entity tagged with enemy"). In this course, you get to decide where the engine-game boundary falls.

For now, I'd suggest trying to keep the game in charge of defining and owning the game state and properties and the engine in charge of resources. The engine can define types like colliders and entities, but it might be best to let the game code hold ownership over them and pass them into the engine's functions on an as-needed basis. This way, you can constrain the job of the engine to just processing data and ensuring the right resources are loaded at the right time, and in particular the engine doesn't need to have its own representations of complex game worlds—just knowledge of how to arbitrate collisions, render tilemaps, and so on.