Table des matières

, ,

Créer un Pong avec Godot

Traduction du tutoriel. On se propose ici de coder le mythique Pong avec Godot.

Pour commencer exécuter Godot Engine et démarrer un nouveau projet. Afin de se concentrer sur l'essentiel quelques fichiers images sont fournis pour le tutoriel. Décompresser pong_assets.zip dans le répertoire du projet.

En souvenir du bon vieux temps, la résolution du jeu sera fixée en 640*400. On peut définir la résolution du projet dans le menu:

Dans la section Display cocher les options et définir les valeurs pour:

Dans la section Render activer Default Clear Color et définir sa valeur au noir.

Fermer la fenêtre Paramètre du projet.

Créer un Node2D comme racine du projet. Le type Node2D est le type de base pour les projets 2D. Renommer le Node game.

Insérer les nodes pour les sprites (nodes Sprite) nécessaires pour les deux raquettes, la balle et le séparateur.

  • Ctrl+A: Ajouter un nouveau node
  • Ctrl+D: Dupliquer le node sélectionné.

Renommer les sprites

sprite nom
raquette gauche left
raquette droite right
fillet separator
ball ball

Via l'inspector, associer les textures et dans la Section Node 2DTransform, modifier l'attribut Pos des sprites:

sprite Pos (x,y)
left (67, 183)
right (577, 187)
separator (320, 200)
ball (320, 188)

Lancer le projet pour faire une première sauvegarde de la scene, nommer la scene pong.tscn

Configurer les entrées

Dasn un jeu vidéo, les interactions du joueurs peuvent provenir d'un grand nombre de périphériques: clavier, souris, joustick, joypad, touchscreen etc. Godot est capable de les utiliser tous. Cependant il est plus intéressant de définir des actions au lieu de lier diretement les périphériques. De cette façon, n'importe quel périphérique d'entrée peut être utilisée: chacune des actions du jeu seront alors liées par le joeur au bouton de son périphérique d'entrée.

Dans le cas de pong les seules actions nécessaires permettront à la raquette de monter ou descendre.

Pour définir les actions, ouvrir les propriétés

Certaines actions existent par défaut pour le projet, nous allons en créer de nouvelles:

Actions à créer Touche assignée
left_move_up A
left_move_down Q
right_move_up P
right_move_down M

Fermer la fenêtre de Paramètres du projet

Le script

Créer un script sur la racine du projet (le node game). En premier lieu, il nous faut définir quelques attributs qui nous seront utiles pour les traitements:

La fonction _ready() est appelée lorsque tous les nodes ont été chargés. Ici nous allons l'utiliser pour initialiser nos attributs et activer la fonction _process() de traitements sur temps libre

extends Node2D
 
# attributs du script courant
# variables de travail
var screen_size
var pad_size
var direction = Vector2(1.0, 0.0)
 
 
func _ready():
	# initialisation des attributs
	self.pad_size = get_node("left").get_texture().get_size()
	self.screen_size = get_viewport_rect().size
 
	# Activer les traitements pour chaque frame sur temps libre
	set_process(true)

Mouvement de la balle et collisions

Il nous faut à présent ajouter d'autres attributs au script dans le but de faire bouger la balle.

Maintenant nous allons redéfinir la fonction _process() l'essentiel du comportement est définit ici. On commence par initialiser quelques valeurs utiles aux calculs de position de la balle et de collision. Les ajustement

func _process(delta):
	# Récupère la position courante de la balle
	var ball_position = get_node("ball").get_pos()
 
	# Construit une aire occupée par la raquette gauche
	# pour la détection de collision
	var left_rect = Rect2(get_node("left").get_pos() - self.pad_size * 0.5, self.pad_size)
	var right_rect = Rect2(get_node("right").get_pos() - self.pad_size * 0.5, self.pad_size)
La création des objets de type Rect2 utilisés pour la détection de collision avec la balle se base sur la texture du sprite qui par défaut est centrée d'où l'ajustement en x de pad_size/2

A présent attaquons nous au mouvement de la balle. Comme la position est stockée dans la variable ball_position sa nouvelle position est donnée par le calcul:

# Calcul de la nouvelle position
ball_position += self.direction * self.ball_speed * delta
# Mise a jour de la position de la balle
get_node("ball").set_pos(ball_position)

Si on lance l’exécution de la scène à présent, la balle se déplace bien mais traverse la raquette droite, il va nous falloir gérer les collisions.

Avant de mettre à jour la position de la balle nous allons donc tester si elle n'entre pas en collision avec quelque chose: une raquette ou les bords de la fenêtre. Pour commencer changeons la direction de la balle lorsqu'elle percute les bords haut ou bas de l'écran

...
 
# La balle change de direction lorsqu'elle touche les bords haut ou bas de l'écran
if ((ball_position.y < 0 and self.direction.y < 0) or (ball_position.y > self.screen_size.y and self.direction.y > 0)):
	self.direction.y = -self.direction.y
 
# Mise a jour de la position de la balle
get_node("ball").set_pos(ball_position)

