Skip to content

Helpful functions

Charles edited this page Jun 11, 2026 · 6 revisions

Introduction

Have you ever searched the Godot docs looking for a function and thinking "surely there has to be a function to..." only to not find what you're looking for? ProtoJam comes packed with a variety of commonly needed static functions to help fill that gap, including:

  • Graceful shutdown
  • Framerate independent lerps
  • Time formatting
  • and more...

Using CollectionUtils

The CollectionUtils class provides supplemental functions for Dictionary and Array.

filterd(dictionary, filter)

Returns a copy of a dictionary with only the filtered entries remaining similar to Array.filter. Returning true from the filter callable will retain the key/value pair it was called with.

var items: Dictionary[StringName, int] = {
	&"egg": 2,
	&"bullet": 20,
	&"herb": 0,
	&"key": -1,
}

# Remove egg items
var egg_filter: Callable = func(key: Variant, _value: Variant) -> bool:
	return &"egg" != key


# Prints { "bullet": 20, "herb": 0, "key": -1 }
print(CollectionUtils.filterd(items, egg_filter))

get_or_create(dictionary, key, callable)

Performs the same function as Dictionary.get_or_add(key, default) but defers to a callable to produce the default value. This is ideal for situations where the default value requires initialization beyond its construction or should not be constructed when not required.

var _npcs: Dictionary[String, NonPlayerCharacter] = {}

func _ready() -> void:
	CollectionUtils.get_or_add(_npcs, "blacksmith", _spawn_blacksmith) # Invokes _spawn_blacksmith and stores the result
	CollectionUtils.get_or_add(_npcs, "blacksmith", _spawn_blacksmith) # Returns the previously created blacksmith


func _spawn_blacksmith() -> NonPlayerCharacter:
	var npc: BlacksmithCharacter= BlacksmithCharacter.new()
	add_child(npc)
	return npc

remove_all(a, b)

Removes all entries from the first array that are also in the second array.

var a: Array[int] = [1, 2, 3, 4, 5]
var b: Array[int] = [2, 4]

# Prints [ 1, 3, 5 ]
print(CollectionUtils.remove_all(a, b))

Using NodeUtils

NodeUtils provides functions relating to or requiring a node to function.

connect_descendant_signal(node, type, signal_name, callable, flags)

This function serves as an alternative to the signal busses used by many Godot games. Rather than declaring signals on a shared object and allowing anything to connect and emit them, this function allows allows individual nodes to declare signals locally while a parent elsewhere in the tree searches and connects to them.

The typical implementation will have players, objectives, enemies, or other noteworthy objects declare and emit their own signals as such:

player.gd

class_name Player
extends CharacterBody3D

signal killed()

# ...

func _on_killed() -> void:
	killed.emit()

Elsewhere (typically higher in the tree), a game state class would load the level containing these noteworthy objects, add them to the tree, and connect their signals:

game.gd

class_name Game
extends Node

func _load_level() -> void:
	# The level scene contains players, objectives, etc within it
	var level: Node = _LEVEL_SCENE.instantiate()
	add_child(level)
	
	# Find all child nodes of type "Player" and connect their "killed" signal to game's _lose_game function
	NodeUtils.connect_descendant_signal(self, "Player", "killed", _lose_game, CONNECT_ONE_SHOT)


func _lose_game() -> void:
	# ...

free_all_children(node)

This function simply executes Node.queue_free() on all children of the given node (including internal children).

focus_first_available(node)

To navigate game UIs using a gamepad or keyboard, at least one control node must be active. When no control nodes are active, the built-in UI navigation features of Godot will not work. While activity can be manually set, it's not uncommon for the active node to be freed, disabled, or otherwise changed in a way that makes it inactive thus breaking the UI navigation.

This function provides a convenient fallback to re-establish an active control by finding the first activate-able node and making it active. Users are highly recommended to call this function on the parent of a game UI every time a state change has occurred (ex changing screens).

get_tree()

There are rare circumstances where a class outside the scene tree needs to acquire the tree (for example, to quit the game). In these cases, this function can be used to get a reference to the scene tree.

Unlike Node.get_tree(), this function can be called from anywhere including resources, nodes not yet added to the tree, and any other class.

Caution

