{{tag>ludique dev godot}}
====== 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 {{ludique:godot:tutoriaux: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:
* Scene -> Paramètres du Projet
Dans la section **Display** cocher les options et définir les valeurs pour:
* **width** = 640
* **Height** = 400
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 2D** -> **Transform**, 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
* Menu **Scene** -> **Paramètres du projet**
* Sélectionner l'onglet **Contrôles**
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:
* screen_size: taille de l'écran
* pad_size: taille des raquettes
* direction: position de la balle
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
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 =====
* http://docs.godotengine.org/en/stable/learning/step_by_step/simple_2d_game.html