XIV. Bilan à mi-parcours▲
Faisons un petit bilan pour y voir plus clair.
Nous avons terminé :
- l'écran de menu ;
- l'écran des options.
Il nous reste :
- l'écran des scores ;
- l'écran des crédits ;
- les écrans de fin de partie (gagné / perdu) ;
- et l'écran de jeu !
On constate après tout ce temps que nous n'avons pas encore vraiment commencé le développement du jeu.
Pour l'instant on ne voit pas franchement que nous sommes en train de créer un PacMan.
Par contre tout est prêt pour continuer le développement dans de bonnes conditions.
Pour la suite du tutoriel, nous allons enfin commencer le développement de l'écran de jeu.
XV. Développement de l'écran de jeu▲
L'écran de jeu est développé dans le module clScreenGame.
Ce sera bien entendu le plus complexe des écrans du jeu.
XV-A. Création de l'écran de jeu▲
Préparez le module clScreenGame de la même manière que le module clScreenMenu au chapitre Préparation du premier écran : le menu clScreenMenu.
Pensez à gérer l'affichage et la minuterie (40 images par seconde pour le jeu).
'------------------------------------------------------------------------
' Affichage de l'écran
'------------------------------------------------------------------------
Private Function ClScreen_DisplayScreen() As Boolean
' Dessine l'image sur le formulaire
oGdi.RepaintNoFormRepaint FormGame.Img
End Function
'------------------------------------------------------------------------
' Minuterie d'attente
'------------------------------------------------------------------------
Private Function ClScreen_ScreenWait() As Boolean
' Attente de 1000 / 40 = 25 ms pour affichage de 40 images/s
oGdi.Wait 25
End FunctionIl faut ensuite gérer l'enchaînement de l'écran de jeu.
Dans la fonction GameChangeScreen du module clGame, il faut ajouter l'enchainement vers l'écran de jeu.
Ajoutez un Case au Select.
' Changement pour le jeu
Case "game"
Set oScreen = Nothing
Set oScreen = New ClScreenGameOn teste la valeur « game » car dans l'écran de menu c'est cette valeur qu'on utilise lors du click sur Commencer le jeu.
XV-B. Chargement du tableau de jeu▲
On a défini le tableau de jeu dans la feuille Jeu.
Ce tableau fait 28 colonnes et 30 lignes.
On va stocker les valeurs de ce tableau dans une variable du module clScreenGame.
' Tableau de jeu
Private gTableau(0 To 27, 0 To 29) As StringPuis on charge les données de la feuille Jeu dans ce tableau.
' Compteurs
Dim lCptY As Integer
Dim lCptX As Integer
' Charge les données du tableau
With ThisWorkbook.Sheets("Jeu")
For lCptX = 1 To 28
For lCptY = 1 To 30
gTableau(lCptX - 1, lCptY - 1) = .Cells(lCptY, lCptX).Value
Next
Next
End WithXV-C. Affichage du tableau de jeu▲
Le tableau de jeu est dessiné en fonction du contenu des cases, à base de lignes et d'arcs de cercle.
Pour rappel, le tableau de jeu a été chargé dans gTableau à partir des données de la feuille Jeu.
Pour chaque case, on va lire les valeurs des cases adjacentes pour déterminer le type de trait à dessiner.
Créons donc une petite fonction de lecture du contenu d'une case.
'---------------------------------------------------------------------------------------
' Lecture du contenu d'une case
'---------------------------------------------------------------------------------------
' pX,pY : Coordonnées de la case
' La fonction renvoie le contenu de la case dans un string
' Si on dépasse le tableau, renvoit ""
'---------------------------------------------------------------------------------------
Private Function GetValue(pX As Integer, pY As Integer) As String
On Error Resume Next
' Lecture de la case dans le tableau représentant le niveau
GetValue = gTableau(pX, pY)
If Err.Number <> 0 Then GetValue = "" ' Renvoit une chaîne vide si erreur
End FunctionCette fonction renvoit le contenu de la case de coordonnées pX, pY.
Si les coordonnées n'appartiennent pas au tableau, une chaîne vide est renvoyée.
Ceci nous permettera de nous passer d'un test pour vérifier si la case est bien contenue dans le tableau.
Ensuite, pour dessiner les murs sur l'image, il nous faut connaître le centre de la case.
'---------------------------------------------------------------------------------------
' Calcul la position sur l'image en fonction de la position dans le tableau
'---------------------------------------------------------------------------------------
' pX,pY : Coordonnées dans le tableau
' pXOut,pYOut : Coordonnées sur l'image en pixel
'---------------------------------------------------------------------------------------
Private Sub GetCenter(pX As Currency, pY As Currency, pXOut As Long, pYOut As Long)
pXOut = gBordure + (pX + 0.5) * gTx
pYOut = gBordure + (pY + 0.5) * gTy
End SubLe centre d'une case est déterminé en fonction de la taille des cases et de la bordure.
On a donc besoin de déclarer ces variables, qui seront ensuite calculées à l'initialisation de l'écran.
' Taille d'une case
Private gTx As Long, gTy As Long
' Bordure en pixels
Private gBordure As LongA l'initialisation de l'écran, nous allons dessiner le contenu statique : c'est-à-dire le tableau de jeu.
' Position du centre de la case
Dim lX As Long
Dim lY As Long
' Valeur des cases adjacente
Dim lUp As String
Dim lDown As String
Dim lRight As String
Dim lLeft As String
' Valeur de la case
Dim lValue As String' Dessine le contenu statique
With oGdi
' Nouveau bitmap pour dessiner
.CreateBitmapForControl FormGame.Img
' Dessin lissé pour les ellipses/lignes ...
.SmoothingMode = GdipSmoothingAntialias
' Début et fin de traits arrondis
.LineStart = LineCapRound
.LineEnd = LineCapRound
' Bordure de 5 pixels
gBordure = 5
' Taille des cases
gTx = (.ImageWidth - 2 * gBordure) \ 28
gTy = (.ImageHeight - 2 * gBordure) \ 30
' On s'assure que la taille des cases est un multiple de 2
gTx = gTx - (gTx Mod 2)
gTy = gTy - (gTy Mod 2)
' Fond noir
.FillColor vbBlack
' On dessine les murs
For lCptY = 0 To 29 ' 30 lignes
For lCptX = 0 To 27 ' 28 colonnes
' Calcul du centre de la case de coordonnéex lcptx,lcpty
GetCenter CSng(lCptX), CSng(lCptY), lX, lY
' Lecture du contenu de la case
lValue = GetValue(lCptX, lCptY)
' Lecture du contenu des cases adjacentes
lRight = GetValue(lCptX + 1, lCptY)
lLeft = GetValue(lCptX - 1, lCptY)
lUp = GetValue(lCptX, lCptY - 1)
lDown = GetValue(lCptX, lCptY + 1)
Select Case lValue
' Il y a un mur dans la case
Case "X"
' Mur horizontal
If lRight = "X" And lLeft = "X" And (Not lUp = "X" Or Not lDown = "X" Or Not lRight = "X") Then
.DrawLine lX - gTx / 2, lY, lX + gTx / 2, lY, RGB(0, 0, 255), 3
End If
' Mur vertical
If lUp = "X" And lDown = "X" And (Not lRight = "X" Or Not lLeft = "X") Then
.DrawLine lX, lY - gTy / 2, lX, lY + gTy / 2, RGB(0, 0, 255), 3
End If
' Mur en haut et à droite mais pas ailleurs
If lUp = "X" And lRight = "X" And lLeft <> "X" And lDown <> "X" Then
.DrawEllipse lX + gTx / 2, lY - gTy / 2, gTx / 2, gTy / 2, _
1, -1, RGB(0, 0, 255), 3, , 180, -90
End If
' Murs en coin (arrondis)
' Mur en haut et à gauche mais pas ailleurs
If lUp = "X" And lLeft = "X" And lRight <> "X" And lDown <> "X" Then
.DrawEllipse lX - gTx / 2, lY - gTy / 2, gTx / 2, gTy / 2, _
1, -1, RGB(0, 0, 255), 3, , 90, -90
End If
' Mur en bas et à droite mais pas ailleurs
If lDown = "X" And lRight = "X" And lLeft <> "X" And lUp <> "X" Then
.DrawEllipse lX + gTx / 2, lY + gTy / 2, gTx / 2, gTy / 2, _
1, -1, RGB(0, 0, 255), 3, , -180, 90
End If
' Mur en bas et à gauche mais pas ailleurs
If lDown = "X" And lLeft = "X" And lRight <> "X" And lUp <> "X" Then
.DrawEllipse lX - gTx / 2, lY + gTy / 2, gTx / 2, gTy / 2, _
1, -1, RGB(0, 0, 255), 3, , -90, 90
End If
Case Else
' La case ne contient pas un mur mais est dans un angle formé par deux murs
' On dessine un coin arrondi
' Mur en haut et à droite mais pas sur les deux autres côtés à la fois
If lUp = "X" And lRight = "X" And Not (lLeft = "X" And lDown = "X") Then
.DrawEllipse lX + gTx / 2, lY - gTy / 2, gTx / 2, gTy / 2, _
1, -1, RGB(0, 0, 255), 3, , 0, -90
End If
' Mur en haut et à gauche mais pas sur les deux autres côtés à la fois
If lUp = "X" And lLeft = "X" And Not (lRight = "X" And lDown = "X") Then
.DrawEllipse lX - gTx / 2, lY - gTy / 2, gTx / 2, gTy / 2, _
1, -1, RGB(0, 0, 255), 3, , -90, -90
End If
' Mur en bas et à droite mais pas sur les deux autres côtés à la fois
If lDown = "X" And lRight = "X" And Not (lLeft = "X" And lUp = "X") Then
.DrawEllipse lX + gTx / 2, lY + gTy / 2, gTx / 2, gTy / 2, _
1, -1, RGB(0, 0, 255), 3, , 0, 90
End If
' Mur en bas et à gauche mais pas sur les deux autres côtés à la fois
If lDown = "X" And lLeft = "X" And Not (lRight = "X" And lUp = "X") Then
.DrawEllipse lX - gTx / 2, lY + gTy / 2, gTx / 2, gTy / 2, _
1, -1, RGB(0, 0, 255), 3, , 90, 90
End If
End Select
Next
Next
End WithOn commence par créer une image avec CreateBitmap.
Cette image est remplie de noir, le jeu se déroulant sur fond noir.
La propriété DrawSmooth permet de lisser les dessins de traits.
Les propriétés LineStart et LineEnd définissent le type de début et fin de trait.
La bordure est définie à 5 pixels (pour éviter que le dessin soit « collé » aux bords de l'image).
Puis on calcule la taille de chaque case, en fonction de la taille de l'image, du nombre de cases, et de la bordure.
Notez qu'on s'assure que la taille des cases soit un multiple de 2 pour éviter des décalages dus à des arrondis.
Puis on dessine chaque mur (case contenant « X ») en fonction du contenu des cases adjacentes.
Pour chaque cas rencontré, on peut faire un petit dessin pour mieux visualiser le type de mur à dessiner.
Par exemple pour le troisième cas avec un mur dans la case.
' Mur en haut et à droite mais pas ailleurs
If lUp = "X" And lRight = "X" And lLeft <> "X" And lDown <> "X" Then
.DrawEllipse lX + gTx / 2, lY - gTy / 2, gTx / 2, gTy / 2, _
1, -1, RGB(0, 0, 255), 3, , 180, -90S'il y a un mur en haut et un mur à droite, mais pas de mur sur les autres cases adjacentes :

On doit donc dessiner un arc centré sur le point (lX + gTx / 2, lY - gTy / 2), qui démarre à 180° et qui parcours 90°.
On procède de même pour gérer tous les cas possibles.
On obtient enfin le dessin des murs.

Ensuite on dessine les pastilles.
Les pastilles sont dessinées dans le contenu statique, même si elles devront disparaître de l'affichage.
En effet, on ne va pas redessiner les pastilles à chaque affichage.
On supprimera les pastilles de ce contenu statique en noircissant la case au fur et à mesure qu'elles seront mangées.
Pour l'occasion on crée une variable en-tête de module qui contient le nombre de pastilles du niveau.
Cette variable est initialisée au chargement de l'écran avec le nombre de pastilles dessinées.
Puis elle sera décrémentée durant le jeu.
Une fois la variable égale à zéro, cela signifiera que toutes les pastilles auront été mangées.
' Nombre de pastilles
Private gNbPastilles As IntegerLes pastilles sont représentées par des ellipses blanches.
Les super-pastilles ont un rayon plus important.
' On dessine les pastilles
' Pas de pastilles sur les bords du niveau, on commence à 1 et on termine 1 case avant la fin
' Initialise le décompte de pastille du niveau
gNbPastilles = 0
' Boucle sur le tableau
For lCptY = 1 To 28
For lCptX = 1 To 26
' Calcul du centre de la case pour y placer la pastille
GetCenter CSng(lCptX), CSng(lCptY), lX, lY
' Contenu de la case
lValue = GetValue(lCptX, lCptY)
Select Case lValue
' Il y a une pastille dans la case
Case "o"
' Ellipse de deux pixels de rayon
.DrawEllipse lX, lY, 2, 2, 1, vbWhite, vbWhite
gNbPastilles = gNbPastilles + 1
' Il y a une super-pastille dans la case
Case "O"
' Ellipse de quatre pixels de rayon
.DrawEllipse lX, lY, 4, 4, 1, vbWhite, vbWhite
gNbPastilles = gNbPastilles + 1
End Select
Next
Next
' On conserve le dessin du niveau avec ses murs et ses pastilles
.ImageKeepNotez qu'on conserve ce contenu statique avec un appel à ImageKeep une fois le dessin terminé.
On obtient le dessin des murs et des pastilles.

XV-D. Chargement des ressources▲
A l'initialisation de l'écran, nous devons charger les ressources, c'est-à-dire les images et les sons.
' Chargement des images du PacMan
Dim lCpt As Integer
With oGdi.ImgNew("pac0")
.LoadFile ThisWorkbook.Path & "\images\pac0.png"
.Resize gTx
End With
For lCpt = 1 To 3
With oGdi.ImgNew("pacg" & lCpt)
.LoadFile ThisWorkbook.Path & "\images\pacg" & lCpt & ".png"
.Resize gTx
End With
With oGdi.ImgNew("pach" & lCpt)
.LoadFile ThisWorkbook.Path & "\images\pach" & lCpt & ".png"
.Resize gTx
End With
With oGdi.ImgNew("pacd" & lCpt)
.LoadFile ThisWorkbook.Path & "\images\pacd" & lCpt & ".png"
.Resize gTx
End With
With oGdi.ImgNew("pacb" & lCpt)
.LoadFile ThisWorkbook.Path & "\images\pacb" & lCpt & ".png"
.Resize gTx
End With
Next
' Chargement des images des fantômes en état "normal"
With oGdi.ImgNew("ghost1")
.LoadFile ThisWorkbook.Path & "\images\fantome.png"
With oGdi.ImgClone("ghost1", "ghost2")
.ReplaceColor vbRed, vbGreen
.Resize gTx
End With
With oGdi.ImgClone("ghost1", "ghost3")
.ReplaceColor vbRed, vbYellow
.Resize gTx
End With
.Resize gTx
End With
' Chargement de l'images des fantômes en état "effrayé"
With oGdi.ImgNew("ghostscared")
.LoadFile ThisWorkbook.Path & "\images\fantomeeffraye.png"
.ReplaceColor vbBlack, vbBlack, , 0
End With
' Chargement de l'images des yeux de fantômes pour le retour au centre
With oGdi.ImgNew("yeux")
.LoadFile ThisWorkbook.Path & "\images\yeux.png"
.ReplaceColor vbBlack, vbBlack, , 0
End With
' Chargement des sons
oSound.OpenSound ThisWorkbook.Path & "\sons\aie.wav", "aie"
oSound.OpenSound ThisWorkbook.Path & "\sons\aouch.wav", "aouch"
oSound.OpenSound ThisWorkbook.Path & "\sons\blip.wav", "blip"Les images sont chargées dans l'objet oGdi.
Chaque image est identifiée par une chaîne de caractères.
On redimensionne au chargement chaque image à la taille d'une case (avec le paramètre pWidth = gTx).
Pour les fantômes, on génère trois images, en modifiant la couleur rouge pour obtenir 3 fantômes de couleur différente.
Attention à modifier la couleur avant le redimensionnement qui, avec le lissage de l'image va un peu mélanger les couleurs.
Les sons sont chargés dans l'objet oSound, identifiés également par une chaîne de caractères.
XV-E. Création de du module objet clSprite▲
Nous allons dans le jeu avoir à gérer deux types d'objets : le PacMan et les fantômes.
Ces objets sont similaires, ce sont tout simplement des sprites.
Les données du Pacman et de chaque fantôme seront stockées dans un objet de type clSprite qui contient :
- leur position ;
- leur mouvement ;
- le mouvement suivant ;
- leur vitesse ;
- un compteur d'image (utilisé pour l'animation du PacMan et pour les couleurs de fantômes).
Créez donc un module de classe nommé clSprite.
'***************************************************************************************
'* CLASSE POUR SPRITE
'***************************************************************************************
Option Explicit
' Position
Public X As Currency
Public Y As Currency
' Mouvement (g,d,h,b, ou vide si arrêté)
Public Mvt As String
' Mouvement en attente (entre deux cases)
Public MvtSuivant As String
' Numéro de l'image
Public Img As Integer
' Vitesse
Public Vitesse As CurrencyOn utilise pour la position et la vitesse un type de données Currency car en VBA ce type permet de gérer correctement des valeurs non entières.
Les types Single ou Double ne conviendraient pas car VBA arrondi ces valeurs.
Ainsi un sprite en position X = 10.4 se trouvera entre la colonne 10 et 11.
Lorsque le sprite atteint la position 11, on sait que le sprite est au centre d'une colonne.
Les mouvements sont des lettres :
- g pour gauche ;
- d pour droite ;
- h pour haut ;
- b pour bas.
Il n'y a pas de mouvement possible en diagonale.
On utilise une variable MvtSuivant pour stocker le mouvement suivant.
Ainsi lorsque le joueur appuie sur une touche, on détecte le mouvement demandé que l'on stocke dans cette variable.
Le sprite continue son chemin jusqu'à atteindre une case « pleine » (positions X et Y entières) et on modifie alors son mouvement.
De cette manière le joueur peut demander le déplacement du sprite sans avoir à appuyer sur la touche de direction pile au moment où le sprite atteint le centre d'une case.
XV-F. Gestion du Pacman▲
Le Pacman est donc un objet de type clSprite.
Déclarez cet objet dans le module clScreenGame
' Objet Pacman
Private oPacman As clSpriteA l'initialisation de l'écran de jeu, on va créer l'objet Pacman et lui donner ses paramètres par défaut.
' Initialisation du Pacman
Set oPacman = New clSprite
' Pac immobile par défaut
oPacman.Mvt = ""
oPacman.MvtSuivant = ""
' Initialise la position de départ
oPacman.X = 14
oPacman.Y = 22
' Image initial du Pac
oPacman.Img = 0
' Vitesse initiale du Pac
oPacman.Vitesse = 0.1Au début du jeu, le PacMan est immobile.
Il n'y a pas de mouvement.
Par contre on initialise sa vitesse qui est d'un dixième de case.
On le place au départ vers le bas au centre (colonne 14, ligne 22).
Pour dessiner le PacMan à l'écran, on va placer le code de dessin dans la fonction ClScreen_UpdateScreen.
On commence par restaurer le contenu statique, puis on affichage l'image du PacMan.
L'affichage d'une image se fait à l'aide de la fonction DrawImg de l'objet oGdi.
On utilise la fonction GetCenter pour calculer la position du centre du PacMan, puis on affiche l'image.
(Notez qu'on a défini les paramètres de GetCenter en Currency pour pouvoir passer des paramètres non entiers).
' Position sur le dessin
Dim lX As Long, lY As Long
' Affichage
' Reprend l'image de la mémoire
oGdi.ImageReset
' Calcule le centre du PacMan
GetCenter oPacman.X, oPacman.Y, lX, lY
' Dessine le PacMan
oGdi.DrawImg "pac" & IIf(oPacman.Img=0, "", oPacman.Mvt) & oPacman.Img , lX, lY, , , , GdipSizeModeautosizeDans la fonction DrawImg, le paramètre GdipSizeModeautosize signifie qu'on affiche l'image sans redimensionnement et centrée sur les deux premières coordonnées.
L'image du PacMan à afficher est fonction de son mouvement.
Si le PacMan est en mouvement (paramètre Mvt non vide, différent de "") alors l'image du PacMan est pac[Mvt][Img].
Si le PacMan est immobile, l'image est pac0.
Par exemple pour un mouvement vers la droite, les images seront :
- pac0 ;
- pacd1 ;
- pacd2 ;
- pacd3.
Si on affiche le formulaire, on visualise alors le PacMan, sagement immobile.

Il faut maintenant tester l'appui de touche afin de modifier le mouvement du PacMan.
Dim lDown As Boolean, lUp As Boolean, lLeft As Boolean, lRight As Boolean
' Test directions du joystick
oCommand.TestJoypadDir lLeft, lRight, lUp, lDown
' Test des commandes de déplacement du Pacman
If oCommand.TestKey(oSharedData.ToucheBas) Or lDown Then
oPacman.MvtSuivant = "b"
End If
If oCommand.TestKey(oSharedData.ToucheDroite) Or lRight Then
oPacman.MvtSuivant = "d"
End If
If oCommand.TestKey(oSharedData.ToucheGauche) Or lLeft Then
oPacman.MvtSuivant = "g"
End If
If oCommand.TestKey(oSharedData.ToucheHaut) Or lUp Then
oPacman.MvtSuivant = "h"
End IfComme prévu, on modifie le paramètre MvtSuivant de l'objet Pacman.
Dans la fonction de mise à jour de l'écran, on modifie la position du PacMan en fonction de son mouvement.
Puis on teste si le PacMan est sur une case « pleine » (positions entière) pour remplacer le mouvement en cours par le mouvement suivant.
On effectue le déplacement des sprites avant leur dessin, donc en début de fonction.
' Déplacement du PacMan en fonction de son mouvement et sa vitesse
Select Case oPacman.Mvt
Case "d"
oPacman.X = oPacman.X + oPacman.Vitesse
Case "g"
oPacman.X = oPacman.X - oPacman.Vitesse
Case "h"
oPacman.Y = oPacman.Y - oPacman.Vitesse
Case "b"
oPacman.Y = oPacman.Y + oPacman.Vitesse
End Select
' Si le PacMan atteint une case "pleine" => modifie le mouvement en fonction du mouvement suivant demandé
If oPacman.X = Int(oPacman.X) And oPacman.Y = Int(oPacman.Y) Then
' Mouvement suivant
oPacman.Mvt = oPacman.MvtSuivant
End IfIl manque l'animation du PacMan.
' Animation du PacMan
' On tourne sur 4 images
If oPacman.Mvt = "" Then
' Si PacMan immobile => image 0
oPacman.Img = 0
Else
oPacman.Img = (oPacman.Img + 1) Mod 4
End IfLe déplacement du PacMan fonctionne correctement, sauf qu'il peut traverser les murs.
Il faut donc tester s'il y a un mur dans la direction du mouvement suivant et annuler le mouvement si nécessaire.
On empêche également que le PacMan ne traverse la porte au centre d'où les fantômes sortent.
Et enfin on en profite pour gérer le passage d'un côté à l'autre du jeu si le Pacman est sur une case contenant « P » (pour simplifier le tutoriel, on met les valeurs du passage « en dur » = colonne 27 ou 0).
On a besoin de déclarer quelques variables qui contiendront le contenu des cases.
' Valeur des cases et des alentours
Dim lCase As String
Dim lUp As String
Dim lDown As String
Dim lRight As String
Dim lLeft As String' Si le PacMan atteint une case "pleine" => modifie le mouvement en fonction du mouvement suivant demandé
If oPacman.X = Int(oPacman.X) And oPacman.Y = Int(oPacman.Y) Then
' Contenu des cases
lCase = GetValue(oPacman.X, oPacman.Y)
lRight = GetValue(oPacman.X + 1, oPacman.Y)
lLeft = GetValue(oPacman.X - 1, oPacman.Y)
lUp = GetValue(oPacman.X, oPacman.Y - 1)
lDown = GetValue(oPacman.X, oPacman.Y + 1)
' Passage d'un côté à l'autre du jeu
If lCase = "P" Then
If oPacman.X = 0 Then
oPacman.X = 27
ElseIf oPacman.X = 27 Then
oPacman.X = 0
End If
End If
' Mouvement suivant
oPacman.Mvt = oPacman.MvtSuivant
' Teste si le mouvement suivant est possible
If oPacman.Mvt = "d" And lRight = "X" Then
oPacman.Mvt = ""
End If
If oPacman.Mvt = "g" And lLeft = "X" Then
oPacman.Mvt = ""
End If
If oPacman.Mvt = "b" And (lDown = "X" Or lDown = "_") Then
oPacman.Mvt = ""
End If
If oPacman.Mvt = "h" And lUp = "X" Then
oPacman.Mvt = ""
End If
End IfLe Pacman peut maintenant être déplacé correctement dans tout le jeu.
XV-G. Gestion des pastilles▲
Les pastilles sont mangées par le PacMan.
Lorsqu'une pastille est mangée, on noirci la case en noir sur le contenu statique puis on resauvegarde ce contenu.
Il faut donc déplacer l'appel à la fonction ImageReset en début de fonction ClScreen_UpdateScreen, après les déclarations.
En effet on va :
- d'abord restaurer le contenu statique ;
- puis tester si le PacMan mange une pastille et la supprimer de l'affichage ;
- et enfin on dessinera les sprites.
' Reprend l'image de la mémoire
oGdi.ImageResetLorsque le Pacman atteint une case pleine, on va alors tester si c'est une pastille et la supprimer de l'affichage.
On supprime également la pastille du tableau, et on joue un son.
Et enfin on décrémente le compteur de pastille et on ajoute 1 au score.
Pour le score, il faut ajouter une variable partagée dans clSharedData.
' Score
Public Score As Long' Si le PacMan atteint une case "pleine" => modifie le mouvement en fonction du mouvement suivant demandé
If oPacman.X = Int(oPacman.X) And oPacman.Y = Int(oPacman.Y) Then
' Contenu des cases
lCase = GetValue(oPacman.X, oPacman.Y)
lRight = GetValue(oPacman.X + 1, oPacman.Y)
lLeft = GetValue(oPacman.X - 1, oPacman.Y)
lUp = GetValue(oPacman.X, oPacman.Y - 1)
lDown = GetValue(oPacman.X, oPacman.Y + 1)
' Mange une pastille
If lCase = "o" Or lCase = "O" Then
' Calcule le centre de la pastille
GetCenter oPacman.X, oPacman.Y, lX, lY
' Noirci la case
oGdi.DrawRectangle lX - gTx / 2, lY - gTy / 2, lX + gTx / 2, lY + gTy / 2, vbBlack, vbBlack
' Supprime la pastille du tableau
gTableau(oPacman.X, oPacman.Y) = ""
' Joue un son
oSound.PlaySound "blip"
' Décrémente le compteur de pastille
gNbPastilles = gNbPastilles - 1
' Incrémente le score
oSharedData.Score = oSharedData.Score + 1
' Sauvegarde le contenu "statique"
oGdi.ImageKeep
End If
[...]Pour visualiser le score, on ajoute un code de dessin à la fin de la fonction ClScreen_UpdateScreen.
' Affiche le score
oGdi.DrawText "Score : " & oSharedData.Score, 20, , 0, 0, _
oGdi.ImageWidth, oGdi.ImageHeight, 2, 2, vbRed, , _
RGB(255, 255, 100)Le Pacman peut maintenant manger les pastilles, et on voit le score en bas à droite s'incrémenter à chaque pastille.

XV-H. Gestion des fantômes▲
Les fantômes sont également des objets de type clSprite.
Mais il y a plusieurs fantômes, dont le nombre est défini dans les options.
Nous allons donc déclarer et initialiser une collection de fantômes.
' Collection de fantômes
Private oCollFantomes As Collection' Compteur
Dim lCpt As Long
' Objet Fantome
Dim lFantome As clSprite' Initialisation des fantômes
Set oCollFantomes = New Collection
If oCollFantomes Is Nothing Then
For lCpt = 1 To oSharedData.NbFantomes
Set lFantome = New clSprite
' Position initiale des fantôme
lFantome.X = Int(11 + Rnd * 6) ' Colonne entre 11 et 16
lFantome.Y = 14 ' Ligne 14
' Image (on tourne sur trois images pour changer la couleur)
lFantome.img = 1 + (lCpt Mod 3)
' Vitesse initiale des fantômes
lFantome.Vitesse = 0.1
' Ajout à la collection
oCollFantomes.Add lFantome
Next
End IfSi on affiche le formulaire on obtient une erreur d'exécution '91' : Variable objet ou variable de bloc With non définie.
Cette erreur est levée lorsqu'on essaye de lire le nombre de fantôme de l'objet oSharedData.
En effet à l'initialisation de l'écran, l'objet oSharedData n'est pas encore affecté.
Et malheureusement VBA ne permet pas de passer des paramètres au constructeur de la classe.
Il faut alors déplacer ce code d'initialisation des fantômes dans la fonction ClScreen_UpdateScreen, au début après les déclarations.
On vérifie que la collection de fantômes n'est pas initialisée afin de ne le faire qu'une seule fois.
' Compteur
Dim lCpt As Long
' Objet Fantome
Dim lFantome As clSprite' Initialisation des fantômes
If oCollFantomes Is Nothing Then ' une seule initialisation
Set oCollFantomes = New Collection
For lCpt = 1 To oSharedData.NbFantomes
Set lFantome = New clSprite
' Position initiale des fantôme
lFantome.X = Int(11 + Rnd * 6) ' Colonne entre 11 et 16
lFantome.Y = 14 ' Ligne 14
' Image (on tourne sur trois images)
lFantome.Img = 1 + (lCpt Mod 3)
' Vitesse initiale des fantômes
lFantome.Vitesse = 0.1
' Ajout à la collection
oCollFantomes.Add lFantome
Next
End IfOn va ensuite gérer le déplacement et l'affichage des fantômes après le déplacement et l'affichage du Pacman dans la fonction ClScreen_UpdateScreen.
Etant donné qu'il y a plusieurs fantômes, on doit faire une boucle pour gérer chaque fantôme de la collection.
Comme pour le Pacman, on va d'abord déplacer le fantôme en fonction de sa vitesse.
Puis si le fantôme est sur une case « pleine » (positions entières), on recherche le mouvement à affecter au fantôme.
Dans le cas des fantômes, on n'utilise pas le paramètre MvtSuivant qui n'aurait pas d'intérêt.
Le mouvement des fantômes doit répondre aux règles suivantes :
- Le fantôme doit se rapprocher du PacMan => on recherche donc l'axe où la distance entre le fantôme et le PacMan est la plus grande (X ou Y).
- Si le fantôme est sur la porte de sortie (repérée "_"), alors il se déplace vers le haut (afin d'éviter de sortir et rentrer sans cesse).
- Le fantôme ne peut pas rentrer par la porte au centre de l'écran (repérée »_"), il ne peut que sortir.
- Si on n'a pas pû déterminer le déplacement optimal du fantôme, on lui donne un mouvement aléatoire.
- Le fantôme ne peut pas se déplacer sur un mur ("X »), ou sur un des 2 passages ("P").
Et enfin on dessine le fantôme, de la même manière qu'on a dessiné le PacMan.
Pour un fantôme, l'image est ghost1, ghost2 ou ghost3 en fonction du paramètre Img du fantôme.
' Variable pour numéro de fantôme
Dim lCalc as long' Gestion des fantômes
For Each lFantome In oCollFantomes
' Déplacement des fantômes
Select Case lFantome.Mvt
Case "d"
lFantome.X = lFantome.X + lFantome.Vitesse
Case "g"
lFantome.X = lFantome.X - lFantome.Vitesse
Case "h"
lFantome.Y = lFantome.Y - lFantome.Vitesse
Case "b"
lFantome.Y = lFantome.Y + lFantome.Vitesse
End Select
' Test si le fantôme est sur une case "pleine"
If Int(lFantome.X) = lFantome.X And Int(lFantome.Y) = lFantome.Y Then
' Lecture du contenu des cases aux alentours du fantôme
lRight = GetValue(lFantome.X + 1, lFantome.Y)
lLeft = GetValue(lFantome.X - 1, lFantome.Y)
lUp = GetValue(lFantome.X, lFantome.Y - 1)
lDown = GetValue(lFantome.X, lFantome.Y + 1)
' Pas de mouvement par défaut
lFantome.Mvt = ""
' Recherche du mouvement pour se rapprocher du PacMan
' Si distance X plus grand que distance Y
If Abs(lFantome.X - oPacman.X) > Abs(lFantome.Y - oPacman.Y) Then
If lFantome.X < oPacman.X And lRight <> "X" And lRight <> "P" Then
lFantome.Mvt = "d"
ElseIf lFantome.X >= oPacman.X And lLeft <> "X" And lLeft <> "P" Then
lFantome.Mvt = "g"
End If
End If
' Si distance Y plus grand que distance X ou que le cas précédent n'a pas donné de mouvement possible
If lFantome.Mvt = "" Then
If lFantome.Y < oPacman.Y And lDown <> "X" And lDown <> "_" Then
lFantome.Mvt = "b"
ElseIf lFantome.Y >= oPacman.Y And lUp <> "X" Then
lFantome.Mvt = "h"
End If
End If
' Si on est sur la porte de sortie => déplacement vers le haut
If GetValue(lFantome.X, lFantome.Y) = "_" Then lFantome.Mvt = "h"
' Si fantôme immobile => mouvement aléatoire pour le lancer
If lFantome.Mvt = "" Then
lCalc = 1 + Int(Rnd * 4)
Select Case lCalc
Case 1: lFantome.Mvt = "d"
Case 2: lFantome.Mvt = "g"
Case 3: lFantome.Mvt = "h"
Case 4: lFantome.Mvt = "b"
End Select
End If
' Teste si le mouvement est possible, sinon on l'annule
If lFantome.Mvt = "d" And (lRight = "X" Or lRight = "P") Then
lFantome.Mvt = ""
End If
If lFantome.Mvt = "g" And (lLeft = "X" Or lLeft = "P") Then
lFantome.Mvt = ""
End If
If lFantome.Mvt = "b" And (lDown = "X" Or lDown = "_") Then
lFantome.Mvt = ""
End If
If lFantome.Mvt = "h" And lUp = "X" Then
lFantome.Mvt = ""
End If
End If
' Calcule le centre du fantôme
GetCenter lFantome.X, lFantome.Y, lX, lY
' Dessine le fantôme
oGdi.DrawImg "ghost" & lFantome.Img, lX, lY, , , , GdipSizeModeautosize
NextTestez l'affichage du jeu, on voit bien les fantômes se déplacer.
Les fantômes sont plutôt stupides dans leur déplacement, mais il ne faudrait pas les rendre trop intelligents si on veut que le jeu soit jouable.
XV-I. Super-pastille : fantômes effrayés▲
Un élément essentiel du jeu est le changement d'état des fantômes.
Lorsque le Pacman mange une super-pastille (repérée par un « O » majuscule), les fantômes doivent changer d'état :
- ils deviennent bleus ;
- ils se déplacent plus lentement et fuient le PacMan ;
- ils sont mangeables.
Cet état est temporaire, on le fait durer 15 secondes.
Ajoutons un compteur pour l'état effrayé pour les objets fantômes
' Etat effrayé = temps restant
Public Scared As longLorsque le PacMan mangera une super-pastille, on initialise ce compteur.
On fait ce test après le premier test sur l'arrivé sur une pastille.
' Mange une pastille
If lCase = "o" Or lCase = "O" Then
[...] ' Test déjà écrit, ajouter le test de super-pastille après celui-ci
End If
' Mange une super-pastille
If lCase = "O" Then
' Pour chaque fantôme
For Each lFantome In oCollFantomes
' Passe le fantôme à l'état effrayé durant 15 secondes
' On multiplie par 40 car 40 images par secondes
lFantome.Scared = 40 * 15
Next
End IfEnsuite dans le code de gestion des fantômes, on décrémente le compteur s'il est supérieur à zéro.
' Gestion des fantômes
For Each lFantome In oCollFantomes
' Décrémente le compteur d'état effrayé
If lFantome.Scared > 0 Then lFantome.Scared = lFantome.Scared - 1
[...]Et on modifie la recherche de mouvement des fantômes pour les éloigner du PacMan s'ils sont effrayés.
' Pas de mouvement par défaut
lFantome.Mvt = ""
' Recherche du mouvement pour se rapprocher du PacMan
If lFantome.Scared = 0 Then
' Si distance X plus grand que distance Y
If Abs(lFantome.X - oPacman.X) > Abs(lFantome.Y - oPacman.Y) Then
If lFantome.X < oPacman.X And lRight <> "X" And lRight <> "P" Then
lFantome.Mvt = "d"
ElseIf lFantome.X >= oPacman.X And lLeft <> "X" And lLeft <> "P" Then
lFantome.Mvt = "g"
End If
End If
' Si distance Y plus grand que distance X ou que le cas précédent n'a pas donné de mouvement possible
If lFantome.Mvt = "" Then
If lFantome.Y < oPacman.Y And lDown <> "X" And lDown <> "_" Then
lFantome.Mvt = "b"
ElseIf lFantome.Y >= oPacman.Y And lUp <> "X" Then
lFantome.Mvt = "h"
End If
End If
' Ou pour s'éloigner si le fantome est en état effrayé
Else
' Si distance X plus petite que distance Y
If Abs(lFantome.X - oPacman.X) < Abs(lFantome.Y - oPacman.Y) Then
If lFantome.X > oPacman.X And lRight <> "X" And lRight <> "P" Then
lFantome.Mvt = "d"
ElseIf lFantome.X <= oPacman.X And lLeft <> "X" And lLeft <> "P" Then
lFantome.Mvt = "g"
End If
End If
' Si distance Y plus petite que distance X ou que le cas précédent n'a pas donné de mouvement possible
If lFantome.Mvt = "" Then
If lFantome.Y > oPacman.Y And lDown <> "X" And lDown <> "_" Then
lFantome.Mvt = "b"
ElseIf lFantome.Y <= oPacman.Y And lUp <> "X" Then
lFantome.Mvt = "h"
End If
End If
End IfEt enfin modifiez le code de dessin des fantômes pour afficher l'image fantomeeffraye si nécessaire.
' Dessine le fantôme
oGdi.DrawImg "ghost" & IIf(lFantome.Scared > 0, "scared", lFantome.Img), lX, lY, , , , GdipSizeModeautosizeLes fantômes sont maintenant bleus durant 15 secondes lorsque le PacMan mange une super-pastille.
Et ils s'éloignent au lieu de se rapprocher.
Par contre leur vitesse n'est pas modifiée.
Lors de la modification de la vitesse il faut bien faire attention.
En effet on teste l'arrivée du sprite sur une case en testant si les positions sont entières.
En modifiant la vitesse, on risque de ne plus arriver à des valeurs entières.
Par exemple :
La position X est égale à 9.95 et on modifie la vitesse de 0.05 à 0.1.
La position suivante vaudra alors 10.05 sans passer par la valeur entière 10.
Il faut donc ajuster la position en fonction de la vitesse définie.
Pour modifier la position lors de la modification de la vitesse, on va transformer la variable publique Vitesse en propriété.
Private gVitesse As Currency' Propriété pour lecture et écriture de la vitesse
Public Property Get Vitesse() As Currency
Vitesse = gVitesse
End Property
Public Property Let Vitesse(pVitesse As Currency)
gVitesse = pVitesse
' Si vitesse non nulle
If gVitesse <> 0 Then
' Déplace légérement le sprite pour se repositionner sur un multiple de la vitesse
X = Int(X) + Int((X - Int(X)) / gVitesse) * gVitesse
Y = Int(Y) + Int((Y - Int(Y)) / gVitesse) * gVitesse
End If
End PropertyPour que cela fonctionne, la vitesse doit être un dividende de 1 (exemple : 0.2 ou 0.1).
Une vitesse de 0.3 ou de 0.04 ne conviendrait pas.
Le PacMan a une vitesse constante de 0.1.
Les fantômes ont une vitesse de 0.1 en état normal, et de 0.05 en état effrayé.
La modification de la vitesse des fantômes va se faire également dans le module clSprite.
Lors de la modification du compteur Scared, on va modifier la vitesse des fantômes.
Pour cela il faut tranformer la variable publique Scared en propriété.
' Etat effrayé
Private gScared As Long' Propriété pour lecture et écriture de l'état effrayé
Public Property Get Scared() As Long
Scared = gScared
End Property
Public Property Let Scared(pScared As Long)
If gScared = 0 And pScared <> 0 Then
' Passage à l'état effrayé => on passe la vitesse de 0.1 à 0.05
Vitesse = gVitesse / 2
ElseIf gScared <> 0 And pScared = 0 Then
' Passage à l'état normal => on repasse la vitesse de à 0.1
Vitesse = gVitesse * 2
End If
gScared = pScared
End PropertyLe passage des fantômes de l'état effrayé à l'état normal et vice-versa est terminé.
XV-J. Gestion des collisions entre les sprites▲
Pour simplifier, il n'y a pas de gestion de collisions entre les fantômes : ils se traversent.
Par contre il faut gérer la collision entre le PacMan et les fantômes.
On simplifie également la détection de collision entre les sprites en détectant la collision entre deux rectangles.
Il faudrait vérifier si un des 4 coins du rectangle encadrant un fantome se trouve dans le rectangle encadrant le PacMan.
Plutôt que de tester toutes ces coordonnées (ce ne serait cependant pas très compliqué), on va utiliser les régions de gdi+.
Lors du dessin des sprites (fantôme et PacMan), on va ajouter une région correspondant au rectangle dans lequel le sprite est dessiné.
Cela se fait simplement en rajoutant un paramètre à l'appel de la fonction DrawImg de l'objet oGdi.
Pour identifier le sprite, on va utiliser le pointeur de l'objet qui est déterminé avec ObjPtr.
Ce pointeur est garanti unique.
' Dessine le PacMan et ajoute une région rectangulaire
oGdi.DrawImg "pac" & IIf(oPacman.img = 0, "", oPacman.Mvt) & oPacman.img, lX, lY, , , , _
GdipSizeModeAutoSize, , , CStr(ObjPtr(oPacman))' Dessine le fantôme et ajoute une région rectangulaire
oGdi.DrawImg "ghost" & IIf(lFantome.Scared > 0, "scared", lFantome.img), lX, lY, , , , _
GdipSizeModeAutoSize, , , CStr(ObjPtr(lFantome))On va ensuite rechercher une éventuelle collision après avoir dessiner les sprites.
On profite de la boucle déjà en place sur la collection de fantômes.
Les collisions sont testées à l'aide de la fonction RegionsIntersect de l'objet oGdi.
Pour tester rapidement, on écrit un texte en bas à gauche lorsque le PacMan touche un fantôme.
' Gestion des fantômes
For Each lFantome In oCollFantomes
[...]
' Détection de collision avec le PacMan
If oGdi.RegionsIntersect(ObjPtr(oPacman), ObjPtr(lFantome)) Then
oGdi.DrawText "Collision", 20, , 0, 0, oGdi.ImageWidth, oGdi.ImageHeight, 0, 2, vbRed, , RGB(255, 255, 100), , , , , , True
End If
NextAffichez le formulaire et allez au contact d'un fantôme pour visualiser la collision.
Que faire en cas de collision ?
Si le fantôme est en état normal => le PacMan est mangé => on affiche l'écran de jeu perdu.
Si le fantôme est en état effrayé => le fantôme est mangé => il doit retourner au centre.
Dans les deux cas, on joue un son.
Pour l'état normal c'est facile :
' Détection de collision avec le PacMan
If oGdi.RegionsIntersect(ObjPtr(oPacman), ObjPtr(lFantome)) Then
If lFantome.Scared = 0 Then
' Le fantôme est en état normal => il mange le PacMan
' On joue un son
oSound.PlaySound "aouch"
' On demande le changement d'écran vers l'écran de jeu perdu
gNextScreen = "gameover"
gChangeScreen = True
Else
' Le fantôme est en état effrayé => il est mangé
' Code à suivre
End If
End IfPour l'état effrayé c'est plus compliqué :
Il faut définir une nouvelle propriété des sprites : un indicateur de retour au centre.
Lorsque cet indicateur est à Vrai :
- les fantômes doivent suivre le chemin pour retourner au centre ;
- ils sont accélérés pour un retour rapide ;
- seuls leurs yeux s'affichent ;
- ils ne doivent pas être remangés ou et ne doivent pas pouvoir manger le PacMan ;
- ils reviennent ensuite à l'état normal.
' Indicateur pour retour au centre
Private gReturnToCentre As Boolean' Propriété pour lecture et écriture du retour au centre
Public Property Get ReturnToCentre() As Boolean
ReturnToCentre = gReturnToCentre
End Property
Public Property Let ReturnToCentre(pReturnToCentre As Boolean)
If gReturnToCentre = False And pReturnToCentre = True Then
' Met le flag de retour au centre => on passe la vitesse à 0.25
Vitesse = 0.25
ElseIf ReturnToCentre = True And pReturnToCentre = False Then
' Annule le flag de retour au centre => on repasse la vitesse à 0.1
Vitesse = 0.1
End If
gReturnToCentre = pReturnToCentre
End PropertyModifions ensuite la détection des collisions.
Notez qu'on ajoute 50 points au score par fantôme mangé.
' Détection de collision avec le PacMan
' Pas de détection si retour au centre
If Not lFantome.ReturnToCentre And oGdi.RegionsIntersect(ObjPtr(oPacman), ObjPtr(lFantome)) Then
If lFantome.Scared = 0 Then
' Le fantôme est en état normal => il mange le PacMan
' On joue un son
oSound.PlaySound "aouch"
' On demande le changement d'écran vers l'écran de jeu perdu
gNextScreen = "gameover"
gChangeScreen = True
Else
' Le fantôme est en état effrayé => il est mangé
' On joue un son
oSound.PlaySound "aie"
' Incrémente le score de 50
oSharedData.Score = oSharedData.Score + 50
' Fin de l'état effrayé
lFantome.Scared = 0
' Retour au centre
lFantome.ReturnToCentre = True
End If
End IfOccupons maintenant du retour au centre des fantômes.
XV-K. Retour au centre des fantômes▲
Les déplacements à suivre pour le retour au centre ont été définis dans la feuille Chemin.
De la même manière que pour les données du tableau de la feuille Jeu, nous allons charger ces données dans un tableau.
' Tableau de retour au centre
Private gChemin(0 To 27, 0 To 29) As String' A insérer après le chargement de gTableau
' Charge les données du tableau de retour au centre
With ThisWorkbook.Sheets("Chemin")
For lCptX = 1 To 28
For lCptY = 1 To 30
gChemin(lCptX - 1, lCptY - 1) = .Cells(lCptY, lCptX).Value
Next
Next
End WithEnsuite on gère les déplacements dans le cas où ReturnToCentre est Vrai.
' Test si le fantôme est sur une case "pleine"
If Int(lFantome.X) = lFantome.X And Int(lFantome.Y) = lFantome.Y Then
' Pas de retour au centre
If Not lFantome.ReturnToCentre Then
' =====> Ici on trouve le code de déplacement précédemment écrit
Else
' Retour au centre
lFantome.Mvt = gChemin(lFantome.X, lFantome.Y)
' Si plus de déplacement => on est arrivé au centre
If lFantome.Mvt = "" Then lFantome.ReturnToCentre = False
End If
End IfEt enfin on affiche la paire d'yeux lors du retour au centre.
' Dessine le fantôme et ajoute une région rectangulaire
If lFantome.ReturnToCentre Then
oGdi.DrawImg "yeux", lX, lY, , , , _
GdipSizeModeAutoSize, , , CStr(ObjPtr(lFantome))
Else
oGdi.DrawImg "ghost" & IIf(lFantome.Scared > 0, "scared", lFantome.img), lX, lY, , , , _
GdipSizeModeAutoSize, , , CStr(ObjPtr(lFantome))
End IfOn peut alors tester le retour au centre des fantômes :

XV-L. Fin du jeu▲
Il nous reste une dernière chose : tester si les pastilles ont été toutes mangées.
A la fin de la procédure de mise à jour du jeu (ClScreen_UpdateScreen), on teste si le compteur de pastille est à zéro.
Si c'est le cas, on bascule vers l'écran de jeu gagné.
' Test de fin du jeu
If gNbPastilles = 0 Then
gNextScreen = "gameend"
gChangeScreen = True
End IfSi on mange toutes les pastilles, le jeu se termine.
Prochaines étapes : la création des écrans de fin de jeu :
- écran de jeu perdu ;
- écran de jeu gagné ;
- écran des scores ;
- écran des crédits.


