Omar Bazzi Game Dev
Defensive Programming in Video Games

Defensive Programming in Video Games

tips programming gamedev

“Defensive programming” is nothing more than a way of designing software in such a way that as many unforeseen circumstances as possible are taken into account, allowing the software to behave correctly and the program to never crash.

However, here we already start with the problems because what is “behaving correctly” in the context of video games?

Before going down the rabbit hole, a brief disclaimer: I’m not going to include exception handling (some put it as a separate form of defensive programming, others include it as a whole) simply because in video games we don’t use those control structures, basically for a matter of performance (all those structures are black holes for resources) and we must never lose sight of the fact that in this area it’s not enough to just do it well… we must do it with the best possible performance. I’m also not going to refer to the particular case of defensive programming in the context of security in multiplayer games (because it gives rise to a separate topic and because the underlying concept I want to discuss is totally different).

So we return to what is “behaving correctly”? Let’s see 2 examples with code (in GDScript)

1st example:

#Enemy script
...
var target: Player
...
func _process(delta: float) -> void:
look_at(target.global_position)

2nd example:

#Player script
...
export var projectile_class: Projectile = null
...
func shoot() -> void:
var new_projectile:Projectile = projectile_class.instance()
add_child(new_projectile)

In the first script we have an enemy that constantly rotates (frame by frame) towards the player (target). In the second script we have a method of the player that instantiates an object of the Projectile class (the one of the projectile) stores it in a variable and adds it to the game world (add_child).

It’s clear that both scripts have ways of failing, unexpectedly, and the program (game) stopping with an error.

  • In the first script: if the player is destroyed (by losing a life, for example) the “target” reference will be null until it respawns again. That would give an error (null reference).

  • In the second script: if for some reason someone forgot to assign the value to “projectile_class” from the editor (for that, the export, the equivalent of a SerializeField in Unity or a TSubclassOf with a UPROPERTY in Unreal) that member variable will be null and, again, we will have an error.


What is the solution? I know, the first thing you are thinking is: the famous and beloved “guard clauses”. Which are nothing more than a precondition check (they work very well with early return too).

Let’s try this: 1st example:

#Enemy script
...
func _process(delta: float) -> void:
if target:
look_at(target.global_position)

2nd example:

#Player script
...
func shoot() -> void:
if projectile_class:
var new_projectile:Projectile = projectile_class.instance()
add_child(new_projectile)

Perfect, if target is null (for example when the player is destroyed) the enemy will stop executing the code that would cause an error. Likewise, if someone forgot to set a value to projectile_class the player will not execute the portion of code that causes an error. Ready then? Does the game behave “correctly” now? And no… it really doesn’t.

In the first case, yes, but in the second, what will happen when the person playing the game presses the shoot button? The game will not stop, but no shots will be fired. Imagine an FPS where you can’t shoot… now imagine the poor programmer trying to debug the error (which at first glance can range from the projectile colliding improperly with whoever instantiates it, to there being a binding problem with the inputs, and a bunch of “maybes” in between). This last situation is BAD defensive code, and you see it a lot.

Why do I dare to say it so assertively? Because by definition defensive code says “…behave correctly…” and it is not correct for the game to continue working under those circumstances, “if it has to break… let it break” I like to say. And if you’re not going to take my word for it, take Ari Arnbjörnsson’s (who knows a bit about this) and in this video he talks about “failing better”

I also like to say that if you got to the game’s launch with a bug like that, the least of your problems is the defensive code (the QA part of me comes out).


So? Do we just give in to not defending ourselves? Of course not, there are always ways to improve the quality of our code and our software in general. The problem is that there is no “silver bullet” because sometimes it depends on our tool, and understanding how it works. Here comes the part that many programmers don’t like: you have to read the documentation.

One of the main ways to defend yourself correctly from problems like the one I mentioned is with assertions. But these, of course, do not have the same syntax in all languages ​​(or in the API that an engine provides us). Just to give an example in the 3 most popular engines

Anyway, the spirit of the assertions is always the same, and with the documentation in hand we can understand exactly what we can expect when using them.


As the problematic example was with Godot code, let’s go back to it.

2nd example:

#Player script
...
func shoot() -> void:
if assert(projectile_class, "A projectile scene was not loaded"):
var new_projectile:Projectile = projectile_class.instance()
add_child(new_projectile)

If we read the documentation we will know that:

  • If the condition is false an error is generated.
  • If the above happens we will have a customized error message.
  • When we are running the game from the editor, it will pause, it will show us the error, but it allows us to continue testing the game (continue). If we are running a Debug version it will throw the error.
  • VERY IMPORTANT: asserts “cost” therefore, it is code that does NOT run in release versions. If we include code that has side effects (for example, an entire configuration depends on the success of the assertion) we can have undesired behaviors. This is very common with assertions (in Unreal it is similar) and that is why the solution is not to put “assert” everywhere. There is no need to be scared if in assertion checks we have “jerks” when testing the game in the editor, as I said, they are expensive and performance is lost, but in the release version that code is not executed.

So, faced with the same situation as before (someone forgot to load the projectile scene) this time we are not going to have a game where when pressing “shoot” the character simply will not shoot or the game will just break. We will have the possibility of the game stopping automatically, showing us a very clear and obvious error, but allowing us to continue the execution in case we were testing other things.

What is called “win-win”.


Conclusion: defensive programming is not a design as simple as saying “I put precondition checks everywhere”, a design effort must be made; However, the resulting code is of better quality and (largely) error-proof once in production/released. If we do not have any kind of defensive programming we are exposed, our game will run correctly until it finds an error and then we will simply be faced with the most undesirable situation: a critical error that stops everything.

We must look at what tools the language we are using provides us to improve our defensive programming. Assertions are very important, but they are not a golden hammer and we cannot use them indiscriminately.