Skip to content

Script Extensions

steen edited this page Feb 19, 2024 · 2 revisions

Overriding Virtual Functions in Script Extensions

Virtual functions are functions like _ready() or _init(). They are used to execute code at specific "events" or "notifications". You can learn more about them in the Godot Docs.

For modding, it is important to know that you can't override these functions in a script extension.

Here's how the _ready() function works:

  • It initializes the parent onready variable.
  • It executes the parent Base's _ready() function.
  • It initializes the child's onready variable.
  • It executes the child's _ready() function.

Related GitHub Issue

This is why it's not advisable to call the parent .ready() function in a script extension, as doing so would result in its execution being duplicated.

This behavior changed in Godot 4.x with the introduction of the super keyword.

Script inheritance

*Note: This info comes from the docs for the Delta-V mod loader for a full write up check this blog post.

In order to allow modifying game functions without copying the contents of the functions or entire scripts in mod code, this system makes use of inheritance for hooking. We exploit the fact that all Godot scripts are also classes and may extend other scripts in the same way how a class may extend another class; this allows mods to include script extensions, which extend some standard game script and selectively override some methods.

For example, this simple script extension changes the path that save files are loaded from in the game Brotato.

The location of this example script extension would be here:

res://mods-unpacked/Author-ModName/extensions/singletons/progress_data.gd

# Our base script is the original game script.
extends "res://singletons/progress_data.gd"

# This overrides the method with the same name, changing the value of its argument:
func load_game_file(path:String = SAVE_PATH)->void:
	var modded_path = path + "--modded.json"

	# Calling the base method will call the original game method:
	.load_game_file(modded_path)
        # Godot 4 uses the super() method instead of prefixing the original method with a .

	# Note that if the vanilla script returned something, we would do this instead:
	#return .load_game_file(modded_path)

To install it, call ModLoaderMod.install_script_extension from your mod's mod_main.gd, in _init:

extends Node

func _init():
	ModLoaderMod.install_script_extension('res://mods-unpacked/Author-ModName/extensions/singletons/progress_data.gd')

Inheritance chaining

To allow multiple mods to customize the same base script, installScriptExtension takes care to enable inheritance chaining. In practice, this is transparent to mods, and things should "just work".

If a script given to installScriptExtension extends from a game script which is already extended, then it will be extended from the last child script. For example, if we have two mods with a extends "res://main.gd" script, then the first mod's script will extend from the game's main, but after this "res://main.gd" will point at the extended script, so the second mod's script will extend from the first mod's script instead.