Création d'un jeu Pong en Pascal

Sous Lazarus/Free Pascal

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.


Image non disponible

5 commentaires Donner une note à l'article (5)

Article lu   fois.

L'auteur

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

É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.

1e 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 :

Image non disponible

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 :

Image non disponible

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).

Image non disponible

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 :

Image non disponible

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éboguage

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.

Image non disponible

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.

Image non disponible

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.

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

Image non disponible

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 :

 
Sélectionnez
       Color:= clBlack;

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

Le code devient donc :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
const
       HauteurJeu = 600;
       LargeurJeu = 800;
procedure 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 !

Image non disponible

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 (…) :

Image non disponible

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

 
Sélectionnez
procedure 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 :

 
Sélectionnez
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 contenu 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; :

 
Sélectionnez
procedure 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'effectue 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.

1e 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;) :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
const
        HauteurJeu = 600;
        LargeurJeu = 800;
        TailleBalle = 15;

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

 
Sélectionnez
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 :

 
Sélectionnez
Canvas.Rectangle(400,300,415,315);

Pour simplifier le code, nous allons créer une variable de type entière 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 :

 
Sélectionnez
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 :

 
Sélectionnez
procedure 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 « = » :

 
Sélectionnez
        TailleBalle = 15;

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

 
Sélectionnez
        MiHauteur: integer;

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

 
Sélectionnez
        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.

Image non disponible

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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 variable MiHauteur et MiLargeur) :

 

delphi

 

0

1

 

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) :

 

delphi

 

0

1

 

PosBalleX:= 0;
PosBalleY:= 0;

La procédure FormCreate devient donc :

 

delphi

 

0

1

 

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 :

 
Sélectionnez
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).

A 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).

Image non disponible

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.

Image non disponible Image non disponible

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) :

 
Sélectionnez
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é 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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

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

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

 
Sélectionnez
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 coté, en modifiant une nouvelle fois cette procédure :

 
Sélectionnez
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.

1e 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.

Image non disponible

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 :

 
Sélectionnez
Private
        ...
        PosBalleX: Integer;
        PosBalleY: Integer;
        Direction: Integer;
        Vitesse: Integer;
        ...

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

 
Sélectionnez
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 :

 
Sélectionnez
        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 :

Image non disponible

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 :

 
Sélectionnez
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 procedure FormCreate, ainsi qu'éventuellement celle de Vitesse.

Il reste à gérer le rebond sur les coté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 :

Image non disponible

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 :

 
Sélectionnez
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 tout les coté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'effectue 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.

1e 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 :

 
Sélectionnez
         ...
         RaqHauteur = 80;
         DecalageRaquette = 30;

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

 
Sélectionnez
         PosRaqDrY: Integer;

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

Initialiser la variable PosRaqDrY dans la procédure FormCreate :

 
Sélectionnez
         PosRaqDrY:=0;

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

 
Sélectionnez
         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 :

 
Sélectionnez
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 :

 
Sélectionnez
procedure 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 :

Image non disponible

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 :

 
Sélectionnez
procedure 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 :

 
Sélectionnez
         PosRaqGaY: Integer;

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

Initialiser la variable PosRaqGaY dans la procédure FormCreate :

 
Sélectionnez
         PosRaqGaY:=0;

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

 
Sélectionnez
procedure DessineRaquetteGauche (const YNew: Integer);

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

 
Sélectionnez
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;

Image non disponible

La procédure FormKeyPress devient :

 
Sélectionnez
procedure 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 :

 
Sélectionnez
procedure 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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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  !

Image non disponible

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 !

1e phase : création des variables

Dans la section private, ajouter les deux variables suivantes :

 
Sélectionnez
ScoreGauche: integer;
ScoreDroit: integer;

2e phase : initialisation des variables

Ajouter le code suivant dans la procédure FormCreate :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
procedure NouveauService;

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

 
Sélectionnez
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) :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
procedure AfficheScore;

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

 
Sélectionnez
procedure TForm1.AfficheScore;
begin
end;

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

 
Sélectionnez
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 :

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

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

 
Sélectionnez
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 :

 
Sélectionnez
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) :

 
Sélectionnez
AfficheScore;

Voici la modification dans la procédure FormKeyPress :

 
Sélectionnez
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 :

 
Sélectionnez
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.

 
Sélectionnez
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 :

 
Sélectionnez
procedure AnnoncerGagnant;

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

 
Sélectionnez
procedure TForm1.AnnoncerGagnant;
Begin
End;

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

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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  !

Image non disponible

É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.

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

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

 
Sélectionnez
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 :

 
Sélectionnez
procedure TForm1.DessineFilet;
begin
end;

3e phase : création du code

 
Sélectionnez
procedure 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 :

 
Sélectionnez
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 :

 
Sélectionnez
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  !

Image non disponible

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.

1e phase : déclaration des procédures

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

 
Sélectionnez
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) :

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

procedure 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) :

 
Sélectionnez
SauveEcran: TBitMap;

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

 
Sélectionnez
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 à coté pour créer la procédure).

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

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

 
Sélectionnez
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 :

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

La procédure DessineBalle devient donc :

 
Sélectionnez
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.

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

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

 
Sélectionnez
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 :

 
Sélectionnez
procedure TForm1. AfficherInstructions;
Begin
End;

3e phase : création du code

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

 
Sélectionnez
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;) :

 
Sélectionnez
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 à coté pour créer la procédure).

 
Sélectionnez
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  !

Image non disponible

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.

1e 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.wavrebond.wav ;
  • miss-left.wavperdu-gauche.wav ;
  • miss-right.wavperdu-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) :

 
Sélectionnez
Implementation
uses
       LCLType, MMSystem;

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

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

 
Sélectionnez
procedure JouerSonRebond;
procedure JouerSonPerduGauche;
procedure 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éés :

 
Sélectionnez
procedure TForm1.JouerSonRebond;
Begin
End;

procedure TForm1.JouerSonPerduGauche;
Begin
End;

procedure TForm1.JouerSonPerduDroit;
Begin
End;

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

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

 
Sélectionnez
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 :

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

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

procedure 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.

 
Sélectionnez
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 coté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) :

 
Sélectionnez
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 cotés ou sur la raquette, et lorsqu'un coté 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 :
http://creativecommons.org/licenses/by-sa/3.0/deed.fr


Image non disponible


Ce TP a été crée 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

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Licence Creative Commons
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.