Hello and welcome to the last Blightmare dev blog of 2020! It’s been a very interesting year for everyone on the Plateau team, to say the very least. We ended up with a lot less time to work on the game over the year which threw our plans out of whack, but we’re still making progress each and every day. Today I would like to continue a thought from last week about how to integrate retained mode systems with immediate mode systems. It’s not revolutionary, but I’ve always thought that many of the descriptions of game development that can be found on the internet lack some of the small details which help build the full picture.
Last week I talked about a system that used a pool of resources which were freely allocated each frame. This works well when there are a relatively large number of instances of an object. For example, we use the same indicator to show the position of every object in a level. As you can imagine, there are many of these, and the whole point is that they’re all the same. This use case makes it perfect for a pooled solution. I was implementing the updated decoration editing tools recently and it became clear that a different approach was warranted. We have over 50 unique decoration assets right now, and we anticipate many more to come. Managing this many pools of objects is rather cumbersome, and can become somewhat inefficient if the pools aren’t purged properly to clean up unused resources. I’ll introduce a different approach to this problem today to show the tradeoffs.
The problem that we need to solve is previewing of placed decorations. Decorations can be static or animated, and they belong to one of several parallax layers. We assign a simple integer id to every decoration in the level so that we can easily determine which decoration we’re talking about at any given time. This id is guaranteed to be stable (doesn’t change) when decorations are added or removed, and it’s also guaranteed to be relatively dense (no big holes in the id space). We achieve these guarantees by using a vector of allocated ids that also contains a free list threaded through it. Perhaps this data structure deserves a post of its own at some point. Anyway, what this gives us is a straightforward way to index active decorations within reasonable bounds.
We use the id as the key in a dictionary that contains the preview instances. This makes it easy to do the common case of updating the preview configuration to match the editor’s state. The tricky parts now are when to add a new preview instance and when to remove one. These operations are implemented at the same time during the update process of our preview logic. The basic process is to start with an empty dictionary, then walk through all the active decorations and either move the existing preview into the new dictionary, or create a new preview and place it in the new dictionary. When we move the preview that means we delete the entry in the previous dictionary. This leaves us with only the old entries in the previous dictionary which we can safely dispose of.
This process works pretty well and can be made very efficient with careful reuse of containers to avoid allocating a lot of short-lived memory. However, it might seen odd that we are re-creating the create/destroy situations when we surely would know somewhere when these things are happening. It is entirely true that we could be processing the lifecycle events directly in the code that was triggering them which would save us some of the set processing. However, this means that everywhere that we want to create or destroy a decoration will need to be aware of the preview state. If you start to think about undo/redo or other advanced features, it may become somewhat more clear why we want to minimize the amount of information that each system actually needs to know. This solution is entirely designed to make the code easier to work with from the programmer’s point of view. Limiting the complexity in this way means that the amount of information that a programmer needs to be aware of to make a change is smaller. This in turn means that the changes can be made faster and with greater certainty. As long as we can afford the extra runtime cost, then this tradeoff is entirely worthwhile. If we find that a solution is running too slowly, we can then look to increase complexity in order to increase efficiency. This kind of tradeoff is very common in larger scale programming projects, and games are no exception.
This brings me to the end of the last post of 2020. We’re going to take the last week off for the holidays, but we’ll be back January 5th! On behalf of the entire team, I want to thank you for supporting the game by reading what we have to say here, it really means a lot to us. As we move on to 2021 we are as hopeful as anyone that things can begin going back to normal and we’re feeling optimistic that it’s a good year to release a game! Have a great holiday and we hope you stay safe!