Création d'un jeu Pong en Pascal

Sous Lazarus/Free Pascal

Laurent Dubettier-Grenier

Dans ce tutoriel, nous allons créer pas à pas un jeu Pong à l'aide de l'environnement de développement Lazarus, basé sur le compilateur Free Pascal.

Commentez ♪

- Titre : Création d'un jeu Pong en Pascal

- Auteur : Laurent Dubettier-Grenier

- Parution : 29 novembre 2013

- Mise à jour : 28 mars 2015

- Public visé :

- Licence : Le contenu de cet article est rédigé par Laurent DUBETTIER-GRENIER et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Partage dans les Mêmes Conditions 3.0 non transposé. Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright ® 2013 Developpez.com.

Sommaire

- Étape 1 : création de l'interface graphique

- 1re phase : créer un répertoire de travail

- 2e phase : ouvrir Lazarus

- 3e phase : création de l'application Pong

- 4e phase : création du fond d'écran noir

- 5e phase : dimensionnement de la fenêtre du jeu Pong

- 6e phase : gestion de l'arrêt du jeu

- Étape 2 : Création et gestion de la balle

- 1re phase : lancer le jeu

- 2e phase : créer la balle

- 3e phase : déplacer la balle

- 4e phase : automatiser le déplacement de la balle

- 5e phase : l'ordinateur gère le déplacement de la balle

- 6e phase : faire rebondir la balle sur les côtés

- Étape 3 : Gestion des rebonds

- 1re phase : calcul des coordonnées de déplacement de la balle

- 2e phase : préparation à la gestion des rebonds

- 3e phase : gestion du rebond latéral

- 4e phase : gestion des rebonds supérieur et inférieur

- Étape 4 : Création et gestion des raquettes

- 1re phase : création de la raquette droite

- 2e phase : gérer le déplacement de la raquette droite

- 3e phase : faire de même pour la raquette gauche

- 4e phase : limiter le déplacement des raquettes

- 5e phase : gestion du rebond sur la raquette

- Étape 5 : gestion du score

- 1re phase : création des variables

- 2e phase : initialisation des variables

- 3e phase : détection d'un échec et incrémentation du score

- 4e phase : affichage du score sur l'écran

- 5e phase : annoncer le vainqueur

- Étape 6 : Dessiner le filet

- 1re phase : déclaration de la procédure

- 2e phase : création de la procédure

- 3e phase : création du code

- 4e phase : appel de la procédure

- Étape 7 : Corriger les problèmes d'effacement

- 1re phase : déclaration des procédures

- 2e phase : création des procédures

- 3e phase : gestion de l'objet BitMap

- 4e phase : création du code de la procédure EnregistrerEcran

- 5e phase : création du code de la procédure RestaurerEcran

- 6e phase : appel des procédures

- Étape 8 : Écrire les instructions de jeu

- 1re phase : déclaration de la procédure

- 2e phase : création de la procédure

- 3e phase : création du code

- 4e phase : appel de la procédure

- Étape 9 : Jouer des sons

- 1re phase : télécharger les sons

- 2e phase : ajouter la bibliothèque de gestion du son

- 2e phase : déclaration des procédures de gestion du son

- 3e phase : création des procédures

- 4e phase : création du code des procédures

- 5e phase : appel des procédures

- Étape 10 : Améliorations possibles

- Licence de cette documentation

Étape 1 : création de l'interface graphique

Nous allons créer l'interface graphique en six phases :

- création du répertoire de travail ;

- lancement de Lazarus ;

- création de l'application Pong ;

- création du fond d'écran noir ;

- dimensionnement de la fenêtre du jeu Pong ;

- gestion de l'arrêt du jeu.

1re phase : créer un répertoire de travail

Créer un répertoire de travail nommé « Pong » dans votre espace personnel.

2e phase : ouvrir Lazarus

Lazarus se lance à partir du menu Démarrer : dans le dossier Lazarus, choisir Lazarus :

3e phase : création de l'application Pong

Créer l'application Pong en cliquant sur Fichier/Nouveau puis choisir Application dans le dossier Project :

Choisir Application.

Choisir ensuite Fichier/Enregistrer tout, et nommer le projet Pong : cela génère un fichier pong.lpi, qui contiendra le jeu Pong.

Bien vérifier que ce fichier est créé dans le dossier Pong créé précédemment sur votre espace personnel.

Enregistrer ensuite le fichier .pas, qui contiendra le code Free Pascal du jeu, dans un fichier nommé unit_pong.pas, que vous enregistrerez toujours dans ce même répertoire de votre espace personnel.

Votre répertoire doit donc contenir plusieurs fichiers, dont les deux fichiers pong.lpi et unit_pong.pas (les extensions de fichier n'apparaîtront pas forcément).

Vérifier que tout fonctionne bien, en compilant et lançant cette application vide, en cliquant sur le bouton Exécuter (triangle vert en haut à gauche) ou en utilisant le raccourci clavier F9 : une fenêtre s'ouvre, identique à celle ci-dessous :

Fermer alors l'application créée.

Remarque : si vous regardez dans le répertoire du projet, un nouveau fichier nommé pong.exe vient d'être créé : il fait plus de 14 Mo…

Pour diminuer la taille de votre exécutable, il faut changer les options de compilation, et supprimer certaines options qui rajoutent beaucoup de code dans le fichier exécutable (pour le débogage) :

Cocher la case :

Projet --> Options du projet --> Options du compilateur --> Compilation and Linking --> Style de l'unité --> Lien intelligent (-CX)

Cocher la case :

Projet --> Options du projet --> Options du compilateur --> Compilation and Linking --> Édition des liens --> Lier intelligemment (-XX)

Décocher toutes les cases :

Projet --> Options du projet --> Options du compilateur --> infos de débogage

Sauf Éliminer les symboles de l'exécutable (-Xs)

Cocher 'Set compiler options as default'

Si vous recompilez votre application (F9), le fichier exécutable obtenu devrait avoir une taille plus petite (2 Mo environ).

4e phase : création du fond d'écran noir

Sous Lazarus, cliquer sur la fenêtre Form1 : dans l'inspecteur d'objet (partie gauche de Lazarus), nous allons définir la couleur (noire) de la fenêtre.

Dans l'onglet Propriétés de l'inspecteur d'objet, choisir la propriété Color et la régler à noir (clBlack). Taper sur Entrée pour activer cette couleur.

Vous pouvez aussi changer la propriété Caption (qui indique Form1) en Pong. Taper sur Entrée pour activer cette couleur.

Double-cliquer sur la fenêtre Form1, et regarder l'éditeur de source : du code vient d'apparaître à la fin de la page.

procedure TForm1.FormCreate(Sender: TObject);
begin
end;

Nous allons ajouter manuellement dans cette procédure la couleur de fond noire. C'est dans cette procédure, qui gère la création de l'application Pong, que nous indiquerons tous les paramètres généraux de notre application.

Rajouter entre begin et end; le code suivant :

Color:= clBlack;

Ne pas oublier le point-virgule en fin de ligne !

Le code devient donc :

procedure TForm1.FormCreate(Sender: TObject);
begin
Color:= clBlack;
end;

Vérifier le bon fonctionnement de l'application en appuyant sur F9  !

5e phase : dimensionnement de la fenêtre du jeu Pong

Pour dimensionner la fenêtre de notre jeu, nous allons rajouter deux lignes dans le code (fenêtre Éditeur de source), de manière à ce que l'aire de jeu soit un rectangle de 600 par 800 pixels :

Afin de faciliter la suite, nous allons stocker dans une constante les valeurs de largeur et de hauteur de l'aire de jeu :

const
HauteurJeu = 600;
LargeurJeu = 800;

Explication : nous affectons à la constante nommée HauteurJeu la valeur 600, et à la constante nommée LargeurJeu la valeur 800.

Ce code s'insère juste avant le mot clé var :

const
HauteurJeu = 600;
LargeurJeu = 800;
var
Form1: TForm1;

Quelques petites retouches esthétiques, pour centrer la fenêtre d'application dans l'écran et pour supprimer les bordures de fenêtre :

const
HauteurJeu = 600;
LargeurJeu = 800;
procédure TForm1.FormCreate(Sender: TObject);
begin
Color:= clBlack;
Height:= HauteurJeu;
Width:= LargeurJeu;
Position:= poScreenCenter;
BorderStyle:= bsNone;
end;

Vérifier le bon fonctionnement en appuyant sur F9  !

Attention, il n'y a plus la possibilité de fermer l'application !

Pour la fermer, il faut cliquer sur le bouton stop (rouge) de Lazarus !

6e phase : gestion de l'arrêt du jeu

Pour régler ce petit problème, nous allons faire en sorte que l'application Pong se ferme lorsque l'on clique sur le bouton Échap (ESC), situé en haut à gauche du clavier.

Dans L'inspecteur d'objets, pour la fenêtre Form1, dans l'onglet Événements, nous allons choisir OnKeyPress, et cliquer sur les trois points qui suivent (…) :

Une nouvelle procédure est créée dans l'éditeur de source :

procédure TForm1.FormKeyPress(Sender: TObject; var Key: char);
begin
end;

C'est cette procédure qui va permettre de gérer la fermeture de notre application Pong lorsque l'on presse la touche Échap.

Il faut rajouter, toujours dans l'éditeur de source, juste sous l'instruction existante Implementation, le code suivant :

Implementation
uses
LCLType;

Ceci indique à Lazarus qu'il doit charger la bibliothèque LCLType, contenant les éléments nécessaires à la bonne détection des caractères frappés sur le clavier.

Si l'on parcourait cette bibliothèque, nous trouverions que la touche du clavier Échap est codée par une valeur contenue dans la constante nommée VK_ESCAPE (pour info, c'est le nombre 27).

