For a metroidvania game, I'm trying to make a powerup system that accepts a script with specific functions to call when something happens. The base class ('powerup_class.gd') is just this:
extends Node
class_name Powerup
var player
func aquire():
pass
func in_air():
pass
func on_jump():
pass
func update(_delta):
pass
Then, to create a powerup, I do this:
extends Powerup
func aquire():
print("Aquired!")
To get it set up so the player has these scripts, I use the following code.
@export var powerups : Array[Script] = []
# in _ready() for testing
for i in powerups:
i.aquire()
# in _process()
for i in powerups:
i.update(delta)
Running this, it gives me errors like such:
E 0:00:01:0537 player.gd:85 @ _ready(): Can't call non-static function 'aquire' in script.
<C++ Error> Condition "!E->value->is_static()" is true. Returning: Variant()
<C++ Source> modules/gdscript/gdscript.cpp:868 @ callp()
<Stack Trace> player.gd:85 @ _ready()
Changing all of the functions to be static works, but then I can't change non-static variables, which could be a problem.
How would I go about fixing this so that functions can be called from the scripts? If the reccomended way to go about doing this is by making everything static that should be okay, but I feel this isn't a good way to do it. Am I getting the approach for godot abstraction all wrong?
Perhaps working with static Script
(i.e. Script
s where everything is static
) is viable for you. However, I suspect it is not your intention.
Thus, you don't want an Array
of Script
s.
The Script
s are classes (In Godot Script
is what other languages/platforms would call a metaclass).
Instead you would want an Array
of the instances of the Script
s (created, for example, by calling the new
method on the Script
). In this case, that would be an Array
of Powerup
s (Array[Powerup]
).
Currently a Powerup
is a Node
(i.e. the Powerup
class extends Node
). It is unclear to me if they need to be Node
s...
It might be useful for them to be Node
s. For example, if you add them to the scene tree (with add_child
) then Godot might call _process
on them, instead of you iterating over an array of them and calling update
for each one. However, this is not what you are doing.
An alternative would be to make the Powerup
a Resource
. Then you can create custom resource files, which you can drag from the filesystem to the inspector to add them to your Array[Powerup]
.
In general, when you have Resource
files (which include Script
s and scene files among others), you can drag them from the filesystem to the inspector.
The scene files when added in the inspector would be PackedScene
objects, which you can instantiate by calling instantiate
on them, which gives you a Node
object.
Yet, be aware that if you drag a file multiple times into your Array[Powerup]
you are getting the same Resource
multiple times.
Bear with me for a moment, as I find this hard to express: There is the thing, and there is the kind of thing... For example, if you have an inventory system (as in accounting), you might have a record for a model (with a manufacturer field) and a record for a product (with serial number, and model fields).
So, there is the power-up kind and there is the power-up. You can have a collectible object (which is not a power-up nor a power-up kind), that when the player collects it, it gives them a power-up. And you need to define which power-up kind the collectible will give. So you have the collectible export a variable of power-up kind type. But when the player collects it, you create a power-up from the power-up kind.
Now, your options for power-up kind are a Script
(from which you make instances by calling new
), a scene (i.e a PackedScene
, from which you create instances by calling instantiate
) or some custom Resource
that has either a a Script
or PackedScene
. And yes, these would exist as resource files in the filesystem, and you would be able to drag them into the exported variable of the collectible Node
in the inspector.
There would be code to instantiate the power-up from the power-up kind (which involves either new
or instantiate
), and also to add the new instance to the array of current power-ups that the player has.
You might also be interested in How to implement a composable character/skill system in the Godot 4.0 Game Engine? where I answer the general version of this approach. You might also be interested in now how do I go about making a skill bar and a tool bar? that has simple particular case, which despite being a different case as the one you have, comes form the same ideas.