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.
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.
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.
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.
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.
Comments
Post a Comment