Guide to the Godot game engine/Optimisation

Games run on machines that have a finite amount of resources and CPU power. There is only so much a phone or computer can do before your game starts to lag. There are good ways to fix this though.

Less is more

edit

Try to reduce how much code runs. The best way to increase your game's FPS is to stop code from running that does not need to run. For e.g, you don't need to calculate collision of an object twice per frame, or if you never get to collide with that object in the first place. For often updated objects, try delaying the update for the next frame, and using the most recent update attempt.

Minimise math

edit

When the game makes complex calculation every frame, the game gets quite laggy. Try storing the calculations in a variable before using it. Calls to get_node() also cause a little lag, so try to cache those in a variable as well.

Avoid this:

var direction

func _process(delta):
  var turret = $Turret
  direction = Vector2.UP.rotated(turret.rotation)

Prefer this:

var direction
onready var turret = $Turret

func _process(delta):
  direction = Vector2.UP.rotated(turret.rotation)

The after code sample is 1% faster than the before. It may not sound like much, but doing this optimisation to all your code adds up. Larger functions with many calls to get_node() or get_parent() makes more lag than caching it to a variable beforehand.

There is no need to do more math than necessary. Cache the results of calculations that stay the same before loops.

This is slow:

var alloy_strength = 1.0
var alloy_thickness = 5.0
var alloy_layers = 15.0

for robot in army:
  robot.armour = pow(alloy_strength * alloy_thickness, alloy_layers) + robot.native_armour

This is faster:

var alloy_strength = 1.0
var alloy_thickness = 5.0
var alloy_layers = 15.0

# Here, we're calculating the base armour rating of robots outside of the loop
var base_armour = pow(alloy_strength * alloy_thickness, alloy_layers)

for robot in army:
  robot.armour = base_armour + robot.native_armour

Iterate over a 1D array over an Array of Arrays

edit

Accessing a small Array only once or twice is negligible. But for larger Arrays that are accessed often, the performance difference adds up.

# 0.115443 seconds
for x in 1000:
  for y in 1000:
    var element = my_array[y][x]
      #...

# 0.108107 seconds
for x in 1000:
  for y in 1000:
    var element = my_array[y * 1000 + x]
      #...

# 0.062938 seconds, about 45% faster than the first example
for i in 1000000:
  var element = my_array[i]
    #...

By using Godot's native iterator instead of making your own, you get a larger performance boost:

# 0.048952 seconds, almost 60% faster than the first example
for x in 1000:
  for element in my_array[x]:
    #...

# 0.047986 seconds
for element in my_array:
  #...

You may not always be able to do this, but it is worth keeping in mind. The main things to takeaway from this when working with arrays are:

  • 1D Arrays are faster than multi-dimensional Arrays
  • Single loops are faster than nested loops
  • Accessing an array element with the iterator for element in array is faster than using indices, e.g. for i in array.size().

Remove items from the back of an Array

edit

Whenever you remove or add an item from an array, it needs to re-index every item after it, which means adding/removing an item from the beginning of an array re-indexes more items than when adding/removing an item from the end of an array.

Favor pop_back() and push_back() or append() when removing or adding elements to an array. Avoid pop_front() and push_front().

Use the right data type for the job

edit

These are three rules of thumb to help you decide if you should use an Array or a Dictionary.

  • If you want to remove elements from a collection at any location and at any time, use a Dictionary.
  • If you will access elements from a collection by key randomly, use a Dictionary.
  • If you lay your elements in order, use an Array.

Don't use print()

edit

The print() function is slow. Try to not use it when you export the game. Instead make your own logging function and autoload it. An example:

extends Node
onready var log = File.new()

func _ready():
  log.open("res://log.txt", File.WRITE)

func _exit_tree():
  log.close()

func log_message(message, source):
  var current_time = OS.get_time()
  log.write_line("%s (%s:%s:%s): %s" % [source, current_time.hour, current_time.minute, current_time.second, message])


The gains of using static typing are very high, between 50% to 150% gains. The reasoning of this, in short, is that Godot is putting less effort into finding out what type everything is to run the correct logic.

Use get_parent().get_parent(), not get_node('../../')

edit

If trying to get a node's grandparent (or an upper node), don't use get_node('../../'), since it is 37% slower than just using get_parent().get_parent(). It looks more messy, sure, but if you need faster code, this is how you will want to do it.

Use the latest stable version

edit

Newer stable Godot versions have more optimizations and tend to be more performant than older ones.



Guide to the Godot game engine

Getting started [edit]
Installation
What is a node?
Programming
Resources and importing
Signals and methods
Your first game
Making it work
Debugging
Input
Physics
Saving and loading
Multiplayer
Making it look good
UI skinning
Animation
Advanced help
Servers (singletons)
Platform specific
Optimisation
Encryption
Exporting
Plugins
Miscellaneous
Helpful links
Authors and contributors
Print version


<-- previous back to top next -->