A Word on Scalability: Scope and You
As promised in my earlier blog post on my audio project, this blog post will be about scalability in games programming, how it’s achieved, and its importance.
What is scalability?
The word scalability, according to Oxford Languages, means “the ability of a computing process to be used or produced in a range of capabilities”. Specifically, this refers to a piece of code’s ability to handle increasing or varying levels of demands, according to its utility.
For online games, this can, for instance, refer to properly managing server load (more players), while hardware scalability refers to designing the game to run properly on a wide range of devices and platforms.
The focus of this blog post, however, is on code scalability. In broad terms, code scalability refers to a series of principles a programmer should follow, in order to create code that works in a wide variety of scenarios/scopes, as well as code that will accomodate the increase of a game’s scope.
Scalability also has a wide range of benefits, including ease of adding features, simplified debugging, reduction of technical debt, and the ability of less technically inclined people to tweak the code (not that I’d ever let an artist touch MY code).
An example of scalability
QUICK NOTE: I will be using Unity-specific C# for these examples, but the principles apply for any OOP language.
Let’s take a look at some examples of good scalability and bad scalability.
Let’s say we’re building an enemy for an RPG. The simplest way to implement this enemy would be something like this:
public class Enemy
{
string enemyName = “Goblin”
int level = 5;
public void Attack()
{
Debug.Log(“Goblin attacks!”)
}
}
This code is very barebones, but implements an enemy class. However, it’s very, very bad and inflexible code - so bad, in fact, that the code-savvy among us are probably screaming furiously at the screen.
In short order, the issues with the code are:
Hard-coded, private values
We have an Enemy class, however both its name and level are hard-coded, which means it’s part of the code. Just like magic numbers, this is very bad for a few reason. The first reason is that we can’t manipulate it, and since we can’t manipulate it, we can’t test/balance it without going into the code and changing the value every time. It’s also hard for outsiders and non-coders to read, compared to a variable with a fitting name, such as “enemyName” or “enemyLevel”.
This leads to the second point - the code is hidden away due to its access level. Keeping as many variables private as possible is good, though in this case it’s bad. What if your designer wants the name to “Orc”? They’d have to go into the code to do it. With the “public” keyword, we could expose the variable in the editor, thus making it easier to change.
In order to make the class scalable, we would create a new class that inherits from the Enemy class, and we can then define our variables there instead. However, there’s still another issue.
Writing specific code in non-specific class
Did you notice that the code in the Attack-function specifically mentions “goblin”? This is an example of using specific code in non-specific classes. Say we have an Animal-class, and we’d be looking to define a bunch of different animals that inherit from this class. If we then have a function in the Animal-class called Bark, that would mean every class that inherits from Animals would be able to bark, which is great for dogs, but not so great for elephants.
The point I’m trying to make is that you want to define the overarching variables and functions in the base classes, and then add specific variables and functions in their children.
Using these principles, we would then have code that looks like this:
public class Enemy
{
public string enemyName;
public int level =;
public void Attack()
{
Debug.Log(enemyName + “ attacks!”)
}
}
and
public class Goblin : Enemy
{
public void GoblinChop()
{
insert code here
}
}
Using these principles, we have now designed a very simple system that can accomodate several different kinds of enemies. So let’s quickly go over some other principles that are good to follow:
Keep the code clean and commented
I cannot stress the importance of clean, commented code, especially if others have to read it. This can save hours of work. Strive to stick to naming conventions, use sensible variable- and function names, and make sure to comment why the code does what it does.
Reuse code wherever possible
Having to learn new functions takes time, so why not reuse the ones you already know? This is where generic programming and templating comes in handy, as we can define template functions which we can then define when we need to!
Strive for simplicity
Whenever you write new code, you should ask yourself the following question: “Is this too complex? Could this be done in a simpler way?” The answer is usually yes. While complex code is great to show off to others, simple code is easy to ready and, in many cases, will be easier to maintain. Therefore, stick to simplicity.
That concludes my blog post on scalability.