0 votes

I am trying to create a reference script that can be placed on a class and define extra parameters.

And it works! EXCEPT for when the reference script is edited. Then I get this error:

EnemyController.gd:100 - Invalid call. Nonexistent function 'get_script_export_list' in base 'Reference (paraenemy.gd)'.

Here's the code:

tool

var controller_script = null
var controller : Reference
func get_controller_script():
    if controller_script == null:
        controller_script = controller.new(base_direction, controller_variables)
    return controller_script

func _set(property, value):
    # Changing controller
    if property == "movement/controller":
        controller = value
        if controller != null:
            reset_controller_script()
        property_list_changed_notify() # update inspect
        return true
    # Controller variables
    if controller_script != null:
        for prop in get_controller_script().get_script_export_list():
            if prop.name == property:
                controller_variables[prop.name] = value
                get_controller_script().update_exports(controller_variables)
                property_list_changed_notify() # update inspect
                return true
    return false

When a controller (which is a script with its own class) is edited, the whole thing falls apart, but I have no idea why. Does the reference from controllerscript become invalid? Is there a way to check that? Even hasmethod says the method on the controller_script exists *only for the program to immediately claim it doesn't*.

I've also tried both weakref.getref and isinstance_valid. They turn up the same value before and after the editing.

Godot version 3.2.1
in Engine by (136 points)
edited by

1 Answer

0 votes

After some work, I have come to a simple answer:

set and _getpropertylist are used in saving a file. You cannot keep _set and _getproperty_list from being called after a file is saved.

This was my first attempt and object composition: The art of splitting scripts according to the work they do for better maintenance. But there are several SUPPORTED, NON-BUGGY ways of doing this same thing. Here is a list for those who come after me who want to do some beautiful object composition:

  • Node Composition

Yeah, so, Godot has nodes containing nodes. Nothing beats that. A "parent" can consider all children as accessible variables. It's guaranteed that those nodes will exist and can be reached from a consistent path. If you need to split functionality (such as when a Mario hero needs to split movement logic from damage and HP logic), this is what I consider the best way:

RootNode (Script: enemyController)
  -> Health (Script: playerHealth)

The rootnode can call damage() on the $Health when needed.

This also fixes the "Export properties" problem above, as each node can specify its own export properties which can be accessed in the editor.

  • Inheritance

Subclassing! Useful when you want to extend existing behavior with slight alterations. For instance, my problem was with a movement method that needed the slight addition of a frame() function. This is different depending on the enemy type. Goombas, Koopas, and Bowser all move different ways, even though basic things like dying, freezing and respawning are the same for all of them. Since an enemy freezing and an enemy moving belong to the same type of script, and depend on the same variables, it makes no sense to split them into two nodes. Instead, I solved the problem using a template method that gets overriden!

Superclass:

extends KinematicBody2D

func _physics_process(delta):
    self.frame(sprite, delta)

func frame(sprite : Sprite, delta):
    pass

Subclass:

extends "res://enemy/Scripts/EnemyController.gd"

func frame(sprite : Sprite, delta):
    # Moving functionality
  • Using a script variable

This is what I was doing above, and it IS useful in some cases.

This option is useful if the script being accessed changes during RUNTIME. The movement logic for enemies and the health logic for a player do not change after the game starts. This is why giving them nodes to attach to is safe and useful. It also allows for editting export variables! BUT if a script is swapped out with others on the fly, you're not going to be specifying export variables for them, are you?

Example: A Mario hero needs to have a script for shooting fireflowers. It has a presspowerbutton function. BUT that script needs to get swapped out with a tanooki power script. In this case, having your script contain an "export(Resource) var power" variable and assigning instantiated classes on the fly is the best solution.

onready var body : KinematicBody2D = get_node("..")
export(Resource) var defaultPower = preload("res://items/fireFlowerPower.gd")
onready var power = defaultPower.new(body)

func change_to_tanooki():
    var script = preload("res://items/tanookiPower.gd")
    power = script.new(body)

This does not have problems when saving the scripts because the scripts are not depended on or used until runtime. That is when defaultPower is instantiated. In cases like this, the scripts MAY use variables, but they all use the same set, like how the Power scripts would probably use the player's rigidbody.

by (136 points)
Welcome to Godot Engine Q&A, where you can ask questions and receive answers from other members of the community.

Please make sure to read How to use this Q&A? before posting your first questions.
Social login is currently unavailable. If you've previously logged in with a Facebook or GitHub account, use the I forgot my password link in the login box to set a password for your account. If you still can't access your account, send an email to webmaster@godotengine.org with your username.

Categories