Skip to main content

Design like a programmer, part 2: copy-pasting the road to disaster

One condition to break it all

Let’s assume we’re working on a quest. Throughout it, we trigger a gameplay event whenever a certain condition is met. To make it less abstract, let’s say we’re registering an in-game clue as found whenever the player character is 1 meter away from it. Since the scene investigation is one of the core gameplay loop mechanics, we do this a lot, in many different places.
After many months of work, we ask first playtesters for feedback, and the most common point raised in the surveys is that finding clues just doesn’t feel right. The required distance between the PC and a clue is too short, forcing players to squeeze into some weird nooks and crannies, exposing a multitude of locomotion issues that would be too costly to fix at this point. The decision is made to increase the clue-registering distance, because surely it’s simpler and cheaper to make small numerical tweaks, than it is to rework all the environments or to fix the animations system that’s holding on a duct tape already. In short, your typical gamedev scenario.
Suddenly, we realise that maybe having that one distance condition node copy-pasted all over the place might not have been such a good idea after all. We find ourselves forced to plow through our quest structures and manually change each of those condition nodes. Since we’re doing this manually and there are hundreds of them scattered around, it’s very likely the first pass we do won’t be 100% complete. We’ll either have to do at least 2 passes, or have QA bug us all unchanged and/or incorrect instances after the first pass.
Now, there could be tools that could make this work easier for us, or tools programmers in our team that could write such a tool/functionality into the toolset we use for our project. However, if our condition was actually a set of nodes interconnected to meet a more complex set of requirements, the tools can only get us so far. The design seems to be fundamentally flawed.
There are many steps we could have taken to avoid the situation. Steps, that would have given us the same results and ensure the maintainability, both at the same time.

Using configuration files

Any value that’s referenced in many different places can be saved into a configuration file. It’s very simple for a programmer to build a framework that allows designers to alter some data directly in an .xml, .json or any other format of choice, including those created specifically for the needs of our project. If the distance variable from our example would have been saved in such a configuration file, all we’d have to do to meet the new requirements would have been to open the config and change the float value. Done.
Additionally, if our engine needs to recompile the quest structure into some kind of binary after every alteration, having some variables kept in a config file reduces the iteration time considerably. Since modifying a config file does not modify the quest structure itself, there is no need for constant recompilation while we’re iterating on the values to find the sweet spot.
A performance cost of such a solution is virtually nought, as most usually the config files are loaded into the memory at startup and serve as a lookup throughout the runtime of the application. The most common config formats are lightweight enough to not even be considered a memory cost.

Using node references

Another effective and maintainable solution could have been to keep our conditional logic inside a separate node, and then reference this node in every place where the condition is used. The big advantage of this approach is that it lends itself well to more complex logical operations. If our globally used condition comprises of multiple nodes and connections, it would have been very difficult, if not impossible, to save it to a config file in an intelligible form. Thus, I would consider this approach to be the solution of choice in such circumstances.
It’s worth noting, though, that we again pay the recompilation cost again whenever our complex condition node is altered. If our engine does recompile the quest structure on each edition, there might other solutions that could at least reduce the frequency with which the recompilation happens. I strongly advise everyone to try and find those solutions, and the sooner it’s done the better. In the end, recognizing those potential problems and finding the right, preferably systemic solutions to address them, is what a pre-production is for.

Copy-pasting is not a bad practice

Before I finish today’s article, I’d like to mention that not all copy-pasting is evil. Copying stuff around is not necessarily a bad practice and can save some time if used wisely. It’s only when it’s done without the proper insight when it can lead to severe issues down the line.
In the table below I’ve listed some questions we should ask ourselves before copy-pasting. The colour indicates the potential risk of the operation, with red being a NO-NO, yellow meaning ‘proceed with caution’ and green indicating a safe operation. If we cannot answer any of these questions ourselves, we should consult somebody who can before a puppy dies.
Question Yes No
Is the asset referenced in many places?
Does the asset contain a unique identifier?
Is the asset a piece of a bigger set of assets?
Do I copy this asset a lot?
Is it possible that there is a loose reference to this asset (like a name reference)?
Is the asset generated or maintained by automated processes?
Turned out I never got to use the green color in the table, and that’s the bottom line I’d like to close today’s article with.
Next time we’ll take a look at how to keep our designs open for extension, but closed for external modification. See you then!

Comments

Popular posts from this blog

Array property customization in Unreal Engine 4

With the drive towards as much data-driven gameplay as possible, there comes a need for easy to edit and nice to interact with data assets that can accommodate all that data. While Unreal Engine 4’s UI framework allows us to display a wide range of data structures, its default handling of nested properties can quickly result in deeply nested structures that need to be expanded in order to edit them. This can really hurt productivity in some scenarios. Fortunately, we have the ability to fully customize how our data is laid out. While there are nice tutorials all around the web that explain how to customize our custom classes and structs, I’ve not been able to find an article that would explain how one would go about customizing how collection types display their data. In this article I will describe how to customize the display of an array property in UE4. I will follow it with the one for customizing maps display in the near future. Defining the issue I intend to explain the pr...

Float precision and Time in Unity

Float precision and Time in Unity Recently, I was tasked with addressing an interesting issue that occured in a Unity game. After the game run for about 2 days, it virtually stopped streaming in map assets during world locomotion. Assets were streamed in with a huge delay (up to x-teen seconds) and it simply wouldn’t do. After finding out what caused this strange bug, I thought it would make an interesting article to share with you. Both the problem, and the process of finding its origin were quite an interesting experience. Debugging Since the problem only occurred after at least 2 days of ‘soaking’, I knew time is going to play a role here. I started investigating the issue by looking at the custom streaming code used in the game. It consisted of a bunch of asset loading and unloading functions, called every tick. At the start of each tick, before calling the functions, the code would cache the current Time.realtimeSinceStartup, which is a timer managed by the Unity engine tha...

Designing a flexible radial menu control

It’s been a while since the last article I published. I was busy wrapping up a project I’m working on currently, which, as most game development projects do, turned out to require a little more attention than was planned. The project I was working on was my first porting gig. As can be expected, this exposed me to a bunch of problems I never considered before. One of the tasks I was assigned was writing up a radial menu control, and that’s what I would like to write about today. Radial menu At its core, a radial menu is nothing more than a UI control that consists of a circular items layout and an optional direction indicator. Seems simple enough, right? Well, as it often is with simple concepts, there is a ton of things that go into writing a radial control that is responsive, easy to use and flexible enough. Initial guidelines Let’s start by quickly going over some of the initial discussions I had with people who had done this sort of work before. It’s very important to go thr...