D'où le code suivant, à insérer dans la procédure FormKeyPress, entre begin et end; :

procédure TForm1.FormKeyPress(Sender: TObject; var Key: char);
begin
If ord(Key) = VK_ESCAPE then
Close;
end;

Key est une variable de type caractère (char), renvoyée par la procédure FormKeyPress.

VK_ESCAPE est une constante du système contenant un nombre prédéterminé.

Pour comparer les deux, il faut convertir le caractère Key en sa valeur numérique : c'est ce que fait l'instruction Ord.

À chaque frappe d'un caractère sur le clavier (Événement), la procédure FormKeyPress se lance, et ce qui est situé entre begin et end; s'exécute.

L'instruction If … Then cherche à savoir si le caractère tapé sur le clavier est la touche Échap : si c'est le cas, l'application se ferme.

Vérifier le bon fonctionnement en appuyant sur F9  !

Étape 2 : Création et gestion de la balle

La création et la gestion de la balle s'effectuent en six phases :

- lancement du jeu ;

- création de la balle ;

- déplacement de la balle ;

- automatisation du déplacement de la balle ;

- l'ordinateur gère le déplacement de la balle ;

- gestion des rebonds de la balle.

1re phase : lancer le jeu

Pour lancer le jeu, nous allons frapper la touche Espace du clavier.

La constante système qui correspond à la touche Espace est nommée VK_SPACE et contient le nombre 32.

Dans la procédure FormKeyPress, nous allons rajouter le code suivant (après close;) :

If Ord(Key) = VK_SPACE then
Begin
End;

2e phase : créer la balle

L'écran de l'application, qui mesure 600 pixels de hauteur et 800 pixels de largeur, a son origine dans le coin en haut à gauche. L'axe horizontal positif va vers la droite, l'axe vertical positif va vers le bas.

La balle va être créée à l'aide du code suivant :

Canvas.Brush.Color:=clWhite;
Canvas.Rectangle(0,0,15,15);

Ceci va créer un carré blanc de 15 pixels par 15 pixels, situé à l'origine (coordonnées 0,0), c'est-à-dire en haut à gauche de l'application : (0,0) représente l'origine du carré, et (15,15) l'extrémité du carré.

Pour intégrer ceci, dans la procédure FormKeyPress, nous allons donc rajouter le code ci-dessus à l'endroit suivant :

If Ord(Key) = VK_SPACE then
Begin
Canvas.Brush.Color:=clWhite;
Canvas.Rectangle(0,0,15,15);
End;

Vérifier le bon fonctionnement en appuyant sur F9  !

Lorsque vous appuyez sur la barre d'espace, un carré blanc apparaît à l'origine de la fenêtre (en haut à gauche)

Pour rendre le programme plus élégant, comme précédemment, nous allons créer une constante qui contient la valeur de taille de balle :

const
HauteurJeu = 600;
LargeurJeu = 800;
TailleBalle = 15;

Donc, on va modifier le code de la procédure FormKeyPress, qui devient :

If Ord(Key) = VK_SPACE then
Begin
Canvas.Brush.Color:=clWhite;
Canvas.Rectangle(0,0,TailleBalle,TailleBalle);
End;

Ensuite, pour créer un rectangle au centre de la fenêtre, il faudra insérer cette ligne de code dans le programme :

Canvas.Rectangle(400,300,415,315);

Pour simplifier le code, nous allons créer une variable de type entier correspondant à la moitié de la hauteur et une autre correspondant à la moitié de la largeur.

Une variable de type entière est une variable qui n'est autorisée qu'à contenir des nombres entiers.

Pour faire ceci, déclarer les variables dans la rubrique private de l'éditeur de source :

private
MiHauteur: integer;
MiLargeur: integer;

La procédure FormKeyPress est ensuite modifiée pour tenir compte de ses nouvelles variables contenant le centre de la fenêtre graphique :

procédure TForm1.FormKeyPress(Sender: TObject; var Key: char);
begin
MiHauteur:= HauteurJeu div 2;
MiLargeur:= LargeurJeu div 2;
If ord(Key) = VK_ESCAPE then
Close;
If Ord(Key) = VK_SPACE then
begin
Canvas.Brush.Color:=clWhite;
Canvas.Rectangle(MiLargeur, MiHauteur, MiLargeur + TailleBalle, MiHauteur + TailleBalle);
end;
end;

MiHauteur est une variable de type entier (integer). L'instruction div effectue une division de HauteurJeu par 2 et renvoie un résultat qui est un entier, compatible avec le type de MiHauteur.

Attention : lorsqu'on affecte une valeur à une constante, on utilise le signe « = » :

TailleBalle = 15;

Lorsqu'on déclare une variable, on utilise le signe « : » :

MiHauteur: integer;

Lorsqu'on affecte une valeur à une variable, on utilise les signes « := » :

MiHauteur := HauteurJeu div 2;

Vérifier le bon fonctionnement en appuyant sur F9  !

La balle apparaît maintenant au centre de la fenêtre de jeu.

3e phase : déplacer la balle

Pour déplacer la balle sur l'écran, il va falloir afficher la balle à un endroit, puis l'effacer et l'afficher à un autre endroit, et ainsi de suite, suffisamment rapidement pour que l'œil ne capte pas cela : cela donnera l'impression que la balle se déplace sur l'écran.

La balle va donc être affichée en blanc pendant 100 ms, puis effacée (affichée en noir), puis affichée en blanc un cran plus loin.

Modifier la procédure FormKeyPress de la manière suivante :

If Ord(Key) = VK_SPACE then
begin
Canvas.Brush.Color:=clWhite;
Canvas.Rectangle(MiLargeur, MiHauteur, MiLargeur + TailleBalle, MiHauteur
+ TailleBalle);
Sleep(100);
Canvas.Brush.Color:=clBlack;
Canvas.Rectangle(MiLargeur, MiHauteur, MiLargeur + TailleBalle, MiHauteur
+ TailleBalle);
Canvas.Brush.Color:=clWhite;
Canvas.Rectangle(MiLargeur + 20, MiHauteur + 20, MiLargeur + TailleBalle
+ 20, MiHauteur + TailleBalle + 20);
end;

