La meilleure façon d’apprendre un moteur est de terminer un petit jeu, pas de lire indéfiniment. Donc dans cet article nous construisons un jeu 2D complet, certes minuscule, dans Godot 4. Un joueur se déplace, ramasse des pièces pour augmenter un score, et meurt si un ennemi le touche. À la fin vous aurez touché à tous les systèmes nécessaires pour la plupart des projets 2D. Si vous n’avez jamais ouvert Godot, lisez d’abord mon guide débuter avec le moteur de jeu Godot 4, car je suppose ici que vous savez ce qu’est un nœud et une scène.
Mise en place du projet et de la scène du joueur
Créez un nouveau projet avec le moteur de rendu Forward Plus. La première scène que nous construisons est le joueur. Ajoutez un CharacterBody2D comme racine, puis donnez lui trois enfants : un Sprite2D pour le visuel, une CollisionShape2D pour le corps physique, et une Camera2D pour que la vue suive le joueur. Réglez la forme de collision sur une capsule ou un rectangle qui correspond grossièrement à votre sprite. Sauvegardez la scène sous player.tscn.
CharacterBody2D est la bonne base ici parce qu’il nous donne une vélocité intégrée et une méthode move_and_slide qui gère la réponse aux collisions sans qu’on fasse le calcul vectoriel à la main.
Déplacer le joueur
Avant d’écrire le code de déplacement, définissez quelques actions d’entrée. Ouvrez les Paramètres du projet, allez dans l’onglet Contrôles, et ajoutez quatre actions nommées move_up, move_down, move_left et move_right. Liez chacune aux touches fléchées et aux touches WASD. Associer les entrées à des actions nommées plutôt que de coder les touches en dur signifie que vous pourrez prendre en charge les manettes et la reconfiguration plus tard sans rien réécrire.
Attachez maintenant un script à la racine du joueur. Voici la logique de déplacement :
extends CharacterBody2D
# Pixels par seconde
@export var speed: float = 220.0
func _physics_process(delta: float) -> void:
# Construit un vecteur de direction à partir des quatre actions.
# get_axis renvoie une valeur de -1 à 1 pour chaque paire.
var direction := Vector2.ZERO
direction.x = Input.get_axis("move_left", "move_right")
direction.y = Input.get_axis("move_up", "move_down")
# Normalise pour que le déplacement diagonal ne soit pas plus rapide
if direction.length() > 1.0:
direction = direction.normalized()
velocity = direction * speed
move_and_slide()
Quelques points à souligner. Nous utilisons _physics_process plutôt que _process parce que tout ce qui déplace un corps physique doit tourner sur le pas de temps fixe de la physique. L’annotation @export rend speed modifiable dans l’Inspecteur, donc je peux l’ajuster sans toucher au code. Et normaliser la direction supprime le bug classique où se déplacer en diagonale est environ quarante pour cent plus rapide que tout droit.
Fabriquer une pièce à ramasser
Créez une deuxième scène avec une Area2D comme racine. Area2D détecte les chevauchements sans bloquer physiquement quoi que ce soit, ce qui est exactement ce dont un objet à ramasser a besoin. Donnez lui un Sprite2D et une CollisionShape2D. Sauvegardez sous coin.tscn et attachez ce script :
extends Area2D
# Émis quand le joueur attrape cette pièce
signal collected
func _ready() -> void:
body_entered.connect(_on_body_entered)
func _on_body_entered(body: Node2D) -> void:
if body.is_in_group("player"):
collected.emit()
queue_free()
Nous déclarons notre propre signal appelé collected et nous l’émettons quand un joueur entre. La pièce ne sait pas ce qu’est un score, et elle ne devrait pas. Elle annonce simplement qu’elle a été ramassée et se retire. Pour que la vérification de groupe fonctionne, sélectionnez le nœud joueur, ouvrez le dock Nœud à côté de l’Inspecteur, passez à Groupes, et ajoutez le à un groupe nommé player.
Suivre le score avec un global
Le score doit survivre à toute scène individuelle, donc enregistrez le comme autoload. Créez un script appelé game_state.gd :
extends Node
var score: int = 0
signal score_changed(new_score: int)
func add_points(amount: int) -> void:
score += amount
score_changed.emit(score)
func reset() -> void:
score = 0
score_changed.emit(score)
Enregistrez le dans les Paramètres du projet sous l’onglet Globaux avec le nom GameState. Maintenant n’importe quel script du jeu peut appeler GameState.add_points et écouter le signal score_changed pour mettre à jour l’affichage. C’est la façon la plus propre de partager un état dans Godot sans emmêler vos scènes.
Brancher le niveau et l’interface
Construisez une scène de niveau. Déposez y une instance du joueur, dispersez plusieurs instances de pièces autour, et ajoutez un CanvasLayer avec un Label à l’intérieur pour le score. Le CanvasLayer garde le label fixé à l’écran même quand la caméra suit le joueur. Attachez un petit script au label :
extends Label
func _ready() -> void:
GameState.score_changed.connect(_on_score_changed)
_on_score_changed(GameState.score)
func _on_score_changed(new_score: int) -> void:
text = "Score : " + str(new_score)
Pour que chaque pièce ajoute réellement au score, connectez son signal collected à un gestionnaire dans le script du niveau qui appelle GameState.add_points(10). Vous pouvez le faire dans l’éditeur en sélectionnant une pièce et en utilisant le dock Nœud, ou en code en bouclant sur les pièces dans _ready. Je préfère le code quand il y a beaucoup d’instances, car connecter cinquante pièces à la main dans l’éditeur est fastidieux et source d’erreurs.
Ajouter un ennemi et une fin de partie
Un ennemi peut être aussi simple qu’une autre Area2D qui va et vient. Quand elle chevauche le joueur, la partie se termine. Voici un patrouilleur minimal :
extends Area2D
@export var move_speed: float = 90.0
var _direction: int = 1
func _ready() -> void:
body_entered.connect(_on_body_entered)
func _physics_process(delta: float) -> void:
position.x += move_speed * _direction * delta
# Inverse la direction aux bords de la patrouille
if position.x > 600 or position.x < 200:
_direction *= -1
func _on_body_entered(body: Node2D) -> void:
if body.is_in_group("player"):
get_tree().change_scene_to_file("res://scenes/game_over.tscn")
L’appel change_scene_to_file démonte la scène actuelle et en charge une nouvelle. Construisez une scène de fin simple avec un Label et un bouton qui réinitialise le score et recharge le niveau. Pensez à appeler GameState.reset sur ce bouton pour que la prochaine partie démarre propre.
Du polissage qui pèse plus lourd que prévu
Un jeu qui fonctionne et un jeu qui fait du bien sont deux choses différentes. Les gains les moins chers que je connaisse :
- Ajoutez un son court au ramassage d’une pièce. Même un simple bip rend la collecte réelle.
- Donnez à la caméra une petite valeur de lissage pour qu’elle glisse vers le joueur au lieu de claquer dessus.
- Jouez un rapide tween d’échelle sur la pièce avant qu’elle disparaisse pour qu’elle éclate plutôt qu’elle s’évanouisse.
- Ajoutez un léger tremblement d’écran quand le joueur meurt. Vingt minutes de travail, énorme qualité perçue.
Les tweens dans Godot 4 se créent avec create_tween et sont parfaits pour ce genre de jus sans écrire d’animation image par image.
Jusqu’où ça monte
Vous avez maintenant la boucle complète : entrée, déplacement, collision, objets à ramasser, état global, un ennemi et des transitions de scène. Presque tout jeu 2D est une version plus élaborée de ces mêmes pièces. Un jeu de plateforme ajoute la gravité et le saut au script de déplacement. Un shooter fait apparaître des scènes de projectiles sur un minuteur. Un jeu de puzzle remplace la physique par une grille. L’architecture que vous venez de construire se transpose directement.
Terminez ce jeu, puis cassez le exprès. Ajoutez un deuxième type d’ennemi, un meilleur score qui persiste sur le disque, un écran titre. Livrer petit et itérer, voilà comment on apprend réellement le moteur, bien plus que n’importe quel tutoriel y compris celui ci.

Laisser un commentaire