> voidtick
Menu
  • #Godot
  • #GDScript
  • #Architecture

Global context provider

Problem

Sometimes you need a node to be accessible from any part of the code. The most straightforward way to do this is using autoloads. But they have a couple of drawbacks:

  • You cannot add export variables to autoload scripts to do some custom setup.
  • To bypass this problem, you can add a scene to autoloads and do all the setup in it, but you lose typing (I mean you cannot dynamically see vars and methods when writing code).
  • If you need the same global node but with a different setup, you just add more and more items to autoloads. As for me, I prefer to keep as few autoloads as possible.
  • And sometimes you just need a node to be accessible anywhere in the tree just for a particular scene, without polluting the global namespace.

Solution

I picked up this approach from the JavaScript VUE framework. The main idea is creating a single context object that holds other registered nodes in a simple dictionary.

So the first step is to create this global autoload node:

# context_provider.gd (autoload)
class_name ContextProvider extends Node

var items: Dictionary = {}

func provide(key: StringName) -> Node:
	return items.get(key, null)

func reset(key: String):
	items[key] = null

func register(key: StringName, obj: Node):
	assert(provide(key) == null, "Trying to register a node in context with an existing key '%s'" % key)
	items[key] = obj
	obj.tree_exited.connect(func (): reset(key))

It just accepts a string key and the object itself to register. Then you can retrieve it anywhere in code by just a string key.

Godot autoload settings: the Context script res://src/global/context.gd is enabled

To make registration even simpler, let’s create a small node class. It will be attached as a direct child to the required node and will automatically register it in the context.

# context_injector.gd
class_name ContextInjector extends Node

@export var token: StringName

func _ready() -> void:
	Context.register(token, get_parent())

And that’s it!

Now you can easily expose every node in a scene to be accessible anywhere in the tree.

For example, we created some ObjectsFactory in our battle scene, which is responsible for spawning objects and doing other stuff. We don’t need it in menus or other scenes. So we create this factory in the battle scene and add a ContextInjector node as a direct child. Also, give it the key “obj_factory”.

Scene tree: a ContextInjector node as a direct child of ObjectsFactory

The ContextInjector node in the inspector with its Token property set to obj_factory

To access this node we use the following code:

var o_factory:= Context.provide(&"obj_factory") as ObjectsFactory

And if you need a different setup for this factory in another scene, you can just use the same steps and everything will work like a charm.

More Articles