Vérifier le bon fonctionnement en appuyant sur F9  !

La balle se déplace : en fait la balle apparaît en blanc, est effacée (donc coloriée en noir), puis réapparaît quelques pixels plus à droite et plus en bas.

4e phase : automatiser le déplacement de la balle

Nous allons maintenant créer une procédure qui dessine la balle.

Dans la section private, déclarer la procédure DessineBalle :

procedure DessineBalle (const XNew, YNew: Integer);

En positionnant le curseur sur ce que l'on vient d'écrire, et un tapant au clavier en même temps CTRL + SHIFT + C, le code de la procédure se crée automatiquement :

procedure TForm1.DessineBalle(const XNew, YNew: Integer);
begin
end;

Nous reviendrons plus tard sur cette procédure.

Ensuite, il faut déclarer deux nouvelles variables entières PosBalleX et PosBalleY dans la section private, juste avant la procédure DessineBalle (et après la déclaration des variables MiHauteur et MiLargeur) :

Private
MiHauteur: integer;
MiLargeur: integer;
PosBalleX: Integer;
PosBalleY: Integer;
procedure DessineBalle (const XNew, YNew: Integer);

Puis il faut initialiser les variables, dans la procédure FormCreate, en rajoutant ceci (entre begin et end) :

PosBalleX:= 0;
PosBalleY:= 0;

La procédure FormCreate devient donc :

procedure TForm1.FormCreate(Sender: TObject);
begin
Color:= clBlack;
Height:= HauteurJeu;
Width:= LargeurJeu;
Position:= poScreenCenter;
BorderStyle:= bsNone;
PosBalleX:= 0;
PosBalleY:= 0;
end;

Il reste à compléter la procédure DessineBalle avec le code suivant :

procedure TForm1.DessineBalle (const XNew, YNew: Integer);
begin
Canvas.Brush.Color:=clBlack;
Canvas.Rectangle(PosBalleX, PosBalleY, PosBalleX + TailleBalle,PosBalleY +
TailleBalle);
Canvas.Brush.Color:=clWhite;
Canvas.Rectangle(XNew,YNew,Xnew + TailleBalle,YNew + TailleBalle);
PosBalleX:= XNew;
PosBalleY:= YNew;
end;

PosBalleX et PosBalleY sont des variables globales (valables dans tout le programme), qui contiennent la position courante de la balle.

Xnew et Ynew sont les positions futures de la balle (celle que l'on va dessiner dans la procédure DessineBalle).

À chaque fois que l'on appelle la procédure DessineBalle, la balle est effacée (coloriée en noir), puis retracée un peu plus loin. On mémorise dans les variables PosBalleX et PosBalleY la nouvelle position de la balle.

Vérifier le bon fonctionnement en appuyant sur F9  !

5e phase : l'ordinateur gère le déplacement de la balle

L'ordinateur va désormais gérer le déplacement de la balle. Pour cela il faut ajouter un timer (onglet System/TTimer).

Un timer, c'est comme un chronomètre, qui décompte le temps, et qui va permettre à intervalle régulier de déclencher un événement (le déplacement de la balle).

Sélectionner TTimer et cliquer dans la fenêtre Form1 pour faire apparaître le timer.

Nous allons changer son nom Timer1 en tmrMvtBalle (dans l'inspecteur d'objet, choisir l'objet TTimer, puis l'onglet Propriétés, et la propriété Name).

Nous allons aussi passer la propriété Enabled à False : le timer ne sera actif que lorsque le jeu débutera.

Enfin, nous réglons l'intervalle de temps du timer sur 100 ms (à la place de 1000) : inspecteur d'objet, onglet Propriétés, propriété Interval.

Sélectionner l'onglet Événements, et activer l'événement OnTimer (cliquer sur les trois points).

La procédure correspondante (TForm1.tmrMvtBalle) est automatiquement créée.

Nous allons maintenant faire se déplacer la balle automatiquement, en modifiant comme suit la procédure TForm1.FormKeyPress (remplacer tout ce qui est entre begin et end; par les deux lignes ci-dessous) :

if ord(Key) = VK_SPACE then
begin
DessineBalle (MiLargeur,MiHauteur);
tmrMvtBalle.Enabled:= True;
end;

Lorsqu'on lance le jeu (appui sur barre d'espace), le timer se met en route, et la procédure DessineBalle est activée : la balle est dessinée au centre de l'écran.

Pour avoir un déplacement automatique selon l'axe x (horizontal) de la balle, insérer dans la procédure TForm1.tmrMvtBalle la ligne suivante :

procedure TForm1.tmrMvtBalleTimer(Sender: TObject);
begin
DessineBalle (PosBalleX + 10, PosBalleY);
end;

À chaque impulsion du timer, la fonction DessineBalle sera activée, et la balle sera redessinée sur l'écran avec un changement de position de 10 pixels suivant l'axe x.

Vérifier le bon fonctionnement en appuyant sur F9  !

La balle se déplace toute seule vers la droite. Mais, problème, la balle sort de l'écran...

6e phase : faire rebondir la balle sur les côtés

Ajouter une variable de type entière pour gérer la direction de la balle : Direction.

Il faut rajouter la déclaration de la variable après le mot clé private :

private
MiHauteur: integer;
MiLargeur: integer;
PosBalleX: Integer;
PosBalleY: Integer;
Direction: Integer;
procedure DessineBalle (const XNew, YNew: Integer);

Puis il faut initialiser cette variable dans la procédure TForm1.FormCreate :

procedure TForm1.FormCreate(Sender: TObject);
begin
…
PosBalleX := 0;
PosBalleY := 0;
Direction := 1;
end;

Il faut ensuite modifier la procédure TForm1.tmrMvtBalleTimer :

procedure TForm1.tmrMvtBalleTimer(Sender: TObject);
begin
DessineBalle (PosBalleX + 10 * Direction, PosBalleY);
end ;

Enfin, il faut gérer la valeur de Direction, pour faire rebondir la balle de chaque côté, en modifiant une nouvelle fois cette procédure :

procedure TForm1.tmrMvtBalleTimer(Sender: TObject);
begin
DessineBalle (PosBalleX + 10 * Direction, PosBalleY);
If (PosBalleX<=0) or (PosBalleX >= (LargeurJeu - TailleBalle)) then
Direction:= Direction * -1;
end;

Si la balle touche à gauche : PosBalleX <= 0.

Si la balle touche à droite : PosBalleX >= (LargeurJeu - TailleBalle), ceci pour tenir compte de l'épaisseur de la balle (rappel : TailleBalle = 15 pixels).

Lorsque la balle touche un des bords latéraux, on change la direction de déplacement en multipliant par (-1) la variable Direction.

Vérifier le bon fonctionnement en appuyant sur F9  !

Étape 3 : Gestion des rebonds

La gestion des rebonds de la balle s'effectue en quatre phases :

- calcul des coordonnées de déplacement de la balle ;

- préparation à la gestion des rebonds ;

- gestion du rebond latéral ;

- gestion des rebonds supérieur et inférieur.

1re phase : calcul des coordonnées de déplacement de la balle

(x1, y1) est la position initiale de la balle, (x2, y2) est la position finale de la balle.

La balle se déplace selon un angle de déplacement nommé Direction, et à une vitesse de déplacement nommée Vitesse.

2e phase : préparation à la gestion des rebonds

Nous allons créer et initialiser une variable Vitesse, qui permettra de gérer facilement la vitesse de déplacement de la balle.

Comme d'habitude, on déclare la variable dans la partie private :

