Score, spawn, cooldown dans Godot 4 : faire des boucles propres avec Timer
Formations

Score, spawn, cooldown dans Godot 4 : faire des boucles propres avec Timer

Antharuu Antharuu
11 min de lecture
godot 4 tutoriel godot godot timer node timer godot signal timeout godot gdscript godot 4 score avec timer godot clignotement avec timer godot autostart timer godot one shot vs repeat godot pause mode timer godot

Vous en etes deja a un stade super pratique : vous savez connecter des signals, vous avez une scene Main avec Player, HUD et un menu... bref, la base est la. Et maintenant, vous vous demandez peut-etre comment faire des evenements reguliers (score qui monte, clignotement, spawn, cooldown) sans transformer _process(delta) en usine a gaz (et sans cramer du CPU pour rien). Accrochez-vous, on va voir ca ensemble : les Timers, c'est simple, propre, et franchement top... mais seulement si on evite 2 ou 3 pieges classiques.


Contexte rapide (ou on en est)

Vous avez un projet Godot 4.x avec :

  • une scene Main (racine),
  • un Player,
  • un HUD (avec au moins un Label),
  • un menu simple (pause ou ecran de debut, peu importe).

Vous connaissez deja les signals (c'est essentiel), et vous voulez declencher des actions a intervalle regulier sans faire des calculs chaque frame.


Objectifs (clairs, mesurables)

A la fin de ce module, vous saurez :

  1. Utiliser un Timer node et son signal timeout.
  2. Comprendre la difference entre periodicite (repeter) et duree (delai).
  3. Creer une petite "boucle" de score (ou un clignotement) sans coder dans _process.
  4. Ajouter et configurer un Timer dans une scene (wait_time, autostart, one_shot, pause).
  5. Diagnostiquer les pieges classiques (timer non demarre, autostart, pause mode).

Mini glossaire (rapide, utile)

  • Timer : noeud qui compte a rebours et emet un signal quand il arrive a zero.
  • timeout : signal emis par le Timer quand le temps est ecoule.
  • wait_time : duree (en secondes) avant un timeout.
  • one_shot : si true, le timer ne se relance pas tout seul (une seule fois).
  • repeat : quand one_shot = false, le timer repart automatiquement apres chaque timeout.
  • autostart : si true, le timer demarre automatiquement quand la scene est prete.
  • time_left : temps restant avant le prochain timeout.
  • pause mode : comportement du noeud quand le jeu est en pause (il peut continuer ou s'arreter selon votre config).

Pourquoi les Timers (et pourquoi ce n'est pas _process) ?

Un evenement regulier, c'est tentant de le faire comme ca : "j'accumule du delta, si ca depasse 1 seconde je fais mon truc". Ca marche... mais c'est du code en plus, du risque de bug en plus, et ca tourne chaque frame.

Un Timer, c'est l'inverse : vous lui dites "toutes les 1.0 secondes, fais-moi un timeout", et lui se debrouille (et votre _process reste clean). C'est leger, c'est lisible, et c'est facile a ajuster dans l'editeur (ce qui est mega confortable quand vous testez).


Notion cle : duree vs periodicite (one-shot vs repeat)

Avant de coder, retenez juste cette image mentale :

  • Duree (delai) : "attends 3 secondes puis fais X". C'est souvent un one-shot.
  • Periodicite (boucle) : "toutes les 1 seconde, fais X". C'est un Timer en repeat.

Dans Godot, ca se regle avec one_shot :

  • one_shot = true : une seule fois, puis stop.
  • one_shot = false : le Timer continue en boucle (periodique).

Vous allez utiliser les deux dans un jeu, mais pour un score qui monte, on veut du repeat.


Pas a pas guide : un score temporel dans Main

On va creer un compteur time_survived qui s'incremente toutes les secondes, et on l'affiche dans un Label du HUD. L'objectif est que ce soit visible tout de suite quand vous lancez la scene.

1) Ajouter le Timer dans la scene Main

  1. Ouvrez Main.tscn.
  2. Dans l'arborescence, selectionnez Main.
  3. Clic droit -> Ajouter un enfant -> cherchez Timer.
  4. Renommez-le ScoreTimer.

Petit detail important : un Timer n'a pas de visuel, donc sa "position" dans la scene, on s'en fiche. Par contre, il doit etre dans l'arbre (dans la scene en cours) pour tourner.

Checkpoint

  • Vous voyez ScoreTimer sous Main.
  • Il apparait comme un noeud Timer (pas un Node generic).

2) Configurer le Timer (inspecteur)

Selectionnez ScoreTimer, puis dans l'inspecteur :

  • Wait Time : 1.0
  • One Shot : decoche (donc repeat)
  • Autostart : coche (pour demarrer tout seul)
  • Process Callback : laissez par defaut pour l'instant

C'est simple, mais c'est la que se cache un piege : si Autostart n'est pas coche, il ne se passera... rien. (Oui, c'est bete, mais c'est ultra frequent.)

Checkpoint

  • wait_time = 1.0
  • one_shot = false
  • autostart = true

3) Connecter timeout() a Main

  1. Cliquez sur ScoreTimer.
  2. Onglet Node (ou "Noeud") a droite.
  3. Double-clic sur timeout().
  4. Choisissez la cible Main.
  5. Validez.

