Search code examples
godotgodot4

Collecting Coins using singletons in Godot 4.1.1


I am making a 3d endless runner game. but the problem lies in collecting the coin.
I want to display the coins that are collected by the player.

This is my label script:
enter image description here
This is my Globalscript:
enter image description here
This is my Coin script:
enter image description here

I was expecting that if i collected a coin. my coin display would display how many coins I have collected.

so far, my collision shapes and the coin detect each other as it shows "collected" on my output :
enter image description here

but in game, it does not display that I collected it:
enter image description here


Solution

  • For what I gather there are two "body_entered" signals:

    • Global_Coin (I'm taking it is an autoload) has a _on_body_entered signal defined, which passes no arguments:

      extends Node
      
      signal _on_body_entered
      

      Then in your Area3D you connect it to the _on_body_entered method.

      And it would be emitted by the Label of all things, from the _on_body_entered method which I believe is not called at all.

    • The Area3D has a body_entered that has a body:Node3D parameter.

      This signal would be emitted by the Area3D when some body enters it.

      And I'm guessing is connected to the _on_body_entered method by other means, which explains how the "collected" message shows up in output.

    Note: I'm confident that the body_entered signal of the Area3D is not connected to the _on_body_entered of the Label because that method has no parameters, and thus you would have an error if it were connected, given that the body_entered signal from Area3D passes an argument.


    Thus:

    1. Pick different names for different things. Otherwise you will end up confusing whoever reads your code, which most of the time is yourself (which I believe is the case here).

    2. Single responsibility principle: The job of the Label is to display text. Why is the Label responsible from emitting a signal (and of "body entered" of all things)? It should not be emitting a signal for this (that is not its responsibility). In fact, it makes more sense that it would be receiving it.


    The following is what makes sense:

    1. Make sure the body_entered signal of the Area3D is connected to the _on_body_entered method in the scrip attached to the same Area3D. I believe this is already the case, but I do not see the usual green icon that indicates the connection, thus I suggest to double check.

    2. Have the _on_body_entered method in the Area3D emit the _on_body_entered signal from Global_Coin. In fact, I'd suggest to change the name of the signal to something more meaningful, e.g. coin_collected.

    3. Connect from code the _on_body_entered signal from Global_Coin to the _on_body_entered method in the Label (currently you do this in the Area3D), so the Label can increment its count.

    In general the flow of data would be like this:

    • Godot detects the collision, and tells the Area3D
    • The Area3D emits its Area3D.body_entered signal.
    • The Area3D handles the Area3D.body_entered signal, and emits the Global_Coin._on_body_entered signal, and also queues itself from removal.
    • The Label handles the Global_Coin._on_body_entered signal and increments its count. I'll insist in renaming the singal.

    Furthermore, we can put a setter on coins instead of calling _ready.

    Thus:

    Global_Coin:

    extends Node
    
    signal coin_collected
    

    Area3D:

    extends Area3D
    
    func _on_body_entered(body:Node3D) -> void:
        prints("collected")
        Global_Coin.coin_collected.emit()
        queue_free()
    

    Label:

    extends Label
    
    
    var coins:int = 0:
        set (mod_value):
            coins = mod_value
            text = str(coins)
    
    
    func _ready() -> void:
        Global_Coin.coin_collected.connected(_on_coin_collected)
        # coins = 0
    
    
    func _on_coin_collected() -> void:
        coins = coins + 1