Private
...
PosBalleX: Integer;
PosBalleY: Integer;
Direction: Integer;
Vitesse: Integer;
...

Et on l'initialise dans la procédure FormCreate :

procedure TForm1.FormCreate(Sender: TObject);
begin
...
PosBalleX:= 0;
PosBalleY:= 0;
Direction:= 25;
Vitesse:= 20;
end;

Ne pas oublier de changer la valeur de Direction.

Sous Lazarus, le sinus et le cosinus se calculent pour un angle exprimé en radians.

Rappel : conversion de degrés en radians :

180 ° = ? radians ;

donc 1 ° = ? / 180 radians.

Donc, pour calculer le cosinus de l'angle Direction, je vais devoir écrire l'équation suivante :

cos(Pi / 180 * Direction)

La modification du programme s'effectue un peu plus loin...

3e phase : gestion du rebond latéral

Voici une figure permettant de déterminer l'angle de rebond latéral :

La balle arrive sur le mur avec un angle égal à Direction. L'angle de rebond sera donc égal à Direction + (90 - Direction) * 2.

Il faut donc modifier la procédure tmrMvtBalle de la manière suivante :

procedure TForm1.tmrMvtBalleTimer(Sender: TObject);
Var
XChange: Integer;
YChange: Integer;
begin
XChange:= trunc(cos(Pi / 180 * Direction) * Vitesse);
YChange:= trunc(sin(Pi / 180 * Direction) * Vitesse);
DessineBalle (PosBalleX + XChange,PosBalleY + YChange);
If (PosBalleX<=0) or (PosBalleX >= (LargeurJeu - TailleBalle)) then
Direction:= Direction + (90 - Direction) * 2 ;
end;

Attention : begin est précédé de la déclaration des variables locales.

Xchange et Ychange sont des variables locales, qui n'existent que dans la procédure TmrMvtBalleTimer.

Elles stockent les composantes du "vecteur déplacement" de la balle, suivant l'axe x et l'axe y.

Trunc est une instruction qui renvoie la partie entière d'un résultat :

Trunc (12,56) renvoie 12.

En effet, Xchange et Ychange étant des entiers, ils ne peuvent stocker qu'un résultat de type entier.

Vérifier le bon fonctionnement en appuyant sur F9  !

Vous pouvez faire plusieurs essais, en changeant la valeur de Direction dans la procédure FormCreate, ainsi qu'éventuellement celle de Vitesse.

Il reste à gérer le rebond sur les côtés haut et bas de la zone graphique...

4e phase : gestion des rebonds supérieur et inférieur

Voici une figure permettant de déterminer l'angle de rebond supérieur et inférieur :

La balle arrive sur le mur avec un angle égal à Direction. L'angle de rebond sera donc égal à Direction + (180 - Direction) * 2.

Il faut donc modifier la procédure tmrMvtBalle ainsi :

procedure TForm1.tmrMvtBalleTimer(Sender: TObject);
Var
XChange: Integer;
YChange: Integer;
begin
XChange:= trunc(cos(Pi / 180 * Direction) * Vitesse);
YChange:= trunc(sin(Pi / 180 * Direction) * Vitesse);
DessineBalle (PosBalleX + XChange,PosBalleY + YChange);
If (PosBalleX<=0) or (PosBalleX >= (LargeurJeu - TailleBalle)) then
Direction:= Direction + (90 - Direction) * 2
else if (PosBalleY<=0) or(PosBalleY >= (HauteurJeu - TailleBalle)) then
Direction:= Direction + (180 - Direction) *2
end;

Vérifier le bon fonctionnement en appuyant sur F9  !

Le rebond de la balle est désormais géré sur tous les côtés de la fenêtre graphique du jeu.

Étape 4 : Création et gestion des raquettes

La création et la gestion des raquettes s'effectuent en cinq phases :

- création de la raquette droite ;

- gestion du déplacement de la raquette droite ;

- création et gestion du déplacement de la raquette gauche ;

- limiter les déplacements verticaux des raquettes ;

- gestion du rebond sur la raquette.

1re phase : création de la raquette droite

Nous avons besoin de stocker dans une constante la hauteur de la raquette (RaqHauteur), et dans une autre constante la valeur du décalage de la raquette (DecalageRaquette).

Déclarer cette constante dans la zone const :

...
RaqHauteur = 80;
DecalageRaquette = 30;

De même, déclarer la variable PosRaqDrY dans la zone private :

PosRaqDrY: Integer;

Cette variable stockera la position verticale (axe Y) de la raquette droite.

Initialiser la variable PosRaqDrY dans la procédure FormCreate :

PosRaqDrY:=0;

Déclarer la nouvelle procédure dans la zone private (en dessous de la déclaration de la procédure DessineBalle) :

procedure DessineRaquetteDroite (const YNew: Integer);

Puis construire la procédure (avec les touches CTRL + SHIFT + C).

Compléter la procédure DessineRaquetteDroite de la manière suivante :

procedure TForm1.DessineRaquetteDroite (const YNew: Integer);
const
RaqDrXPos = LargeurJeu - DecalageRaquette - TailleBalle;
begin
Canvas.Brush.Color:=clBlack;
Canvas.Rectangle(RaqDrXPos, PosRaqDrY, RaqDrXPos + TailleBalle, PosRaqDrY + RaqHauteur);
Canvas.Brush.Color:=clWhite;
Canvas.Rectangle(RaqDrXPos , YNew, RaqDrXPos + TailleBalle, YNew + RaqHauteur);
PosRaqDrY:= YNew;
end;

La procédure FormKeyPress devient :

procédure TForm1.FormKeyPress(Sender: TObject; var Key: char);
begin
MiHauteur:= HauteurJeu div 2;
MiLargeur:= LargeurJeu div 2;
if ord(Key) = VK_ESCAPE then
Close;
if ord(Key) = VK_SPACE then
begin
DessineBalle (MiLargeur, MiHauteur);
tmrMvtBalle.Enabled:=True;
DessineRaquetteDroite (MiHauteur);
end;
end;

Vérifier cette phase en appuyant sur F9  !

La raquette droite est dessinée :

2e phase : gérer le déplacement de la raquette droite

Les touches M et K définissent le déplacement de la raquette droite verticalement : un appui sur la touche M fait monter la raquette droite, un appui sur la touche K la fait descendre.

Le déplacement de la raquette se fait dans la procédure FormKeyPress :

procédure TForm1.FormKeyPress(Sender: TObject; var Key: char);
begin
MiHauteur:= HauteurJeu div 2;
MiLargeur:= LargeurJeu div 2;
if ord(Key) = VK_ESCAPE then
Close;
if ord(Key) = VK_SPACE then
begin
DessineBalle (MiLargeur, MiHauteur);
tmrMvtBalle.Enabled:=True;
DessineRaquetteDroite (MiHauteur);
end;
if Key in ['m', 'M'] then
DessineRaquetteDroite(PosRaqDrY + 20);
if Key in ['k', 'K'] then
DessineRaquetteDroite(PosRaqDrY - 20);
end;

Vérifier cette phase en appuyant sur F9  !

3e phase : faire de même pour la raquette gauche

De même déclarer la variable PosRaqGaY dans la zone private :

PosRaqGaY: Integer;

Cette variable stockera la position verticale (axe Y) de la raquette gauche.

Initialiser la variable PosRaqGaY dans la procédure FormCreate :

PosRaqGaY:=0;

Déclarer la nouvelle procédure dans la zone private :

procedure DessineRaquetteGauche (const YNew: Integer);

Puis construire la procédure (CTRL + SHIFT + C), et compléter le code :

