Search code examples
godotgdscript

How to call method in Godot 3.5.1?


I'm currently making an auto chess game using Godot, specifically the game TFT. My idea is to randomize a list of items (Common, Uncommon, Rare, Epic, Legendary) in an array (you can check this video tutorial that I followed https://www.youtube.com/watch?v=IAhBZLlz5wY). The problem is that I want to randomize another list of items that contain all of the in-game champions (for example, If it's common, it could be a butterfly, earthworm, etc. If it's uncommon than it could be a dog, cat, etc). My idea is to call a method using the given word, for example:

export(Array, Resource) var items_array: Array = []

func _ready():
 randomize()

func _on_RollButton_pressed():

 var Portrait1 = RandomPicker.new().pick_random_item(items_array)

I would like to explain it from start to finish, but the tutorial video I mentioned above explained it very clearly. For a little bit of summarize, we have an array which contains a list of tree files

enter image description here

As I said, in the array we have Common, Uncommon, Rare, Epic, Legendary. I also add other items as champions in this case (IconBlack, IconBlue, IconPink), which I will talk about later.

The reason why I used tres, or you can say why the person in the video above uses tres, is to what I understand, being able to assign value (Common, Uncommon, etc), Pick chance (for example, the pick chance rate is 75% if the item is Common, 50% if the item is Uncommon, etc) and Pickable (to check if the item is pickable. I will talk about this in detail later) to the items. Here is the code I used to do it, which contains in RandomItem.gd:

extends Resource
class_name RandomItem

export(String) var VALUE 
# You can use whichever type you want, it will not affect the algorithm at all
#export(int) var VALUE
#export(String) var VALUE
#export(PackedScene) var VALUE
#export(Resource) var VALUE

export(int) var PICK_CHANCE: int = 1
export(bool) var can_be_picked: bool = true
export(bool) var common: bool = false

Here is what it looks like:

enter image description here

So what does RandomPicker do, well let us take a look at the code first. We have another gdscript named RandomPicker:

extends Node
class_name RandomPicker

export(Array, Resource) var items_list: Array = []

func pick_random_item(items_array: Array = items_list):
 var chosen_value = null
 if items_array.size() > 0:
    # 1. Calculate the overall chance
    var overall_chance: int = 0
    for item in items_array:
        if item.can_be_picked:
            overall_chance += item.PICK_CHANCE
    
    # 2. Generate a random number
    var random_number = randi() % overall_chance
    
    # 3. Pick a random item
    var offset: int = 0
    for item in items_array:
        if item.can_be_picked:
            if random_number < item.PICK_CHANCE + offset:
                chosen_value = item.VALUE
                break
            else:
                offset += item.PICK_CHANCE
 # 4. Return the value
 return chosen_value

func pick_random_Common(items_array: Array = items_list):
 var chosen_value = null
 if items_array.size() > 0:
    # 1. Calculate the overall chance
    var overall_chance: int = 0
    for item in items_array:
        if item.common:
            overall_chance += item.PICK_CHANCE
    
    # 2. Generate a random number
    var random_number = randi() % overall_chance
    
    # 3. Pick a random item
    var offset: int = 0
    for item in items_array:
        if item.common:
            if random_number < item.PICK_CHANCE + offset:
                chosen_value = item.VALUE
                break
            else:
                offset += item.PICK_CHANCE
 # 4. Return the value
 return chosen_value

Again, I would recommend watching the video if you may not able to understand it. As you can see, on the pick_random_item function on the step 3, when I try pick a random item, there is a line of code that checks if the item is Pickable:

if item.can_be_picked:

Similar to that, I also created another function which is called pick_random_Common, which is written exactly like the previous function, except a line of code that checks if the item is Common:

if item.commmon:

Remember the tres files with value Icon Black, Icon Blue, Icon Pink in the array? Those items are unpickable, that is because when I generate a random item with Common value, I want to generate it again with another function in order to generate a common champion. The common champions in this case are Icon Black, Icon Blue, Icon Pink. Here is a closer look:

enter image description here

The problem is when I try to call the method. I known that the line of code RandomPicker.new().pick_random_item(items_array) will generate items like common, uncommon, etc, so I try to do a similar thing with the function pick_random_Common. My idea is that if Portrait1 is common, then I would call the method like this:

call("RandomPicker.new().pick_random_" + Portrait1 + "(items_array)")

And it didn't work. It works with the function in the script but is unable to work with the method RandomPicker.new().pick_random_Common(items_array). I guess I have misunderstood something here, so I would be deeply appreciative if you could give me some hints related to the problem. My programming skill is still under average so pls ask if there is anything that you haven't understand about related to the code, and I would try to answer them as fast as possible. Once again, thank you for you help and your effort (and also sorry for my broken English).


Solution

  • Calling a method directly

    Since you can call methods directly, and you know which are the methods, you can do this:

    var result
    var random_picker := RandomPicker.new()
    match Portrait1:
        "common":
            result = random_picker.pick_random_common(items_array)
        _:
            result = random_picker.pick_random_item(items_array)
    

    Call

    The call method calls a method on the object on which you call it.

    And your object does not have a method called "RandomPicker.new().pick_random_" + Portrait1 + "(items_array)".


    What you would do with call is this:

    var random_picker := RandomPicker.new()
    var result = random_picker.call("pick_random_" + Portrait1, items_array)
    

    Here the object is the random_picker of type RandomPicker, and we are trying to call a method on it called "pick_random_" + Portrait1 which presumably exists.

    The second argument of the call method will be passed as argument of the method being called.


    Coming back to the idea before, you might have a dictionary that maps the variable value to the method name. Something like this:

    var random_picker := RandomPicker.new()
    var methods := {
        "common": "pick_random_common"
    }
    var result = random_picker.call(
        methods.get(Portrait1, "pick_random_item"),
        items_array
    )
    

    FuncRef

    In fact, you could create function references (FuncRefs):

    var random_picker := RandomPicker.new()
    var methods := {
        "common": funcref(random_picker, "pick_random_common")
    }
    var result = methods.get(
        Portrait1,
        funcref(random_picker, "pick_random_item")
    ).call(items_array)
    

    Expression

    What you were doing looks similar to making an Expression, which would be something like this:

    var expression := Expression.new()
    expression.parse(
        "RandomPicker.new().pick_random_" + Portrait1 + "(items_array)",
        PoolStringArray(["items_array"])
    )
    var result = expression.execute([items_array])
    

    The second argument of parse is an PoolStringArray with the names of the variables. In this case we should need "items_array". Then when calling execute I need to tell it the value of said variable.


    Script

    Since we are on this, for completeness sake, I'll also mention that it is possible to create an script in runtime and execute it.

    var script:= GDScript.new()
    
    script.source_code = """
    
    func method(items_array):
        return RandomPicker.new().pick_random_{name}(items_array)
    """.format({"name": Portrait1})
    
    script.reload()
    var instance := script.new()
    var result = instance.method(items_array)
    

    Yes, as you would imagine, that is overkill for this case.

    You might also do this in a tool script, and then save the script, and you have unlocked code generation. But that is beyond the question.