Lesson 14

Make Your Own Signal

Three floor switches, and not one line of game_events.gd. Write a signal that lives inside its own node, and learn when a plain function call beats a signal.

Loading the game engine. This takes a moment the first time.

This lesson uses no game_events.gd at all, and that is the point of it. Every lesson since Platformer Movement has put its news on the shared bus. This level does the same kind of job without one, so you can see what the bus is FOR by watching a level that does not need it.

ASK ONE QUESTION: CAN YOU POINT AT THE NODE YOU NEED TO TALK TO?

  • YES. Just call its function. There are exactly two ways to be able to point at a node, and you can check both: it is your own child, like hud.set_count(2, 3), or a collision handed it to you, like body.take_damage(1).
  • NO, AND IT IS MY PARENT. Write a local signal in your own script. Your parent can reach you, so it connects to you. That is this lesson.
  • NO, ANYTHING ELSE. Put the signal in game_events.gd, the shared bus.

WHO TALKS TO WHOM IN THIS LEVEL

SWITCH TO LEVEL
A child cannot point at its parent. Godot gives a node no easy way up the tree, and a switch that reached up would only work while it sat under this exact level. So the switch emits and we connect. LOCAL SIGNAL.
LEVEL TO HUD
The HUD is our own child, so we point straight at it. No signal at all. PLAIN CALL: hud.set_count(2, 3)

WHEN WOULD THIS LEVEL HAVE NEEDED THE BUS?

  1. A SECOND thing needs the same news. Adding a listener to the bus is one line. Adding a second direct call means this file has to learn about one more node.
  2. The two nodes are far apart, or sitting in different scenes.
  3. A switch is BUILT MID-GAME. We connect in _ready(), which only works because all three switches are already in the level when it starts. Nothing can connect to a node that does not exist yet.

New code in this lesson

None of the earlier lessons use these. Each one shows the line where it appears here.

for ... in

A loop that runs the lines under it once for every item in a list, or once for every number up to a count.

game.gd
for child in get_children():

get_children()

A list of every node directly under this one.

game.gd
for child in get_children():

.color

The fill color of a Polygon2D or ColorRect.

switch.gd
square.color = Color(0.2, 0.9, 0.3)

The scenes

■ UI node · ■ 2D node · % unique name · .tscn instanced scene

game.tscn
  • Game Node2D game.gd
  • Player CharacterBody2D player.tscn
  • Switch1 Area2D switch.tscn
  • Switch2 Area2D switch.tscn
  • Switch3 Area2D switch.tscn
  • HUD CanvasLayer % hud.tscn
  • Walls Node2D
  • TopWall StaticBody2D hidden block.tscn
  • BottomWall StaticBody2D hidden block.tscn
  • LeftWall StaticBody2D hidden block.tscn
  • RightWall StaticBody2D hidden block.tscn
hud.tscn
  • HUD CanvasLayer hud.gd
  • MarginContainer
  • ScoreLabel Label %
player.tscn
  • Player CharacterBody2D player.gd
  • Sprite2D
  • CollisionShape2D
switch.tscn
  • Switch Area2D switch.gd
  • Polygon2D
  • CollisionShape2D
block.tscn
  • Block StaticBody2D
  • Polygon2D
  • CollisionPolygon2D

FileSystem

  • game.tscn
  • game.gd
  • hud.tscn
  • hud.gd
  • player.tscn
  • player.gd
  • switch.tscn
  • switch.gd
  • block.tscn

The code

extends Node2D

# Both counted in "_ready()" below. Nobody types these by hand.
var total_switches: int = 0
var switches_on: int = 0

@onready var hud: SwitchHUD = %HUD

func _ready() -> void:
	# Walk our own children and pick out the switches.
	for child in get_children():
		if child is FloorSwitch:
			total_switches += 1
			# "flipped" is a LOCAL signal, written inside switch.gd. We can
			# connect to it because this switch is our own child, so we are
			# holding it right here as "child".
			child.flipped.connect(_on_switch_flipped)
	hud.set_count(switches_on, total_switches)

# Runs when ANY one switch emits "flipped". All three connected to this same
# function, so one function covers however many switches the level has.
func _on_switch_flipped() -> void:
	switches_on += 1
	hud.set_count(switches_on, total_switches)
	if switches_on >= total_switches:
		hud.show_win()