procedure TForm1.DessineRaquetteGauche (const YNew: Integer);
const
RaqGaXPos = DecalageRaquette;
begin
Canvas.Brush.Color:=clBlack;
Canvas.Rectangle(RaqGaXPos, PosRaqGaY, RaqGaXPos + TailleBalle, PosRaqGaY
+ RaqHauteur);
Canvas.Brush.Color:=clWhite;
Canvas.Rectangle( RaqGaXPos , YNew, RaqGaXPos + TailleBalle, YNew +
RaqHauteur);
PosRaqGaY:= YNew;
end;

La procédure FormKeyPress devient :

procédure TForm1.FormKeyPress(Sender: TObject; var Key: char);
begin
MiHauteur:= HauteurJeu div 2;
MiLargeur:= LargeurJeu div 2;
if ord(Key) = VK_ESCAPE then
Close;
if ord(Key) = VK_SPACE then
begin
DessineBalle (MiLargeur, MiHauteur);
tmrMvtBalle.Enabled:=True;
DessineRaquetteDroite (MiHauteur);
DessineRaquetteGauche (MiHauteur);
end;
end;

Vérifier cette phase en appuyant sur F9  !

Régler alors le déplacement de la raquette gauche.

Les touches F et S définissent le déplacement de la raquette gauche verticalement.

Le déplacement de la raquette se fait dans la procédure FormKeyPress :

procédure TForm1.FormKeyPress(Sender: TObject; var Key: char);
begin
MiHauteur:= HauteurJeu div 2;
MiLargeur:= LargeurJeu div 2;
if ord(Key) = VK_ESCAPE then
Close;
if ord(Key) = VK_SPACE then
Begin
DessineBalle (MiLargeur, MiHauteur);
tmrMvtBalle.Enabled:=True;
DessineRaquetteDroite (MiHauteur);
DessineRaquetteGauche (MiHauteur);
end;
if Key in ['m', 'M'] then
DessineRaquetteDroite(PosRaqDrY + 20);
if Key in ['k', 'K'] then
DessineRaquetteDroite(PosRaqDrY - 20);
if Key in ['f', 'F'] then
DessineRaquetteGauche(PosRaqGaY + 20);
if Key in ['s', 'S'] then
DessineRaquetteGauche(PosRaqGaY - 20);
End;

Vérifier cette phase en appuyant sur F9  !

4e phase : limiter le déplacement des raquettes

Il faut modifier le code de la procédure DessineRaquetteDroite :

procedure TForm1.DessineRaquetteDroite (const YNew: Integer);
const
RaqDrXPos = LargeurJeu - DecalageRaquette - TailleBalle;
begin
if (YNew <= 0) or (YNew + RaqHauteur >= HauteurJeu) then
Exit;
Canvas.Brush.Color:=clBlack;
Canvas.Rectangle(RaqDrXPos, PosRaqDrY, RaqDrXPos + TailleBalle, PosRaqDrY + RaqHauteur);
Canvas.Brush.Color:=clWhite;
Canvas.Rectangle(RaqDrXPos , YNew, RaqDrXPos + TailleBalle, YNew + RaqHauteur);
PosRaqDrY:= YNew;
end;

Faire de même pour la procédure DessineRaquetteGauche :

procedure TForm1.DessineRaquetteGauche (const YNew: Integer);
const
RaqGaXPos = DecalageRaquette;
begin
if (YNew <= 0) or (YNew + RaqHauteur >= HauteurJeu) then
Exit;
Canvas.Brush.Color:=clBlack;
Canvas.Rectangle(RaqGaXPos, PosRaqGaY, RaqGaXPos + TailleBalle, PosRaqGaY + RaqHauteur);
Canvas.Brush.Color:=clWhite;
Canvas.Rectangle( RaqGaXPos , YNew, RaqGaXPos + TailleBalle, YNew + RaqHauteur);
PosRaqGaY:= YNew;
end;

Vérifier cette phase en appuyant sur F9  !

Les raquettes ne sortent plus de l'écran.

5e phase : gestion du rebond sur la raquette

Il faut rajouter un test dans la procédure tmrMvtBalleTimer.

Pour la raquette droite :

else if
(PosBalleX + TailleBalle >= LargeurJeu - DecalageRaquette - TailleBalle) and
(PosBalleY >= PosRaqDrY) and
(PosBalleY + TailleBalle <= PosRaqDrY + RaqHauteur) then
Direction:= Direction + (90 - Direction) * 2

Et faire de même pour la raquette gauche :

else if
(PosBalleX <= 30 + 15) and
(PosBalleY >= PosRaqGaY) and
(PosBalleY + 15 <= PosRaqGaY + 80) then
Direction:= Direction + (90 - Direction) * 2;

Le code complet de la procédure est donc :

procedure TForm1.tmrMvtBalleTimer(Sender: TObject);
Var
XChange: Integer;
YChange: Integer;
begin
Xchange:= trunc(cos(Pi / 180 * Direction) * Vitesse);
Ychange:= trunc(sin(Pi / 180 * Direction) * Vitesse);
DessineBalle (PosBalleX + XChange,PosBalleY + Ychange);
If (PosBalleX<=0) or (PosBalleX >= (LargeurJeu - TailleBalle)) then
Direction:= Direction + (90 - Direction) * 2
else if (PosBalleY<=0) or(PosBalleY >= (HauteurJeu - TailleBalle)) then
Direction:= Direction + (180 - Direction) *2
else if
(PosBalleX + TailleBalle >= LargeurJeu - DecalageRaquette - TailleBalle)
and
(PosBalleY >= PosRaqDrY) and
(PosBalleY + TailleBalle <= PosRaqDrY + RaqHauteur) then
Direction:= Direction + (90 - Direction) * 2
else if
(PosBalleX <= DecalageRaquette + TailleBalle) and
(PosBalleY >= PosRaqGaY) and
(PosBalleY + TailleBalle <= PosRaqGaY + RaqHauteur) then
Direction:= Direction + (90 - Direction) * 2;
end;

Vérifier cette phase en appuyant sur F9  !

La balle rebondit sur les deux raquettes.

Étape 5 : gestion du score

La gestion du score se fait en plusieurs étapes :

- créer les variables contenant le score ;

- initialisation des variables quand le jeu débute ;

- détection d'un échec et incrémentation du score ;

- affichage du score sur l'écran ;

- quand le score d'un joueur atteint 10, annoncer le vainqueur.

Attention : beaucoup de code à taper dans cette partie, avant de pouvoir tester le fonctionnement : soyez attentif !

1re phase : création des variables

Dans la section private, ajouter les deux variables suivantes :

ScoreGauche: integer;
ScoreDroit: integer;

2e phase : initialisation des variables

Ajouter le code suivant dans la procédure FormCreate :

ScoreGauche:= 0;
ScoreDroit:= 0;

3e phase : détection d'un échec et incrémentation du score

La détection se fait dans la procédure tmrMvtBalle : lorsque la balle va trop à gauche (PosBalleX <= 0), on incrémente le compteur du joueur de droite, et lorsque la balle va trop à droite (PosBalleX >= LargeurJeu - TailleBalle), on incrémente le compteur du joueur de gauche.

L'incrémentation se fait à l'aide de l'instruction Inc.

Exemple :

Inc (ScoreGauche);

Il faut ensuite relancer la balle pour un nouveau service : ce sera fait par une procédure appelée NouveauService.

Il faut la déclarer dans la section private :

procedure NouveauService;

Avec CTRL + SHIFT + C, on crée le code suivant :

procedure TForm1.NouveauService;
begin
end;

Dans cette procédure, nous allons intégrer le code nécessaire au service d'une nouvelle balle.

Il faut d'abord arrêter le timer (la balle n'a plus besoin de bouger temporairement) :

tmrMvtBalle.Enabled:= False;

Il faudra penser à le remettre en route à la fin de la procédure.

On redessine la balle et les raquettes droite et gauche :

DessineBalle (MiLargeur, MiHauteur);
DessineRaquetteDroite (MiHauteur);
DessineRaquetteGauche (MiHauteur);

