Search code examples
staticabstract-classgodotgodot4

Godot 4 "Can't call non-static function 'aquire' in script." when trying to do "abstraction"


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?


Solution

  • Perhaps working with static Script (i.e. Scripts where everything is static) is viable for you. However, I suspect it is not your intention.

    Thus, you don't want an Array of Scripts.


    The Scripts 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 Scripts (created, for example, by calling the new method on the Script). In this case, that would be an Array of Powerups (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 Nodes...

    It might be useful for them to be Nodes. 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 Scripts 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.