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

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

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

My CD Projekt RED story, part 3

My CD Projekt RED story, part 3 We are now 2 years into my CD Projekt RED adventure and so far, it’s been mostly smooth sailing. I got promoted to a QA Analyst position, an equivalent of a specialist position for developers. I was earning around 2600zł or $650 per month, which would sometimes go as high as 3500zł with additional crunch pay. At this point I felt fairly rewarded for my efforts and could even start planning my wedding. I also received the first bonus for my participation in the creation of both Witcher 3 expansions. This amounted to roughly 13.000zł or $4250, which was an amount of money I had a hard time wrapping my head around. I still lived in a single room in an apartment I shared with other people, but at this point it was mostly my own choice. I’ve had my own wedding on the horizon so I needed every coin I could save for the occasion. Getting out of QA It was during that time that I decided I want to transition away from doing QA work. I approached the audio te