Il faut ensuite afficher le score, ce qui sera fait par une autre procédure nommée AfficheScore.

Une petite pause (1s) sera nécessaire pour bien visualiser le score à l'écran :

sleep(1000);

La procédure NouveauService sera appelée dans la procédure tmrMvtBalle, en ajoutant du code à la fin de la manière suivante :

procedure TForm1.tmrMvtBalleTimer(Sender: TObject);
Var
XChange: Integer;
YChange: Integer;
begin
Xchange:= trunc(cos(Pi / 180 * Direction) * Vitesse);
Ychange:= trunc(sin(Pi / 180 * Direction) * Vitesse);
DessineBalle (PosBalleX + XChange,PosBalleY + Ychange);
If (PosBalleX<=0) or (PosBalleX >= (LargeurJeu - TailleBalle)) then
Direction:= Direction + (90 - Direction) * 2
else if (PosBalleY<=0) or(PosBalleY >= (HauteurJeu - TailleBalle)) then
Direction:= Direction + (180 - Direction) *2
else if
(PosBalleX + TailleBalle >= LargeurJeu - DecalageRaquette - TailleBalle) and (PosBalleY >= PosRaqDrY) and (PosBalleY + TailleBalle <= PosRaqDrY + RaqHauteur) then
Direction:= Direction + (90 - Direction) * 2
else if
(PosBalleX <= DecalageRaquette + TailleBalle) and (PosBalleY >= PosRaqGaY) and (PosBalleY + TailleBalle <= PosRaqGaY + RaqHauteur) then
Direction:= Direction + (90 - Direction) * 2;
If (PosBalleX<=0) then
begin
Inc (ScoreDroit);
NouveauService;
end
else if (PosBalleX >= (LargeurJeu - TailleBalle)) then
begin
Inc (ScoreGauche);
NouveauService;
end
end;

Donc la procédure NouveauService complète est la suivante :

procedure TForm1.NouveauService;
begin
If (ScoreDroit >= 10) OR (ScoreGauche >= 10) then
AnnoncerGagnant
else
begin
tmrMvtBalle.Enabled:= False;
DessineBalle (MiLargeur,MiHauteur);
DessineRaquetteDroite (MiHauteur);
DessineRaquetteGauche(MiHauteur);
AfficheScore;
sleep(1000);
tmrMvtBalle.Enabled:= True;
end;
end;

4e phase : affichage du score sur l'écran

C'est l'objectif de la procédure AfficheScore.

Il faut déclarer la procédure dans la section private :

procedure AfficheScore;

puis cliquer sur CTRL + SHIFT + C pour créer le code de la procédure :

procedure TForm1.AfficheScore;
begin
end;

Pour afficher le score, nous utiliserons l'instruction Canvas.TextOut de la manière suivante :

Canvas.TextOut (MiLargeur - 50, 30, IntToStr(ScoreGauche));

Cela affiche un texte, un peu avant le milieu de l'écran (à la position 400 - 50 pixels en x), un peu plus bas que le sommet de l'écran du jeu (30 pixels).

L'instruction IntToStr convertit le contenu de ScoreGauche (qui en un nombre entier) en une chaîne de caractères (String) : l'instruction Canvas.TextOut n'affiche en effet que des caractères, pas des nombres.

De même, pour le score de droite :

Canvas.TextOut (MiLargeur + 50, 30, IntToStr(ScoreDroit));

Afin d'avoir un affichage visible, nous ajouterons avant l'instruction Canvas.TextOut les instructions suivantes :

Canvas.Font.Name:= 'Courier New';
Canvas.Font.Size:= 24;
Canvas.Font.Style:=[fsBold];
Canvas.Font.Color:= clWhite;
Canvas.Brush.Color:= clBlack;

Ceci sélectionnera une police « Courier New », de taille 24, en gras, de couleur blanche, sur fond noir.

La procédure AfficheScore complète est donc ainsi :

procedure TForm1.AfficheScore;
begin
Canvas.Font.Name:= 'Courier New';
Canvas.Font.Size:= 24;
Canvas.Font.Style:=[fsBold];
Canvas.Font.Color:= clWhite;
Canvas.Brush.Color:= clBlack;
Canvas.TextOut (MiLargeur - 50, 30, IntToStr(ScoreGauche));
Canvas.TextOut (MiLargeur + 50, 30, IntToStr(ScoreDroit));
end;

Pour afficher le score sur l'écran, il faut rajouter la ligne suivante au début du jeu (procédure FormKeyPress) et à chaque nouveau service (procédure NouveauService) :

AfficheScore;

Voici la modification dans la procédure FormKeyPress :

If Ord(Key) = VK_SPACE then
Begin
DessineBalle(MiLargeur, MiHauteur);
tmrMvtBalle.Enabled:=True;
DessineRaquetteDroite (MiHauteur);
DessineRaquetteGauche (MiHauteur);
AfficheScore;
End;

Et voici la modification dans la procédure NouveauService :

else
begin
tmrMvtBalle.Enabled:= False;
DessineBalle (MiLargeur,MiHauteur);
DessineRaquetteDroite (MiHauteur);
DessineRaquetteGauche(MiHauteur);
AfficheScore;
sleep(1000);
tmrMvtBalle.Enabled:= True;
end;

5e phase : annoncer le vainqueur

Le test pour savoir s'il y a un vainqueur se fait dans la procédure NouveauService. S'il y a un vainqueur, il faudra gérer cela, sinon, le jeu continue.

If (ScoreDroit >= 10) OR (ScoreGauche >= 10) then
AnnoncerGagnant
else
begin
tmrMvtBalle.Enabled:= False;
DessineBalle (MiLargeur,MiHauteur);
DessineRaquetteDroite (MiHauteur);
DessineRaquetteGauche(MiHauteur);
AfficheScore;
sleep(1000);
tmrMvtBalle.Enabled:= True;
end;

Dans la phase de débogage du jeu, il peut être judicieux de limiter le score à 3 (à la place de 10).

Il faut donc déclarer la procédure AnnoncerGagnant dans la zone private :

procedure AnnoncerGagnant;

puis cliquer sur CTRL+SHIFT+C pour créer le code correspondant à cette procédure :

procedure TForm1.AnnoncerGagnant;
Begin
End;

Nous allons ajouter entre Begin et End le code suivant (formatage du texte) :

Canvas.Font.Name:= 'Courier New';
Canvas.Font.Size:= 48;
Canvas.Font.Style:=[fsBold];
Canvas.Font.Color:= clWhite;
Canvas.Brush.Color:= clBlack;

Puis, suivant le gagnant, nous allons écrire le mot « Gagnant » sous le score :

if ScoreGauche >= 10 then
Canvas.TextOut (MiLargeur - 50 - Canvas.TextWidth('Gagnant'), 100, 'Gagnant')
else
Canvas.TextOut (MiLargeur + 50, 100, 'Gagnant');

L'instruction Canvas.TextWidth('Gagnant') calcule la largeur du mot « Gagnant » (afin de le décaler correctement par rapport au centre de l'écran).

La procédure AnnoncerGagnant complète est donc la suivante :

procedure TForm1.AnnoncerGagnant;
begin
tmrMvtBalle.Enabled:= False;
Canvas.Font.Name:= 'Courier New';
Canvas.Font.Size:= 48;
Canvas.Font.Style:=[fsBold];
Canvas.Font.Color:= clWhite;
Canvas.Brush.Color:= clBlack;
if ScoreGauche >= 10 then
Canvas.TextOut (MiLargeur - 50 - Canvas.TextWidth('Gagnant'), 100, 'Gagnant')
else
Canvas.TextOut (MiLargeur + 50, 100, 'Gagnant');
end;

Vérifier (enfin) cette phase en appuyant sur F9  !

Étape 6 : Dessiner le filet

