Menu
CoddyTech

Signals with Arguments

Part of the Object Oriented Programming section of Coddy's GDScript journey. Lesson 38 of 65.

A signal can carry values. List them in the declaration, like the parameters of a function:

signal damaged(amount: int, hp_left: int)
signal died

Pass the values to emit() in the same order:

func take_damage(amount: int) -> void:
	hp = maxi(hp - amount, 0)
	damaged.emit(amount, hp)
	if hp == 0:
		died.emit()

A connected function receives the values as its parameters, so it needs one parameter per value:

hero.damaged.connect(_on_hero_damaged)

func _on_hero_damaged(amount: int, hp_left: int) -> void:
	print("-%d HP, %d left" % [amount, hp_left])

The types in the declaration document the values: GDScript does not check them when you emit. A mismatch shows up in the receiving function. A function with the wrong number of parameters, or a typed parameter that cannot hold the value, is an error at the emit() line, and that function does not run.

bind() adds values of your own after the emitted ones. One method can then listen to several heroes and still know which one was hit:

ada.damaged.connect(_on_damaged.bind("Ada"))
bo.damaged.connect(_on_damaged.bind("Bo"))

func _on_damaged(amount: int, hp_left: int, who: String) -> void:
	print(who, " lost ", amount, " HP")

Arguments give a listener the data it needs, so it does not have to read it from the emitter. Never connect a lambda that uses the emitting object itself, such as hero.died.connect(func(): print(hero.hp)): the lambda keeps hero alive, hero keeps the lambda, neither is ever freed, and Godot reports leaked instances when the program ends.

challenge icon

Challenge

Easy

A wallet reports every change with signals that carry the numbers. In Wallet, add the parameters to both signals and write the two methods:

  • balance_changed(old_balance: int, new_balance: int) and purchase_failed(price: int, missing: int)
  • add(amount) adds coins and emits balance_changed with the balance before and after.
  • buy(price): when the balance is enough, subtract the price and emit balance_changed. Otherwise change nothing and emit purchase_failed with the price and how many coins are missing.

The supplied code connects one function to each signal, reads how many commands follow, runs each command (add 20, buy 15) and prints the coins left at the end. For add 20, buy 15, buy 10, add 5, buy 10 it prints:

Balance: 0 -> 20
Balance: 20 -> 5
Cannot buy for 10: 5 coins missing
Balance: 5 -> 10
Balance: 10 -> 0
Coins left: 0

Your code goes in wallet.gd. main.gd holds the supplied input/output code and cannot be edited.

Try it yourself

extends Node

func _ready():
	# Supplied input/output code: keep it as it is
	var wallet := Wallet.new()
	wallet.balance_changed.connect(_on_balance_changed)
	wallet.purchase_failed.connect(_on_purchase_failed)
	var count := int(OS.read_string_from_stdin())
	for i in count:
		var parts := OS.read_string_from_stdin().split(" ")
		if parts[0] == "add":
			wallet.add(int(parts[1]))
		else:
			wallet.buy(int(parts[1]))
	print("Coins left: ", wallet.balance)

func _on_balance_changed(old_balance: int, new_balance: int) -> void:
	print("Balance: %d -> %d" % [old_balance, new_balance])

func _on_purchase_failed(price: int, missing: int) -> void:
	print("Cannot buy for %d: %d coins missing" % [price, missing])
quiz iconTest yourself

This lesson includes a short quiz. Start the lesson to answer it and track your progress.

All lessons in Object Oriented Programming

Practice on your own: Online GDScript compiler