Accessing the scene tree from outside it does carry risk and can lead to unmanageable code. Use sparingly.

quit_gracefully(exit_code)

When most developers want to exit the game, they call SceneTree.quit(); however, this method is not always desirable as it stops executing the scene tree immediately and does provide any mechanism for nodes or other classes to perform final actions before the game closes (like saving the game). Furthermore, calling quit from a web export will typically crash the game.

As an alternative, NodeUtils.quit_gracefully(exit_code) propagates the NOTIFICATION_WM_CLOSE_REQUEST notification to all nodes in the tree allowing them to undertake any final operations before the game is exited; additionally, calling this function from a web export will have no effect so the game does not crash.

A typical implementation will have quit_gracefully called from a UI element while some other part of the game listens for NOTIFICATION_WM_CLOSE_REQUEST to perform cleanup as such:

main_menu.gd

class_name MainMenu
extends Node

@onready var _quit_button: Button = %QuitButton

func _ready() -> void:
	_quit_button.pressed.connect(NodeUtils.quit_gracefully)

main.gd

class_name Main
extends Node

func _notification(what: int) -> void:
	match what:
		NOTIFICATION_WM_CLOSE_REQUEST:
			SettingsManager.save_settings()

Using PhysicsUtils

PhysicsUtils provides functions for dealing with physics bodies.

is_on_floor(max_floor_angle, state)

This function provides similar functionality to CharacterBody3D.is_on_floor for RigidBody3D nodes.

class_name Ball
extends RigidBody3D

func _ready() -> void:
	# Required for PhysicsUtils.is_on_floor to work, the number of reported contacts
	# required depends on how many bodies you expect to be touching the body at any given time.
	contact_monitor = true
	max_contacts_reported = 16


func _integrate_forces(state: PhysicsDirectBodyState3D) -> void:
	print(PhysicsUtils.is_on_floor(deg_to_rad(45.0), state))

Important

This function is experimental and has not been thoroughly tested.

Users are strongly encouraged to use a CharacterBody3D if they need floor detection as it is almost always easier to make a CharacterBody3D behave like a RigidBody3D than it is to make a RigidBody3D behave like a CharacterBody3D.

Using RandomUtils

This class provides various utilities for dealing with random numbers.

pick_random(rng, array)

Performs the same function as Array.pick_random() but allows you to specify the RandomNumberGenerator used. This is helpful if your game requires determinism from a set random seed.

Using TimeUtils

The TimeUtils class provides various functions for working with units of time.

framerate_aware_lerp_weight(lambda, delta)

Lerp functions are commonly misused by passing delta time (or some product of it) as the weight parameter. While not intuitive, this actually creates a framerate-dependent lerp which will not be correct as the framerate changes. The correct way to make a framerate independent lerp requires an exponential curve.

This function makes the process simpler by hiding those details and simply returning a weight suitable for use with lerp.

func _physics_process(delta: float) -> void:
	# Wrong
	# velocity = velocity.lerp(Vector3.ZERO, delta)

	# Correct
	var rate: float = 1.0 # Adjust this to change the damping amount
	velocity = velocity.lerp(Vector3.ZERO, TimeUtils.framerate_aware_lerp_weight(rate, delta))

format_stopwatch(millis, full_precision)

Formats the given number of milliseconds into stopwatch format with either centisecond or millisecond precision.

var millis: int = 217195

# Prints 03:37:19
print(TimeUtils.format_stopwatch(millis))

# Prints 03:37:195
print(TimeUtils.format_stopwatch(millis, true))

minutes_to_millis(minutes)

Converts the given number of minutes to milliseconds.

seconds_to_millis(seconds)

Converts the given number of seconds to milliseconds.

minutes_to_centis(minutes)

Converts the given number of minutes to centiseconds.

seconds_to_centis(seconds)

Converts the given number of seconds to centiseconds.

millis_to_centis(millis)

Converts the given number of milliseconds to centiseconds.

millis_to_minutes(millis)

Converts the given number of milliseconds to minutes.

millis_to_seconds(millis)

Converts the given number of milliseconds to seconds.

centis_to_minutes(centis)

Converts the given number of centiseconds to minutes.

centis_to_seconds(centis)

Converts the given number of centiseconds to seconds.

Clone this wiki locally