Pour dessiner le filet, nous allons créer une boucle de programmation qui dessine une ligne pointillée verticale au centre de l'écran.

1re phase : déclaration de la procédure

Dans la section private, déclarer la procédure :

procedure DessineFilet;

2e phase : création de la procédure

Avec le curseur sur la ligne créée en phase 1, taper CTRL + SHIFT + C pour créer le code de la procédure :

procedure TForm1.DessineFilet;
begin
end;

3e phase : création du code

procédure TForm1.DessineFilet
var
VarY: integer;
begin
Canvas.Brush.Color:= ClBlack;
Canvas.Rectangle (0,0,LargeurJeu,HauteurJeu);
Canvas.Pen.Color:= clWhite;
VarY:= 0;
While VarY <= HauteurJeu do
begin
Canvas.Line(MiLargeur, VarY, MiLargeur, VarY + 20);
Inc (VarY, 40);
end;
Canvas.Pen.Color:= clBlack;
end;

La procédure efface complètement l'écran (Canvas.Rectangle) puis dessine en blanc des pointillés de longueur 20 pixels espacés de 40 pixels.

4e phase : appel de la procédure

Dans la procédure FormKeyPress, ajouter les instructions manquantes, pour afficher le filet au début du jeu :

If Ord(Key) = VK_SPACE then
begin
DessineFilet;
AfficheScore;
DessineBalle(MiLargeur, MiHauteur);
tmrMvtBalle.Enabled:=True;
DessineRaquetteDroite (MiHauteur);
DessineRaquetteGauche (MiHauteur);
end;

Il faut aussi afficher le score et le filet à chaque nouveau service :

procedure TForm1.NouveauService;
begin
tmrMvtBalle.Enabled:= False;
DessineFilet;
AfficheScore;
If (ScoreDroit >= 10) OR (ScoreGauche >= 10) then
AnnoncerGagnant
else
begin
DessineBalle (MiLargeur,MiHauteur);
DessineRaquetteDroite (MiHauteur);
DessineRaquetteGauche(MiHauteur);
sleep(500);
tmrMvtBalle.Enabled:= True;
end;
end;

Vérifier cette phase en appuyant sur F9  !

Le filet est dessiné !

Étape 7 : Corriger les problèmes d'effacement

Lorsque la balle circule sur le terrain, il apparaît un problème d'effacement de l'écran (effacement du score, du filet…). Voici comment gérer cela.

Le principe est de sauvegarder la portion d'écran où sera la balle (procédure EnregistrerEcran), puis de déplacer la balle et de restaurer l'écran (procédure RestaurerEcran) comme il était à l'endroit que vient de quitter la balle.

Cela se réalise à l'aide d'un objet BitMap qui stocke le contenu de la portion d'écran correspondant à la balle, le tout en six phases :

- déclaration des procédures ;

- création des procédures ;

- gestion de l'objet BitMap ;

- création du code de la procédure EnregistrerEcran ;

- création du code de la procédure RestaurerEcran ;

- appel des procédures.

1re phase : déclaration des procédures

Dans la zone private, déclarer les deux procédures suivantes :

procedure RestaurerEcran (const VarX,VarY: integer);
procedure EnregistrerEcran (const VarX,VarY: integer);

2e phase : création des procédures

Créer le code des deux procédures (CTRL + SHIFT + C) :

procedure TForm1.RestaurerEcran (const VarX, VarY: integer);
Begin
End;

procédure TForm1.EnregistrerEcran (const VarX, VarY: integer);
Begin
End;

3e phase : gestion de l'objet BitMap

Déclarer l'objet Bitmap dans la zone private (avant les déclarations de procédures) :

SauveEcran: TBitMap;

À la fin de la procédure FormCreate (juste avant le end;), rajouter ceci :

SauveEcran:= TBitMap.Create;
SauveEcran.SetSize(TailleBalle, TailleBalle);

Il faut penser à créer la procédure de destruction de l'objet BitMap.

Pour cela, il faut aller dans l'inspecteur d'objet, sélectionner l'objet Form1, sélectionner l'onglet Événements, et choisir l'événement OnDestroy (cliquer sur les trois points à côté pour créer la procédure).

Puis compléter le code comme ci-dessous (entre begin et end) :

procedure TForm1.FormDestroy(Sender: TObject);
Begin
SauveEcran.Free;
End;

4e phase : création du code de la procédure EnregistrerEcran

Compléter le code de la procédure EnregistrerEcran :

procedure TForm1.EnregistrerEcran (const VarX, VarY: integer);
Begin
SauveEcran.Canvas.CopyRect(
Rect(0, 0, TailleBalle, TailleBalle),
Canvas,
Rect(VarX, VarY, VarX+TailleBalle, VarY+TailleBalle));
End;

5e phase : création du code de la procédure RestaurerEcran

Compléter le code de la procédure RestaurerEcran :

procedure TForm1.RestaurerEcran (const VarX, VarY: integer);
Begin
Canvas.Draw (VarX, VarY, SauveEcran);
End;

6e phase : appel des procédures

Ajouter le code suivant au début de la procédure DessineBalle :

RestaurerEcran(PosBalleX, PosBalleY);
EnregistrerEcran(XNew, Ynew);

Ainsi, on restaure l'écran tel qu'il était avant que la balle passe, et on sauve l'écran là où sera la balle.

Enfin, effacer les deux lignes suivantes :

Canvas.Brush.Color:=clBlack;
Canvas.Rectangle(PosBalleX, PosBalleY, PosBalleX + TailleBalle,PosBalleY + TailleBalle);

La procédure DessineBalle devient donc :

procedure TForm1.DessineBalle(const XNew, YNew: Integer);
begin
RestaurerEcran(PosBalleX, PosBalleY);
EnregistrerEcran(XNew, Ynew);
Canvas.Brush.Color:=clWhite;
Canvas.Rectangle(XNew,YNew,Xnew + TailleBalle,YNew + TailleBalle);
PosBalleX:= XNew;
PosBalleY:= YNew;
end;

Vérifier le bon fonctionnement en appuyant sur F9  !

Le filet ne disparaît plus lors du passage de la balle.

Étape 8 : Écrire les instructions de jeu

Cette étape se fera en quatre phases :

- déclaration de la procédure ;

- création de la procédure ;

- création du code de la procédure ;

- appel de la procédure.

1re phase : déclaration de la procédure

Dans la section private, déclarer la procédure :

procedure AfficherInstructions;

2e phase : création de la procédure

Avec le curseur sur la ligne créée en phase 1, taper CTRL + SHIFT + C pour créer le code de la procédure :

procedure TForm1. AfficherInstructions;
Begin
End;

3e phase : création du code

Compléter le code de la procédure AfficherInstructions :

procedure TForm1. AfficherInstructions;
Begin
Canvas.Font.Name:= 'Courier New';
Canvas.Font.Size:= 24;
Canvas.Font.Style:=[fsBold];
Canvas.Font.Color:= clWhite;
Canvas.Brush.Color:= clBlack;
Canvas.Rectangle (140, 240, 700, 450);
Canvas.Pen.Color:= clBlack ;
Canvas.TextOut (150, 250, '<Espace> pour démarrer');
Canvas.TextOut (150, 300, '<Echap> pour quitter');
Canvas.TextOut (150, 350, '<F> et <S> raquette gauche');
Canvas.TextOut (150, 400, '<M> et <K> raquette droite');
End;

4e phase : appel de la procédure

La procédure AfficherInstructions sera appelée à la fin de la procédure AnnoncerGagnant (juste avant le end;) :

AfficherInstructions;

Afin d'afficher ce texte une fois aussi au début du jeu, nous allons utiliser un événement particulier de la feuille de jeu nommé OnPaint.

Pour cela, il faut aller dans l'inspecteur d'objet, sélectionner l'objet Form1, sélectionner l'onglet Événements, et choisir l'événement OnPaint (cliquer sur les trois points à côté pour créer la procédure).

