Menu
CoddyTech

Patron Observer

Fait partie de la section Programmation orientée objet du Journey GDScript de Coddy. Leçon 48 sur 65.

Le modèle Observer permet à un objet, le sujet, d’informer de nombreux autres objets, les observateurs, qu’un événement s’est produit. Le sujet ne sait pas ce que les observateurs font de cette information ; il se contente de l’annoncer.

Écrit à la main, le sujet conserve un tableau d’observateurs et appelle la même méthode sur chacun d’eux :

# health.gd
class_name Health
extends RefCounted

var observers: Array = []
var hp := 100

func add_observer(observer) -> void:
	observers.append(observer)

func take_damage(amount: int) -> void:
	hp -= amount
	for observer in observers:
		observer.on_hp_changed(hp)

Godot intègre ce modèle. Un signal est l’annonce du sujet, et chaque connect() ajoute un observateur, de sorte que le tableau et la boucle ne sont plus nécessaires :

# health.gd
class_name Health
extends RefCounted

signal hp_changed(new_hp: int)

var hp := 100

func take_damage(amount: int) -> void:
	hp -= amount
	hp_changed.emit(hp)

Un observateur est tout objet doté d’une méthode dont les paramètres correspondent au signal :

# health_bar.gd
class_name HealthBar
extends RefCounted

func on_hp_changed(new_hp: int) -> void:
	print("Bar: ", new_hp, "/100")
# damage_log.gd
class_name DamageLog
extends RefCounted

func on_hp_changed(new_hp: int) -> void:
	if new_hp < 50:
		print("Log: low health!")
# main.gd
var health := Health.new()
var bar := HealthBar.new()
var damage_log := DamageLog.new()
health.hp_changed.connect(bar.on_hp_changed)
health.hp_changed.connect(damage_log.on_hp_changed)
health.take_damage(70)
Bar: 30/100
Log: low health!

Health n’a aucune idée qu’une barre ou un journal existe. Un effet sonore ou un succès peut aussi écouter, sans apporter la moindre modification à health.gd.

Trois règles permettent aux observateurs de fonctionner :

  • Connecter deux fois la même méthode est une erreur. Lorsque cela peut se produire, vérifie d’abord avec hp_changed.is_connected(bar.on_hp_changed). disconnect() supprime un observateur, et déconnecter un observateur qui n’est pas connecté est également une erreur.
  • Une connexion ne maintient pas l’observateur en vie. Conserve l’observateur dans une variable ou un tableau : un objet créé à l’intérieur de l’appel connect() est immédiatement libéré et ne reçoit jamais le signal.
  • Connecte les méthodes des observateurs. Une lambda qui utilise le sujet et qui est connectée au propre signal du sujet maintient celui-ci en vie pour toujours, ce que Godot signale comme une fuite à la fermeture du jeu.
challenge icon

Défi

Moyen

Crée une station météo qui informe ses afficheurs à l’aide d’un signal.

Dans weather_station.gd :

  • Déclare le signal temperature_changed(value: float).
  • attach(display) connecte la méthode on_temperature_changed de l’afficheur. Attacher un afficheur qui l’est déjà ne fait rien.
  • detach(display) le déconnecte. Détacher un afficheur qui n’est pas attaché ne fait rien.
  • set_temperature(value) stocke la valeur dans temperature et émet le signal avec cette valeur.

Dans weather_display.gd, on_temperature_changed(value) affiche le nom de l’afficheur et la valeur avec une décimale, comme Phone: 21.5 C.

Le code fourni lit les noms des afficheurs sur une ligne, le nombre de commandes qui suivent, puis une commande par ligne : attach NAME, detach NAME ou temp VALUE. À la fin, il affiche la dernière température. Pour les noms Phone Tablet et les commandes attach Phone, attach Tablet, temp 21.5, detach Phone, temp 19, le résultat est :

Phone: 21.5 C
Tablet: 21.5 C
Tablet: 19.0 C
Final temperature: 19.0

Ton code va dans weather_station.gd et weather_display.gd. main.gd contient le code d’entrée/sortie fourni et ne peut pas être modifié.

Essayez vous-même

extends Node

func _ready():
	# Code d'entrée/sortie fourni : conservez-le tel quel
	var station := WeatherStation.new()
	var displays := {}
	for display_name in OS.read_string_from_stdin().split(" "):
		displays[display_name] = WeatherDisplay.new(display_name)
	var count := int(OS.read_string_from_stdin())
	for i in count:
		var parts := OS.read_string_from_stdin().split(" ")
		match parts[0]:
			"attach":
				station.attach(displays[parts[1]])
			"detach":
				station.detach(displays[parts[1]])
			"temp":
				station.set_temperature(float(parts[1]))
	print("Final temperature: %.1f" % station.temperature)
quiz iconTestez-vous

Cette leçon comprend un petit quiz. Commencez la leçon pour y répondre et suivre votre progression.

Toutes les leçons de Programmation orientée objet

Entraînez-vous par vous-même : Compilateur GDScript en ligne