A présent les raquettes, si une des raquettes est touchée, la direction sur X est inversée et celle sur Y prend une valeur aléatoire. Par la même occasion, la vitesse est légèrement augmentée.

Pour en finir avec la balle et les collisions, on teste le cas du point gagné. Pour faire simple, si la balle sort, elle est replacée au centre

...
 
# Si la balle sort, elle est replacée au centre avec vitesse et position initiale
	if (ball_position.x < 0 or ball_position.x > self.screen_size.x):
		ball_position = self.screen_size*0.5
		self.ball_speed = self.INITIAL_BALL_SPEED
		self.direction = Vector2(1.0, 0.0)
 
	# Mise a jour de la position de la balle
	get_node("ball").set_pos(ball_position)

Mouvement des raquettes

Il faut modifier la position des raquettes en fonction des entrées faites par les utilisateurs. Pour ce la nous allons utiliser les actions d'entrées définies dans le projet et le node Input, le code parle de lui meme:

	# Bouger la raquette gauche
	var left_pos = get_node("left").get_pos()
 
	if (left_pos.y > 0 and Input.is_action_pressed("left_move_up")):
		left_pos.y += -self.PAD_SPEED * delta
	if (left_pos.y < screen_size.y and Input.is_action_pressed("left_move_down")):
		left_pos.y += self.PAD_SPEED * delta
 
	get_node("left").set_pos(left_pos)
 
	# Bouger la raquette droite
	var right_pos = get_node("right").get_pos()
 
	if (right_pos.y > 0 and Input.is_action_pressed("right_move_up")):
		right_pos.y += -self.PAD_SPEED * delta
	if (right_pos.y < screen_size.y and Input.is_action_pressed("right_move_down")):
		right_pos.y += self.PAD_SPEED * delta
 
	get_node("right").set_pos(right_pos)

script complet

Le script complet a associer au node racine

pong.gd
extends Node2D
 
# attributs du script courant
# variables de travail
var screen_size
var pad_size
var direction = Vector2(1.0, 0.0)
 
# Vitesse initiale de la balle(en pixels/seconde)
const INITIAL_BALL_SPEED = 80
 
# Vitesse de la balle (en pixels/seconde)
var ball_speed = INITIAL_BALL_SPEED
 
# Vitesse des raquettes
const PAD_SPEED = 150
 
func _ready():
	# initialisation des attributs
	self.pad_size = get_node("left").get_texture().get_size()
	self.screen_size = get_viewport_rect().size
 
	# Activer les traitements pour chaque frame sur temps libre
	set_process(true)
 
func _process(delta):
	# position courante de la balle
	var ball_position = get_node("ball").get_pos()
 
	# Construit une aire occupée par la raquette gauche
	# pour la detection de collision
	var left_rect = Rect2(get_node("left").get_pos() - self.pad_size * 0.5, self.pad_size)
	var right_rect = Rect2(get_node("right").get_pos() - self.pad_size * 0.5, self.pad_size)
 
	# Calcul de la nouvelle position
	ball_position += self.direction * self.ball_speed * delta
 
	# La balle change de direction lorsqu'elle touche les bords haut et bas
	if ((ball_position.y < 0 and self.direction.y < 0) or (ball_position.y > self.screen_size.y and self.direction.y > 0)):
		self.direction.y = -self.direction.y
 
	# Change la direction et augmente la vitesse lorsque la balle touche les raquettes
	if ((left_rect.has_point(ball_position) and self.direction.x < 0) or (right_rect.has_point(ball_position) and self.direction.x > 0)):
    self.direction.x = -self.direction.x
    self.direction.y = randf()*2.0 - 1
    self.direction = self.direction.normalized()
    self.ball_speed *= 1.1
 
# Si la balle sort, elle est replacée au centre avec vitesse et position initiale
	if (ball_position.x < 0 or ball_position.x > self.screen_size.x):
		ball_position = self.screen_size*0.5
		self.ball_speed = self.INITIAL_BALL_SPEED
		self.direction = Vector2(1.0, 0.0)
 
	# Mise a jour de la position de la balle
	get_node("ball").set_pos(ball_position)
 
	# Bouger la raquette gauche
	var left_pos = get_node("left").get_pos()
 
	if (left_pos.y > 0 and Input.is_action_pressed("left_move_up")):
		left_pos.y += -self.PAD_SPEED * delta
	if (left_pos.y < screen_size.y and Input.is_action_pressed("left_move_down")):
		left_pos.y += self.PAD_SPEED * delta
 
	get_node("left").set_pos(left_pos)
 
	# Bouger la raquette droite
	var right_pos = get_node("right").get_pos()
 
	if (right_pos.y > 0 and Input.is_action_pressed("right_move_up")):
		right_pos.y += -self.PAD_SPEED * delta
	if (right_pos.y < screen_size.y and Input.is_action_pressed("right_move_down")):
		right_pos.y += self.PAD_SPEED * delta
 
	get_node("right").set_pos(right_pos)

Références