Bite-Sized Godot: Callbacks and function references with FuncRef


Sample project

Unfortunately, functions in Godot are not first class objects, meaning we can’t just pass them around like any other variable like we can in languages like Javascript:

// Sample function
function callbackFunction() {
    console.log('Hello!');
}

// Takes any function and calls it
function myFunction(callback)
{
    callback();
}

// This variable now points to the function and can be used to call callbackFunction like we're calling it directly.
const ref = callbackFunction;
// Pass it to a function as an argument
myFunction(ref);
// Call it directly
ref();

In the above example, since functions are first-class objects, they can be passed around and called via variables as if they are being used directly. This usage is pretty common in a language like Javascript since you’re often dealing with a lot of asynchronous web requests on a website. First-class functions make it easy to request some data and let the program continue on its way, jumping to the callback function once it has a response. This can also apply to a game development context, either directly with requesting data from a game server or maybe running an expensive AI calculation in the background of your game while it continues to run, or by writing a generic object that can be used in multiple contexts, such as having a Spell object and feeding it different functions to call when cast based on what specific type of spell it is. The spell example might look something like this:

// Generic spell class to handle different cast effects
class Spell {
	constructor(castFunction) {
		this.castFunction = castFunction;
	}

	cast() {
		this.castFunction();
	}
}

// Create some spells, assuming the cast functions
// are implemented elsewhere in the codebase
const fireSpell = new Spell(castFire);
const lightningSpell = new Spell(castLightning);

// Will call castFire()
fireSpell.cast();

In these examples, writing some generic code and getting a specific output makes things easier. And so the lack of first-class functions in GDScript is kind of a bummer, but we can get most of the functionality with the use of FuncRefs.

FuncRefs

The Godot documentation sums up FuncRefs nicely:

In GDScript, functions are not first-class objects. This means it is impossible to store them directly as variables, return them from another function, or pass them as arguments. However, by creating a FuncRef using the @GDScript.funcref function, a reference to a function in a given object can be created, passed around and called.

So a FuncRef is an object that allows for equivalent functionality of a first-class function, and creating it is quite simple:

func my_func():
    print("Hello!")

var ref = funcref(self, "my_func")
ref.call_func()

Just call funcref and pass two arguments: 1) The object that owns the function you want a reference to 1) The name of the function as a string

To call the funcref, you can type ref.call_func(). If you have arguments that need to be passed into the function, you can pass them just like normal:

func my_func(msg):
    print(msg)

var ref = funcref(self, "my_func")
ref.call_func("Greetings!")

An example

As a brief example, let’s build upon the above example for a spell-casting system and create a spell-crafting system for today’s sample project, where the player can craft a spell from multiple elements and then cast it, getting the combined effects of their choices. The lone sample scene is setup has a series of CheckButtons to toggle each element on and off, a button to create and cast the spell, and some particle emitters representing each element so there is some visual feedback.

Let’s first look at the Spell class, which takes an array of cast effects as funcrefs and then calls them when it is cast:

class_name Spell
extends Node

var cast_effects = []

func _init(effects: Array) -> void:
	cast_effects = effects

func cast() -> void:
	for effect in cast_effects:
		effect.call_func()

These functions could be stored anywhere, and they most likely would live within their own objects in an actual game, but to keep things simple for this example, the code for the main scene defines the cast functions, which just turn their associated particle emitters on.

extends Node2D

var spell: Spell

func _ready() -> void:
	$controls/HBoxContainer/cast.connect("pressed", self, "cast_spell")

func create_spell() -> void:
	var elements = []
	
	# Add the cast function for each element if it's selected
	if $controls/HBoxContainer/fire_toggle.pressed:
		elements.append(funcref(self, 'cast_fire'))
	if $controls/HBoxContainer/water_toggle.pressed:
		elements.append(funcref(self, 'cast_water'))
	if $controls/HBoxContainer/earth_toggle.pressed:
		elements.append(funcref(self, 'cast_earth'))

	# The spell will call all cast effects it is given
	spell = Spell.new(elements)

func cast_spell() -> void:
	create_spell()
	spell.cast()

func cast_fire() -> void:
	$fire_particles.emitting = true

func cast_earth() -> void:
	$earth_particles.emitting = true

func cast_water() -> void:
	$water_particles.emitting = true

When cast_spell() is called, we create the spell by looking at which toggles are selected and passing an array of casting functions to our new spell before calling its cast() function, which will call each funcref that was used to create the spell.

Running the code, we can play around with the (quite small) selection of elements and create a custom spell with different effects based on what we select, all from an extremely generic Spell class. Obviously this example is quite minimal, but hopefully it helps demonstrate how you can use function references to build a more flexible codebase by writing a generic piece of code that can take on specialized functionality.

Conclusion

And that’s all there is to say about FuncRefs. If you’re looking for a way to implement callback functions or pass function references around, FuncRef is what you need. Check out the Godot documentation if you want to learn more.