Skip to main content

Design like a programmer, part 1: Reducing the brain cycles

As many programmers like to joke, there is nothing worse than reading somebody else’s code. From my experience, debugging somebody else’s visual scripts or designs can be as daring an experience, if not worse at times. Many developers feel at home only within the confines of their own designs. Whenever we have to pick up unfinished work of any of our peers, or debug the bugs in their designs, it very often means we’ll have to work extremely hard to wrap our heads around those unknown environments. Lacking the mental shortcuts the original author had in mind more often than not leads to lots of frustration, experimentation and trial-and-error guesswork development.
If only each one of us would invest the time to make our work more readable to others… In programming, most of our daily work is dealing on somebody else’s codebase. Although this too can be quite a daring experience at times, it generally doesn’t have as much of a negative impact on our morale and sanity. In today’s article I will explore the tricks and rules that we leverage to make our daily struggles easier.

Reducing the brain cycles

Remember the last time you worked on this humongous visual script you’ve had to constantly drag around in the viewport to see what’s going on? Did you have to zoom out to reduce the number of drags?

When was the last time you’ve created such a wonderful piece of art? Maybe you even went to GDC and shared your incredible spaghetti with the community, saw their faces react to the beauty with a mix of amazement and bewilderment.

I’ve certainly authored some exquisite pieces of art myself many times, so I do understand the sentiment. However… Let’s agree to try and not do this ever again, shall we? Making sure our work is easy to read and digest by other people is one of the most undervalued investments in all game development. Whenever we create something that requires a constant viewport dragging to get an overview of, we significantly increase the effort our brains have to put in to make sense of it all. Collecting all the relevant data, putting it together and finally transposing it into some intelligible form we can reflect upon - all this work is redundant and only indicates a flaw in the design. 
Although it may seem silly at first to worry about such trifles, it really does make a difference when our work is easy to understand. It makes iteration faster, our peers happier when we call in sick, and generally saves tons of money for the company in the long run. How many times each one of us have been reading a long sentence in a book and had lost its meaning because of a brief loss of focus? It's quite an annoying experience when this happens, isn't it? And yet, re-reading the same thing over and over again is something we do a lot in gamedev and for some reason not many people see this as an issue. Thankfully, there is a number of tricks and rules that can be used to reduce the brain cycles our daily work requires.

Rule 1: Keep your operations short

One of the principles of clean code is to keep our functions short. It’s way easier to read a text if each individual sentence is not too long. Whenever we write a script, create a blueprint, or any other visual design, we should make it short. It will not only help the next person get their head around our creation, but will also make it easier for us to revisit this work in the future, when a refactor or an optimization pass is requested.

Rule 2: Have each operation do one thing only

Let’s assume we’ve been tasked with creating a script that prints text onto some entity’s display element. We created a PrintTextOnto node that takes the text and the entity to print it onto and called it a day, easy.

We come to the office the next day to find a new task - make it possible to print the text using a specific font. So we open our PrintTextOnto node, make it accept the font as an additional parameter and pushed the change in. Our PrintTextOnto node is a bit more complex now, but nothing any other pro couldn’t handle, right?

No, not really. Even this small addition will make it more difficult for others to make use of our node. Imagine another developer searching for a ‘print text’ functionality in your engine. They find our node and happily make use of it only to discover, that it requires them to also think about the font in which they intend to have the text printed. But the entity that they want to push the text onto might print the text using a predefined font. Or the text input that comes in is already formatted.
All this developer wanted to achieve was to push a text from point A to point B, but the moment they used our PrintTextOnto node they had to factor in an additional set of variables. Does the node work if the font field is left empty? If not, should a bogus font be passed on just to make sure the text doesn’t get broken? Or maybe the node will fall back to a default font, if the field is left empty, and if so, will the default font handle the special characters our text might contain?
Of course the font parameter could be optional, or there could be safelocks written in the node to make sure the input does not go bad in some corner cases. The thing is, none of this really matters, because the real problem lies in the fact, that our feature requires its consumers to think more than they really should. We sacrificed our feature's ease of use for an additional piece of functionality.
Generally speaking, any design that cannot be called without using an “and” word can be a problematic one. While long names can be difficult to avoid and are generally better than short, commented ones, an "and" in a name can be a strong indication that the feature serves more than one purpose. In the example of our PrintTextOnto node, once the font setting functionality is added the de facto name of the node becomes SetFontToTextAndPrintItOnto.
The better approach would have been to create a separate FormatText node. Anybody finding themselves in a need to apply formatting to the text they intend to print onto some entity could easily find the text-formatting node, plug it into their logic and happily move on the their next task.

Rule 3: Hide implementation details

The idea here is to make sure that whenever we look at our design, we can easily navigate through its levels of complexity. It’s a bit of a mouthful, so let’s consider a simple quest structure as an example. Let's take a moment to figure out what it does before continuing.

Although it’s a fairly basic setup, it nonetheless takes a moment to understand what it does.
This piece of visual scripting opens the door for the PC (player character) when either of the two conditions is met. We either open them when the PC acquires the key, or if the terminal placed somewhere in the world gets hacked. Whenever the first condition is met, the other is disabled as the way the player solved this particular problem will determine how the game plays out later on.
Now, let’s imagine this short logic is just a small fraction of what the quest structure contains. From my experience, a quest can contain hundreds or thousands of nodes. If this small door state handling implementation is inserted into that huge bundle of nodes and connections in its current form, every person debugging or expanding the quest will have to pass through those nodes. Each time they will have to figure out what it does and whether this is what they are looking for.
It’s actually very similar to having all our files dumped loose into our root HDD directory and hope this will not get out of control in the future as the number our files increases. Except in the case of a visual script we usually don’t have a powerful search engine we could leverage to find what we are looking for. Additionally, the way items are arranged in relation to each other matters.
When all specific implementation details are hidden away, browsing through complex structures becomes similar to reading a table of contents. Each section contains subsections, and each subsection can increase the level of its internal complexity, because the reader will have already read the name of the parent section at this point. Thus, the reader is eased into a specific train of thoughts, as opposed to them having to figure out what the right train of thoughts should be based on the context that's available to them.

Conclusions

Although most of the ideas mentioned above can be considered very basic, I very rarely see them used. I’ve spent hundreds of hours debugging humongous quest structures that had most of their internals clearly exposed, sometimes comparing two very similar sets of connections to see which is the one I’m looking for to find the bug I was hunting. Many of us have also tried to use a feature that we discovered to already exist in our engine, only to discover it doesn’t really do what it’s advertised to do, or does other things as well without communicating it to the user. Scripts tend to be full of functions that are way too long and strenuous to read, even though they could easily be split into smaller, more goal-oriented chunks. One thing we should try to incorporate into our daily workflow is to have our work reviewed by our peers in terms of readability. Functionality, regardless of its complexity, should never come at the cost of maintainability, which lack of readability inevitably leads to down the line.

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