Godot cree une fonction du style :

  • func _on_score_timer_timeout():

Checkpoint

  • Dans le script de Main, vous avez bien une fonction _on_score_timer_timeout() (meme vide).

4) Ecrire le code du compteur + mise a jour HUD

Dans Main.gd, on pose une variable et on prepare les chemins.

extends Node2D

var time_survived: int = 0

@onready var score_timer: Timer = $ScoreTimer
@onready var hud_label: Label = $HUD/Label # adaptez le chemin

Ici, c'est clair : score_timer, c'est votre Timer, hud_label, c'est l'affichage (et oui, vous devrez peut-etre ajuster $HUD/Label selon votre scene).

Ensuite, dans _ready(), on peut securiser le demarrage. Meme si autostart est coche, c'est pratique d'avoir un plan B si vous changez les reglages plus tard.

func _ready() -> void:
    if not score_timer.autostart:
        score_timer.start()

Et enfin, dans le callback du signal :

func _on_score_timer_timeout() -> void:
    time_survived += 1
    hud_label.text = "Temps survecu: " + str(time_survived) + "s"

Checkpoint

  • Le jeu se lance sans erreur.
  • Le label change toutes les 1 secondes : 1s, 2s, 3s...

5) Tester (et verifier que c'est bien le Timer qui bosse)

Lancez Main (F6) ou le jeu (F5). Si tout va bien :

  • le compteur monte tout seul,
  • _process n'est pas utilise,
  • votre HUD reste fluide.

Si rien ne bouge, ne paniquez pas : c'est quasiment toujours un piege de la section "Erreurs frequentes" (autostart pas coche, mauvais chemin vers le Label, signal pas connecte, pause mode...).


Variante rapide : clignotement (BlinkTimer)

Vous voulez un effet "attention" sur un label, genre clignotement toutes les 0.5 secondes ? C'est exactement le meme schema, et c'est parfait pour comprendre la periodicite.

  1. Ajoutez un second Timer sous Main : BlinkTimer
  2. Configurez :
    • wait_time = 0.5
    • one_shot = false
    • autostart = true
  3. Connectez timeout() vers Main
  4. Code :
func _on_blink_timer_timeout() -> void:
    hud_label.visible = not hud_label.visible

C'est tout. Et surtout : ce clignotement ne depend pas du framerate, et vous n'avez pas ecrit une seule ligne dans _process.


Aller plus loin : piloter un Timer (start, stop, time_left)

Un Timer, ce n'est pas juste "tick toutes les secondes". Vous pouvez le controler tres simplement.

Demarrer, arreter, relancer

  • score_timer.start() : demarre (ou redemarre) le timer avec son wait_time.
  • score_timer.start(2.5) : demarre avec un delai de 2.5s (sans changer wait_time dans l'inspecteur).
  • score_timer.stop() : arrete le timer (plus de timeout).

Exemple pratique : vous stoppez le score quand le joueur meurt, puis vous le relancez au restart.

Lire le temps restant (time_left)

time_left est utile pour un cooldown ou pour afficher "prochaine vague dans X secondes".

func _process(_delta: float) -> void:
    # Juste pour debug (pas besoin en prod)
    # print(score_timer.time_left)
    pass

Ici on l'affiche en debug, mais notez bien : le Timer tourne sans _process. _process ne sert que si vous voulez lire/afficher time_left en continu.


One-shot : un delai unique propre

Cas classique : vous voulez attendre 3 secondes avant d'afficher un message, lancer un spawn, ou autoriser une action.

Avec un Timer node :

func launch_one_shot_example() -> void:
    score_timer.one_shot = true
    score_timer.start(3.0)

Attention : si vous transformez votre ScoreTimer en one-shot, il ne fera plus votre score periodique. En general, on cree un Timer dedie (genre IntroDelayTimer) pour ne pas melanger les roles.


Erreurs frequentes (si tu vois X, alors c'est surement Y)

1) "Rien ne se passe"

  • Cause probable : le timer n'a pas demarre
  • Fix : cochez autostart, ou appelez start() dans _ready()

Astuce simple : selectionnez le Timer en jeu (Remote dans l'arbre) et regardez si time_left diminue.

2) "J'ai une erreur sur le chemin du Label"

  • Cause : $HUD/Label ne correspond pas a votre scene
  • Fix : clic droit sur le Label -> "Copier le chemin du noeud" et collez-le dans le script

3) "Mon timer ne tourne pas en pause" (ou l'inverse)

Si vous avez un menu pause, vous allez tomber dessus.

  • Symptome A : vous mettez le jeu en pause et le score s'arrete, mais vous vouliez qu'il continue (par exemple un menu qui clignote).
  • Symptome B : vous mettez pause et le timer continue, mais vous vouliez tout figer.

Fix : regardez le pause mode du Timer (et du HUD) : selon votre choix, un noeud peut stopper ou continuer en pause. C'est un reglage de noeud, pas juste une question de code.

Et si vous voulez forcer un arret manuellement :

  • score_timer.paused = true quand vous ouvrez le menu
  • score_timer.paused = false quand vous reprenez

4) "start() ne marche pas"

  • Cause : vous tentez de demarrer un Timer qui n'est pas encore dans l'arbre de scene
  • Fix : demarrez-le apres _ready(), ou assurez-vous que le noeud existe deja (bon chemin, bonne scene instanciee)

5) "Je veux du 0.01s, mais c'est instable"

  • Cause : un Timer est traite par frame, donc tres petit wait_time = irregularites (limite framerate)
  • Fix : pour des micro-intervalles, utilisez une logique _process/_physics_process adaptee, ou augmentez l'intervalle

