Jubibani
Jubibani

Reputation: 31

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

Upvotes: 0

Views: 523

Answers (2)

Theraot
Theraot

Reputation: 40295

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

Upvotes: 0

Silvio Mayolo
Silvio Mayolo

Reputation: 70307

To update a label, you need to update it's text field. It won't automatically detect changes to variables it was originally constructed with.

func on_body_entered() -> void:
    coins += 1
    text = str(coins)
    ...

You can automate this with a setter on the field.

var coins = 0:
    set(value):
        coins = value
        text = str(coins)

func on_body_entered() -> void:
    self.coins += 1
    ...

We need self.coins so as to trigger our own setter. Setting the variable directly bypasses it. But this has the advantage that, whenever anybody anywhere sets the coins variable, the label will update alongside it.

The above syntax is for Godot 4. If you're still on Godot 3, it looks like this.

var coins = 0 setget _set_coins

func _set_coins(value):
    coins = value
    text = str(coins)

func on_body_entered() -> void:
    self.coins += 1
    ...

Upvotes: 0

Related Questions