Procedure TForm1.OnPaint (Sender : Tobject);
Begin
DessineFilet;
AfficherInstructions;
DessineRaquetteDroite(MiHauteur);
DessineRaquetteGauche(MiHauteur);
OnPaint:= nil;
End;

L'instruction OnPaint:= nil est là pour empêcher la réexécution de cette procédure lorsque le jeu à commencé.

Vérifier le bon fonctionnement en appuyant sur F9  !

Les instructions de jeux s'affichent.

Étape 9 : Jouer des sons

Cette étape se fera en six phases :

- télécharger les sons ;

- ajouter la bibliothèque nécessaire à la gestion du son ;

- déclaration des procédures de gestion du son ;

- création des procédures ;

- création du code des procédures ;

- appel des procédures.

1re phase : télécharger les sons

Pour télécharger les sons, rendez-vous sur cette page :

http://www.clubengineer.org/examples/pong-sounds.zip

Une fois décompressé, vous disposez de trois fichiers sons, que nous allons renommer ainsi :

- bounce.wav ? rebond.wav ;

- miss-left.wav ? perdu-gauche.wav ;

- miss-right.wav ? perdu-droite.wav.

Attention : il faut placer les fichiers sons au même endroit que le fichier programme (répertoire Pong, créé au tout début de ce tutoriel).

2e phase : ajouter la bibliothèque de gestion du son

Pour que votre programme puisse lire du son, il faut ajouter une bibliothèque qui gère le son : sous Windows, c'est la bibliothèque MMSystem.

Dans la section Implementation, nous avions déjà rajouté une bibliothèque : LCLType.

À la suite, rajouter MMSystem, pour avoir ceci (attention à la virgule et au point-virgule) :

Implementation
uses
LCLType, MMSystem;

2e phase : déclaration des procédures de gestion du son

Dans la section private, déclarer les procédures :

procedure JouerSonRebond;
procédure JouerSonPerduGauche;
procédure JouerSonPerduDroit;

3e phase : création des procédures

En se positionnant sur le nom de la procédure et en cliquant sur CTRL + SHIFT + C, les trois procédures sont créées :

procedure TForm1.JouerSonRebond;
Begin
End;

procédure TForm1.JouerSonPerduGauche;
Begin
End;

procédure TForm1.JouerSonPerduDroit;
Begin
End;

4e phase : création du code des procédures

Pour jouer un son, l'instruction est la suivante :

PlaySound ('fichierson.wav', 0, SND_ASYNC);

SND_ASYNC indique que le jeu peut continuer pendant que le son est joué : si on mettait SND_SYNC, le jeu ferait une pause le temps de la lecture du son.

Nos trois procédures deviennent donc :

procedure TForm1.JouerSonRebond;
Begin
PlaySound ('rebond.wav', 0, SND_ASYNC);
End;

procédure TForm1.JouerSonPerduGauche;
Begin
PlaySound ('perdu-gauche.wav', 0, SND_ASYNC);
End;

procédure TForm1.JouerSonPerduDroit;
Begin
PlaySound ('perdu-droite.wav', 0, SND_ASYNC);
End;

5e phase : appel des procédures

L'appel de la procédure JouerSonPerduGauche doit se faire chaque fois que la balle passe derrière la raquette gauche (PosBalleX <= 0) ; l'appel de la procédure JouerSonPerduDroit doit se faire chaque fois que la balle passe derrière la raquette droite (PosBalleX >= (LargeurJeu - TailleBalle)).

Nous allons donc appeler les procédures JouerSonPerduGauche et JouerSonPerduDroit dans la procédure tmrMvtBalle.

If (PosBalleX<=0) then
Begin
JouerSonPerduGauche;
Inc(ScoreDroit);
NouveauService;
End
else if (PosBalleX >= (LargeurJeu - TailleBalle)) then
Begin
JouerSonPerduDroit;
Inc (ScoreGauche);
NouveauService;
Direction:= Direction + (90 - Direction) * 2;
End

L'appel de la procédure JouerSonRebond doit se faire pour chaque rebond de la balle sur les côtés haut ou bas du terrain et sur les raquettes gauche ou droite.

Au final, la procédure tmrMvtBalleTimer devient (attention aux ajouts de begin-end, à l'ajout de point-virgule à la fin de certaines lignes, et à l'absence de point-virgule à la fin de certain end) :

procedure TForm1.tmrMvtBalleTimer(Sender: TObject);
Var
XChange: Integer;
YChange: Integer;
begin
Xchange := trunc(cos(Pi / 180 * Direction) * Vitesse);
Ychange := trunc(sin(Pi / 180 * Direction) * Vitesse);
DessineBalle (PosBalleX + XChange,PosBalleY + Ychange);
If (PosBalleX<=0) or (PosBalleX >= (LargeurJeu - TailleBalle)) then
begin
Direction:= Direction + (90 - Direction) * 2;
JouerSonRebond;
end
else if (PosBalleY<=0) or(PosBalleY >= (HauteurJeu - TailleBalle)) then
begin
Direction:= Direction + (180 - Direction) *2;
JouerSonRebond;
end
else if
(PosBalleX + TailleBalle >= LargeurJeu - DecalageRaquette - TailleBalle) and
(PosBalleY >= PosRaqDrY) and
(PosBalleY + TailleBalle <= PosRaqDrY + RaqHauteur) then
begin
Direction:= Direction + (90 - Direction) * 2;
JouerSonRebond;
end
else if
(PosBalleX <= DecalageRaquette + TailleBalle) and
(PosBalleY >= PosRaqGaY) and
(PosBalleY + TailleBalle <= PosRaqGaY + RaqHauteur) then
begin
Direction:= Direction + (90 - Direction) * 2;
JouerSonRebond;
end;
If (PosBalleX<=0) then
begin
JouerSonPerduGauche;
Inc (ScoreDroit);
NouveauService;
end ;
else if (PosBalleX >= (LargeurJeu - TailleBalle)) then
begin
JouerSonPerduDroit;
Inc (ScoreGauche);
NouveauService;
end
end;

Vérifier le bon fonctionnement en appuyant sur F9  !

Le son est émis à chaque rebond sur les côtés ou sur la raquette, et lorsqu'un côté ou l'autre perd.

Étape 10 : Améliorations possibles

Ce TP est maintenant terminé.

Néanmoins, il existe encore de nombreux bugs dans ce programme :

- parfois, la balle tape sur la raquette et enlève un morceau de raquette ;

- parfois, la balle rebondit de multiples fois derrière la raquette ;

- …

Des améliorations du jeu sont aussi possibles :

- rendre aléatoires l'angle et le côté de service de la balle ;

- ajouter des niveaux de jeu, de manière à le rendre plus difficile graduellement ;

- ajouter une gestion à long terme des scores des joueurs, de manière à avoir une liste des meilleurs joueurs ;

- …

Pour rendre le jeu plus difficile, il est possible de jouer sur les paramètres suivants :

- grandeur de l'aire de jeu ;

- vitesse de la balle ;

- taille de la balle ;

- largeur des raquettes.

Licence de cette documentation

Ce TP de création sous Lazarus/Free Pascal du jeu Pong est sous licence
Creative Commons BY SA :
https://creativecommons.org/licenses/by-sa/3.0/deed.fr

Ce TP a été créé par Laurent DUBETTIER-GRENIER en novembre 2013,
dans un but d'initiation à la programmation d'élèves de seconde.

Pour contacter l'auteur :
laurent[point]dubettier-grenier[arobase]ac-grenoble.fr

Ce tutoriel est une adaptation/traduction libre basée sur le tutoriel vidéo en anglais créé par le site internet :
clubengineer.org
http://www.clubengineer.org/16-online-lessons/pascal/68-game-programming-in-pascal-pong