Exercices (1 facile + 1 challenge)

Exercice facile : "Temps: X" toutes les secondes

Objectif : refaire le compteur sans copier-coller aveugle (c'est la que ca rentre).

  1. Ajoutez un Timer SurvivalTimer sous Main.
  2. Reglages : wait_time=1.0, autostart=true, one_shot=false.
  3. Connectez timeout() a Main.
  4. Dans Main : variable temps: int = 0.
  5. Dans _on_survival_timer_timeout() :
    • temps += 1
    • mise a jour d'un label $HUD/TempsLabel

Code minimal :

var temps: int = 0
@onready var temps_label: Label = $HUD/TempsLabel

func _on_survival_timer_timeout() -> void:
    temps += 1
    temps_label.text = "Temps: " + str(temps)

Checkpoint

  • Apres 30 secondes, vous voyez "Temps: 30"
  • Aucun code de compteur dans _process

Challenge : acceleration progressive (difficulte qui monte)

Objectif : rendre le timer de plus en plus rapide, sans descendre sous une limite. Ici, on joue sur la periodicite en diminuant wait_time.

Dans le script de Main :

@export var acceleration: float = 0.05
@export var min_wait: float = 0.1

func _on_score_timer_timeout() -> void:
    time_survived += 1
    hud_label.text = "Temps survecu: " + str(time_survived) + "s"

    score_timer.wait_time = max(min_wait, score_timer.wait_time - acceleration)

Petit detail : on ne relance pas start() a chaque fois, donc la nouvelle periode s'applique au cycle suivant. C'est souvent suffisant, et ca evite des effets de reset bizarres.

Checkpoint

  • Au debut : 1 tick par seconde
  • Apres un moment : ca s'accelere (sans devenir completement chaotique)
  • Le timer ne descend jamais sous min_wait

Recap + checklist (ce qui doit etre vrai a la fin)

Vous devez pouvoir cocher ca (sinon, revenez sur l'etape correspondante) :

  • J'ai un Timer dans Main avec wait_time configure.
  • Je sais la difference : one_shot=true (une fois) vs false (repete).
  • Je comprends "duree" (delai) vs "periodicite" (boucle).
  • Le signal timeout() est connecte, et la fonction s'appelle bien.
  • Mon compteur time_survived augmente sans utiliser _process.
  • Mon HUD s'actualise sans erreur de chemin.
  • Je sais demarrer/arreter (start/stop) un timer.
  • Je sais quoi verifier si ca bloque a cause de la pause (pause mode, paused).

Mini quiz (pour verifier que c'est acquis)

  1. Si one_shot = true, le Timer emet timeout...
  • A) toutes les secondes
  • B) une seule fois, puis il s'arrete
  • C) seulement si _process est present
  1. Votre compteur ne monte pas. Quelle verification en premier ?
  • A) verifier autostart ou un start() dans _ready()
  • B) ajouter un _process avec un delta += ...
  • C) changer le nom de la scene
  1. Vous mettez le jeu en pause et votre timer s'arrete, mais vous vouliez qu'il continue. Vous regardez...
  • A) le framerate
  • B) le pause mode du noeud (et/ou paused)
  • C) le texte du Label

Reponses attendues : 1) B, 2) A, 3) B.


Pont vers le module suivant

Maintenant que vous avez des evenements reguliers propres (score, clignotement, spawn, cooldown), vous avez une base parfaite pour synchroniser des effets visuels. Et justement, la suite logique, c'est l'animation fluide : Tweens et Animations (le genre de truc qui rend un jeu "vivant" en 10 minutes). On y va ?

Articles similaires