Insight

Welp, it’s been fun, but Bloodborne came out this week, which means Gunmetal Arcadia is paused indefinitely. (Not really but yeah but not really but yeah.)

For Super Win the Game, I created a dialog tree system for NPCs, signs, and various other pop-up dialog boxes. This system could display text and present the player with options which would branch the tree. It could also query the game state and branch depending on the results. Some of the more complex uses of this system were the jeweller and fortune teller NPCs, whose responses would change depending on how many gems the player were carrying, which items they had collected, and so on.

I’m reusing this system in Gunmetal Arcadia and updating it to hopefully increase its functionality while reducing some of the wordiness. In Super Win, each tree was authored once in the editor and never changed at runtime. For a known set of content, it was reasonable to make a query for the presence of each and every powerup in the game, but for Gunmetal, I’m trying to build with flexibility and scalability in mind. I don’t know how many items and upgrades I might have, so handwriting queries for each is out of the question. What I’m doing instead is making dialog trees able to be dynamically authored in code. The content I write in the editor can now contain markup indicating that the construction of the tree should be delegated to code, and additional markup can be passed to that code to specify exactly what is desired.

A snippet of a dialog tree in Super Win that queries against all known powerups.
A snippet of a dialog tree in Super Win that queries against all known powerups.
A dialog tree in Gunmetal Arcadia that delegates further construction of the tree to code.

In Super Win, dialog box options were typically limited to two or three choices (most often “yes” or “no”), but forĀ Gunmetal, I anticipate using dialog box options for displaying lists of gear that the player may wish to buy or sell. As these lists may contain any number of items, I’ve added support for scrolling through a set of options that occupy a fixed number of lines.

GunArc 2015-03-27 01-52-23-878

After adding medusa heads last week, I continued working on new enemy designs this week, starting with two versions of enemy spawners. This prompted me to solve a problem that’s been lingering for a while. When I wrote about my entity serialization solution recently, I mentioned that a fix-up pass might be necessary to restore references between entities when loading serial data. This week, I finally tackled that problem as part of implementing correct enemy spawner behavior.

Spawners will generate new baddies up to a specified limit, but after the player kills one of these enemies, the spawner can produce another. To facilitate this, either the spawner must keep track of references to its offspring so it can determine when they’ve been destroyed, or else the offspring need to keep track of a reference to their spawner so they can notify it when they’re about to be destroyed, but in either case, a reference must be maintained in order for this system to function correctly.

GunArc 2015-03-27 23-58-17-251

As part of saving and restoring references between entities, I’ve had to reconcile two similar but distinct features of my engine: handles and GUIDs. Handles are assigned at runtime and are unique only for the duration of execution. These serve as a safe wrapper around pointers. When an object is deleted, its entry in the handle table is removed, which allows any remaining handles to that object to correctly fail a NULL test. GUIDs are unique values which identify objects that have been placed in the editor or spawned at runtime and which can be serialized. GUIDs persist beyond a single execution and are used by the serializer to associate data with a particular object. All entities and components have handles, but only entities that need to be serialized contain GUIDs.

Saving a reference to another entity involves simply saving out its GUID, as this is sufficient to locate the object if it exists. In the general case, this is trivial, but there are two problems we have to deal with:

  1. We want to save a reference to an entity that has been removed. We have a handle to this entity (as it did exist as one time), but we do not know its GUID, which is the thing we need to save.
  2. We want to load a reference to an entity that does not currently exist. We know its GUID, but as it does not exist, we cannot get a handle to it.

I went through a few iterations in solving this, and I’m pretty happy with the solution I’ve landed on. Any time a valid serializable entity is destroyed, a record is made of its handle and GUID. When that entity is rebuilt, we can look up its previous handle value from its GUID and reuse it such that any handles that pointed to the old instance of this entity will now point to the new one. This record also allows us to retrieve a GUID for a entity that no longer exists as long as we still have a handle to it, even though that handle no longer points to anything. This solves the first problem described above.

In some cases, this is also sufficient to solve the second problem. If an entity once existed but has been removed, we can look up its old handle from its GUID when we load a reference. That handle will not point to anything yet, but whenever the entity is rebuilt, it will assume its old handle value, and the handle will become valid.

The one remaining case is when we load a reference to an entity that has not yet existed during this execution. In this case, there will be no record of its old handle or GUID, so we need to reserve a handle value for it instead. Whenever the entity is constructed for the first time on this execution, it will assume that reserved handle value, and everything will work as usual. This solves our second problem case.

GunArc 2015-03-27 23-54-40-788

I’m still not quite at the point where the entire game state can be saved and restored exactly as it was, but this gets me one step closer to that goal and also validates some of my earlier assumptions about how development would go as I continue building new content.