11-Gestion du tir
Il ne nous reste plus que le tir à implémenter et notre exemple de shoot ‘em up sera terminé.
Le principe est simple, une fois que la touche ESPACE sera appuyée, on va déclencher le tir et indiquer qu’il faut déplacer le laser en activant une variable indbal
(indicateur de balle tirée), pendant ce déplacement du laser on va surveiller 2 choses :
- la collision entre le SPRITE de l’envahisseur et le SPRITE du laser.
- le laser qui qui finit sa course sans atteindre l’envahisseur.
Dans le cas 1, si le cas arrive c’est le déclenchement de l’explosion et le jeu recommence.
Dans le cas 2, si cela arrive on remettra on fera disparaitre le laser et on remettra indbal en l’état intial pour permettre à nouveau de tirer.
Voici ce que donne l’algorithme que l’on codera ensuite :

Comme on a initialisé le jeu avec toutes les variables à 1, si la touche espace n’est pas appuyée dans la boucle principale du programme, elle ignorera les 2 autres conditions (indbal et indcol égales à zéro).
FEU
Si on appuie sur la touche espace, on déclenche le tir au niveau des coordonnées de la fusée et on change la variable indbal qui servira à :
- déplacer le laser (routine
TIRBAL
). - et empêcher un autre tir par la touche espace tant que le premier n’est pas finit (routine
FEU
).
La routine FEU:
;------------------------------------------------------------------------------
; FEU
; indbal=1 pas de balle tirée / indbal=0 balle tirée
;------------------------------------------------------------------------------
FEU:
ld a,[indbal] ; on vérifie la valeur de indbal
inc a ; si indbal=0 une balle est déjà en cours
dec a ; donc on ne peut pas retirer
ret z ; et on quitte la routine
ld hl,6913 ; si indbal=1 on récupère la coord X de la fusée
call 74 ; via la TAS (lecture)
ld hl,6921 ; et on la met sa valeur dans le X du laser
call 77 ; via la TAS (écriture)
ld hl,6912 ; on récupère la coord Y de la fusée
call 74 ; via la TAS (lecture)
sub 17 ; enlève 17 à Y pour que le laser=tête de la fusée
ld hl,6920 ; et on la met sa valeur dans le Y du laser
call 77 ; via la TAS (écriture)
ld a,0 ; on passe indbal à 0
ld [indbal],a ; pour indiquer qu'un tir est en cours
ret ; retour à la boucle principale
TIRBAL
Comme on vient de tirer : indbal=0 maintenant
Et la boucle principale appelle la routine de déplacement de la balle (ou du laser si vous préférez).
Cette routine ne fait que :
- faire appel à une autre routine pour le déplacement (
DEPBAL:
) - activer le registre de collision du MSX
Elle appelle donc 3 fois DEPBAL
sinon le déplacement d’un pixel serait trop lent à l’affichage.
Puis elle active un registre existant du MSX (en VRAM) qui surveille si des SPRITES entrent en collision. Ce registre est consulté via un CALL 318
.
Si parmi les 8 bits qui composent ce registre, le bit 5 est à 1 alors c’est qu’il y a collision.
Du coup, on met à jour une variable à nous que l’on a appelé indcol
et qui nous permettra de déclencher l’explosion dans la boucle principale.
Voici le code :
;------------------------------------------------------------------------------
; TIRBAL
;------------------------------------------------------------------------------
TIRBAL:
call DEPBAL ; appel routine pour déplacer le sprite laser vers le haut
call DEPBAL ; opération reproduite 3 fois
call DEPBAL ; pour que le laser se déplace très vite
call 318 ; rappel du registre des collisions de sprites
bit 5,a ; on teste le bit 5
ret z ; si le bit 5=0 alors pas de collision et on quitte
ld a,0 ; sinon bit 5=1 collision alors on passe indcol à 0
ld [indcol],a ; ce qui déclenchera une explosion via le boucle principale
ret
DEPBAL
Cette routine ne fait que déplacer le sprite laser d’un pixel vers le haut et vérifie si le laser est arrivé à lextrémité haute de l’écran (Y=0).
Si c’est le cas, il faut que le laser soit réinitialisé, donc caché de l’écran et à disposition pour un nouveau tir.
Pour cela on change les coordonnées du sprite laser et on repasse indbal
à 1 via une sous-routine HAUT:
Voici le code :
;------------------------------------------------------------------------------
; DEPBAL
;------------------------------------------------------------------------------
DEPBAL:
ld hl,6920 ; on récupère le Y du laser
call 74 ; via la TAS (lecture)
dec a ; on décrémente Y pour que le laser monte
jp z,HAUT ; si Y=0 alors appel routine HAUT:
call 77 ; sinon on charge la nouvelle valeur dans la TAS
ret
HAUT:
ld a,200 ; on met la valeur 200 dans le Y du laser
ld hl,6920 ; le sprite sera en attente en dehors de l'écran
call 77 ; via la TAS (écriture)
ld a,1 ; et on passe indbal à 1
ld [indbal],a ; ainsi on autorise on nouveau déclenchement de laser
ret
EXPLO
La boucle principale teste à chaque fois si indcol=0 (une collision a eu lieu). Elle appelle alors la routine EXPLO:
i qui va produire une explosion.
Le mode écran est tout d’abord réinitialisé en mode 3 (multicouleur), les tables de la VRAM sont remplies avec des valeurs aléatoires qui affichera des carrés de toutes les couleurs, ce processus est appelé plusieurs fois et la routine finie par appeller la routine DEBUT:
qui fait recommencer le jeu avec des variables remises à 1.
et voici :
;------------------------------------------------------------------------------
; EXPLO
;------------------------------------------------------------------------------
EXPLO:
ld a,3 ; 3 pour screen 3
ld [64687],a ; (bios)SCRMOD adresse hexa $FCAF :mode courant de l'écran
call 95 ; (bios) CHGMOD change le mode de l'écran
ld a,100 ; valeurs de départ
ld hl,0
EXPLO1:
PUSH hl
PUSH af
ld de,2048
ld bc,1000
call 92
pop af
pop hl
inc h
dec a
jp nz,EXPLO1
jp DEBUT
Il ne nous reste plus qu’à compléter la boucle principale pour tester la touche espace (lignes 27 à 30 du code ci-dessous) et tester les variables (lignes 10 à 17 du code ci-dessous).
BOUCLE PRINCIPALE
;------------------------------------------------------------------------------
; Boucle du jeu : Le programme démarre à DEBUT puis va à BOUCLE
;------------------------------------------------------------------------------
DEBUT: ; c'est ici que débute le programme !!!
call INIT ; on appelle le sous-prog INIT
BOUCLE: ; c'est la boucle principale du programme !!!
call DEPENV ; on appelle le déplacement de l'envahisseur
call DELAI ; on appelle le sous-prog DELAI afin de ralentir l'exécution
ld a,[indcol] ; on charge la valeur de indcol dans a
inc a ; on fait un calcul sur a
dec a
jp z,EXPLO ; si a=0 alors on appelle EXPLO
ld a,[indbal] ; on charge la valeur de indbal dans a
inc a ; on fait un calcul sur a
dec a
call z,TIRBAL ; si a=0 alors on appelle TIRBAL
ld a,8 ; on teste la touche gauche = ligne 8 de la matrice du clavier
call 321 ; (bios) SNSMAT retourne dans la valeur de la ligne 8
bit 4,a ; bit teste la colonne 4 (0=pressé 1=non pressé)
call z,FUSGAU ; si résultat=0 on va au sous-prog FUSGAU (déplacer à droite)
call DELAI ; on ralenti l'exécution
ld a,8 ; la touche droite est aussi sur la ligne 8 de la matrice
call 321 ; on appelle le bios pour qu'il donne la valeur de la ligne 8
bit 7,a ; la colonne 7 est testée (0=pressé 1=non pressé)
call z,FUSDRO ; si résultat=0 on va au sous-prog FUSDRO (déplacer àgauche)
ld a,8 ; on teste la touche espace=ligne 8 de la matrice du clavier
call 321 ; on appelle le bios pour qu'il donne la valeur de la ligne 8
bit 0,a ; la colonne 0 est testée (0=pressée)
call z,FEU ; si résultat=0 on va au sous-prog FEU (déclenchement laser)
call DELAI ; on ralenti l'exécution
jp BOUCLE ; on redémarre la boucle principale
Comme d’habitude je vous donne le code final mis à jour à compiler et à vérifier avec votre émulateur :
; ****************************************************************
; MSX ROM Cartridge Header and Function library
; ****************************************************************
FNAME "test4.ROM"
cpu z80
ORG 4000h
INCLUDE "MSXRom-Include.asm"
db "AB"
DW DEBUT ; la ROM lance le sous-programme DEBUT au démarrage
DW 0
DW 0
DW 0
DS 6
;------------------------------------------------------------------------------
; Iinitialisation, appelé par le début du programme : DEBUT
;------------------------------------------------------------------------------
INIT:
Ld a,1 ; 1 pour screen 1
ld [64687],a ; (bios)SCRMOD adresse hexa $FCAF :mode courant de l'ecran
call 95 ; (bios)CHGMOD : change le mode l'écran
;------------------------------------------------------------------------------
; Charge la TGS avec la forme des 3 sprites
;------------------------------------------------------------------------------
ld hl,donnee ; charge les données TGS via le label: donnee
ld de,14336 ; adresse du début de la TGS
ld bc,24 ; longueur du bloc = 3 sprites de 8 octets = 24
call 92 ; LIDRVM transfert de bloc de la RAM vers la VRAM
;------------------------------------------------------------------------------
; Charge la TAS avec les attributs des 3 sprites (registre 5)
; Registre 5 => valeur x 128 = Début de l'adresse de la TAS
;------------------------------------------------------------------------------
ld c,5 ; registre 5 gère l'adresse de début de la TAS
ld b,54 ; valeur 54 = positionne début de la TAS à l'adresse 6912
call 71 ; WRTVDP Ecrire dans le VDP (VRAM)
ld hl,donne1 ; charge les données TAS au label: donne1
ld de,6912 ; adresse du début de la TAS
ld bc,12 ; longueur du bloc = 4 données pour chaque sprite (3 sprites)
call 92 ; LIDRVM transfert de bloc de la RAM vers la VRAM
;------------------------------------------------------------------------------
; Initialise les 3 variables
; indcol: indicateur de collision adresse 41000
; indbal: indicateur de balle tirée adresse 41001
; inddir: indicateur de direction de l'ennemi adresse 41002
;------------------------------------------------------------------------------
ld a,1
ld [indcol],a ; indcol = 1
ld [indbal],a ; indbal = 1
ld [inddir],a ; inddir = 1
;------------------------------------------------------------------------------
; Taille des sprites agrandis (registre 1)
;------------------------------------------------------------------------------
ld c,1 ; registre 1 gère la taille des sprites
ld b,225 ; 225 c'est la valeur pour agrandir un sprite 8 x 8
call 71 ; WRTVDP Ecrire dans un registre du VDP (VRAM)
;------------------------------------------------------------------------------
; Positionne l'adresse de début de la TNP (registre 2)
; Registre 2 => valeur x 1024 = Début de l'adresse de la TNP
;------------------------------------------------------------------------------
ld c,2 ; registre 2
ld b,6 ; valeur 6 = positionne début de la TNP à l'adresse 6144
call 71 ; WRTVDP Ecrire dans un registre du VDP (VRAM)
ret ; fin de la boucle INIT
;------------------------------------------------------------------------------
; Déplacement de la fusée à gauche
;------------------------------------------------------------------------------
FUSGAU:
ld hl,6913 ; récupère adresse de la position x du sprite dans la TAS
call 74 ; la fonction charge la valeur de l'adresse dans A donc x=A
dec a ; on teste A en le décrementant d'une unité x-1
ret z ; si le résultat de l'opération x=0 on ne fait rien
; et on quitte la sous-routine
; car cela veut dire que la fusée est à la limite de l'écran à gauche
call 77 ; sinon on charge la position x de la TAS avec la nouvelle valeur x-1
ret ; on retourne au programme principal
;------------------------------------------------------------------------------
; Déplacement de la fusée droite
;------------------------------------------------------------------------------
FUSDRO:
ld hl,6913 ; récupère adresse de la position x du sprite dans la TAS
call 74 ; la fonction charge la valeur de l'adresse dans A donc x=A
SUB 240 ; on teste A en lui enlevant 240
ret z ; si le résultat de l'opération=0 on ne fait rien
; et on quitte la sous-routine
; car cela veut dire que la fusée est à la limite de l'écran à droite
add a,241 ; rajoute les 240 pour retrouver le x initial et incrémente de 1(donc 241)
call 77 ; sinon on charge la position x de la TAS avec la nouvelle valeur x+1
ret ; on retourne au programme principal
;------------------------------------------------------------------------------
; DELAI
;------------------------------------------------------------------------------
DELAI:
ld a,255 ; c'est la valeur de départ, plus elles est haute, plus c'est lent
; a ne peut contenir qu'une valeur entre 0 et 255
DEL:
dec a ; c'est la boucle qui va décrémenter de 1 jusqu'à a=0
jp nz,DEL ; si a n'est pas égale à 0 on repart au début de la boucle
ret ; si a=0 on retourne au programme principal
;------------------------------------------------------------------------------
; Déplacement de l'envahisseur / Gestion de la direction de l'envahisseur
; indir=1 : de droite à gauche / indir=0 : de gauche à droite
;------------------------------------------------------------------------------
DEPENV:
ld a,[inddir] ; on charge en a la valeur de l'indicateur de direction
dec a ; on décremente a pour vérifier si indir=1
jp z,ENGAU ; si a=0 c'est que indir valait 1,on va au sous-prog ENGAU (à gauche)
ld hl,6917 ; sinon on récupère adresse de la position x du sprite dans la TAS
call 74 ; la fonction récupère la valeur de l'adresse dans A donc x=A
sub 240 ; on elève 240 à a
jp nz,ENDRO1 ; si le résultat n'égale pas 0 alors on va en ENDRO1 (à droite)
ld a,1 ; sinon on est au bord droit de l'écran et on change l'indicateur
ld [inddir],a ; on charge l'indicateur avec la nouvelle direction (1)
ret
;------------------------------------------------------------------------------
; Déplacement de l'envahisseur vers la droite
;------------------------------------------------------------------------------
ENDRO1:
ld hl,6917 ; on récupère adresse de la position x du sprite dans la TAS
call 74 ; la fonction récupère la valeur de l'adresse dans A donc x=A
inc a ; on incrémente a pour déplacer de 1 à droite (x+1)
call 77 ; on charge la position x de la TAS avec le x+1
ret ; on retourne au programme principal
;------------------------------------------------------------------------------
; Changement de direction (0) si l'envahisseur est arrivé au bord gauche x=0
;------------------------------------------------------------------------------
ENGAU:
ld hl,6917 ; on récupère adresse de la position x du sprite dans la TAS
call 74 ; la fonction récupère la valeur de l'adresse dans A donc x=A
SUB 1 ; on enlève 1 à a (x-1)
jp nz,ENGAU1 ; si maintenant n'est pas égale à 0 on va en ENGAU1
ld a,0 ; sinon on est au bord gauche de l'écran et on change l'indicateur
ld [inddir],a ; on charge l'indicateur avec la nouvelle direction (0)
ret
;------------------------------------------------------------------------------
; Déplacement de l'envahisseur vers la gauche
;------------------------------------------------------------------------------
ENGAU1:
ld hl,6917 ; on récupère adresse de la position x du sprite dans la TAS
call 74 ; la fonction récupère la valeur de l'adresse dans A donc x=A
dec a ; on décrémente a pour déplacer de 1 à gauche (x-1)
call 77 ; on charge la position x de la TAS avec le x+1
ret ; on retourne au programme principal
;------------------------------------------------------------------------------
; FEU
; indbal=1 pas de balle tirée / indbal=0 balle tirée
;------------------------------------------------------------------------------
FEU:
ld a,[indbal] ; on vérifie la valeur de indbal
inc a ; si indbal=0 une balle est déjà en cours
dec a ; donc on ne peut pas retirer
ret z ; et on quitte la routine
ld hl,6913 ; si indbal=1 on récupère la coord X de la fusée
call 74 ; via la TAS (lecture)
ld hl,6921 ; et on la met sa valeur dans le X du laser
call 77 ; via la TAS (écriture)
ld hl,6912 ; on récupère la coord Y de la fusée
call 74 ; via la TAS (lecture)
sub 17 ; enlève 17 à Y pour que le laser=tête de la fusée
ld hl,6920 ; et on la met sa valeur dans le Y du laser
call 77 ; via la TAS (écriture)
ld a,0 ; on passe indbal à 0
ld [indbal],a ; pour indiquer qu'un tir est en cours
ret ; retour à la boucle principale
;------------------------------------------------------------------------------
; DEPBAL
;------------------------------------------------------------------------------
DEPBAL:
ld hl,6920 ; on récupère le Y du laser
call 74 ; via la TAS (lecture)
dec a ; on décrémente Y pour que le laser monte
jp z,HAUT ; si Y=0 alors appel routine HAUT:
call 77 ; sinon on charge la nouvelle valeur dans la TAS
ret
HAUT:
ld a,200 ; on met la valeur 200 dans le Y du laser
ld hl,6920 ; le sprite sera en attente en dehors de l'écran
call 77 ; via la TAS (écriture)
ld a,1 ; et on passe indbal à 1
ld [indbal],a ; ainsi on autorise on nouveau déclenchement de laser
ret
;------------------------------------------------------------------------------
; TIRBAL
;------------------------------------------------------------------------------
TIRBAL:
call DEPBAL ; appel routine pour déplacer le sprite laser vers le haut
call DEPBAL ; opération reproduite 3 fois
call DEPBAL ; pour que le laser se déplace très vite
call 318 ; rappel du registre des collisions de sprites
bit 5,a ; on teste le bit 5
ret z ; si le bit 5=0 alors pas de collision et on quitte
ld a,0 ; sinon bit 5=1 collision alors on passe indcol à 0
ld [indcol],a ; ce qui déclenchera une explosion via le boucle principale
ret
;------------------------------------------------------------------------------
; EXPLO
;------------------------------------------------------------------------------
EXPLO:
ld a,3 ; 3 pour screen 3
ld [64687],a ; (bios)SCRMOD adresse hexa $FCAF :mode courant de l'écran
call 95 ; (bios) CHGMOD change le mode de l'écran
ld a,100 ; valeurs de départ
ld hl,0
EXPLO1:
PUSH hl
PUSH af
ld de,2048
ld bc,1000
call 92
pop af
pop hl
inc h
dec a
jp nz,EXPLO1
jp DEBUT
;------------------------------------------------------------------------------
; Boucle du jeu : Le programme démarre à DEBUT puis va à BOUCLE
;------------------------------------------------------------------------------
DEBUT: ; c'est ici que débute le programme !!!
call INIT ; on appelle le sous-prog INIT
BOUCLE: ; c'est la boucle principale du programme !!!
call DEPENV ; on appelle le déplacement de l'envahisseur
call DELAI ; on appelle le sous-prog DELAI afin de ralentir l'exécution
ld a,[indcol] ; on charge la valeur de indcol dans a
inc a ; on fait un calcul sur a
dec a
jp z,EXPLO ; si a=0 alors on appelle EXPLO
ld a,[indbal] ; on charge la valeur de indbal dans a
inc a ; on fait un calcul sur a
dec a
call z,TIRBAL ; si a=0 alors on appelle TIRBAL
ld a,8 ; on teste la touche gauche = ligne 8 de la matrice du clavier
call 321 ; (bios) SNSMAT retourne dans la valeur de la ligne 8
bit 4,a ; bit teste la colonne 4 (0=pressé 1=non pressé)
call z,FUSGAU ; si résultat=0 on va au sous-prog FUSGAU (déplacer à droite)
call DELAI ; on ralenti l'exécution
ld a,8 ; la touche droite est aussi sur la ligne 8 de la matrice
call 321 ; on appelle le bios pour qu'il donne la valeur de la ligne 8
bit 7,a ; la colonne 7 est testée (0=pressé 1=non pressé)
call z,FUSDRO ; si résultat=0 on va au sous-prog FUSDRO (déplacer àgauche)
ld a,8 ; on teste la touche espace=ligne 8 de la matrice du clavier
call 321 ; on appelle le bios pour qu'il donne la valeur de la ligne 8
bit 0,a ; la colonne 0 est testée (0=pressée)
call z,FEU ; si résultat=0 on va au sous-prog FEU (déclenchement laser)
call DELAI ; on ralenti l'exécution
jp BOUCLE ; on redémarre la boucle principale
;------------------------------------------------------------------------------
; Les données
;------------------------------------------------------------------------------
donnee:
db 24,24,126,126 ; sprite 1 pour la TGS
db 255,255,255,0 ; sprite 1 " "
db 60,126,153,255 ; sprite 2 " "
db 102,60,66,36 ; sprite 2 " "
db 24,24,24,24 ; sprite 3 " "
db 24,24,24,24 ; sprite 3 " "
donne1:
db 170,100,0,15 ; Attributs du sprite 0 (position x, y,n°sprite,couleur)
db 0,0,1,12 ; Attributs du sprite 1 (position x, y,n°sprite,couleur)
db 200,0,2,1 ; Attributs du sprite 2 (position x, y,n°sprite,couleur)
;**************************************************************************************************
; Standard Libraries
;**************************************************************************************************
INCLUDE "MSXRom-Lib.asm" ; intègre la librairie MSX
END: EQU $
indcol: EQU 41000 ; défini l'adresse de la variable indcol (registre des collisions)
indbal: EQU 41001 ; défini l'adresse de la variable indbal
inddir: EQU 41002 ; défini l'adresse de la variable inddir
;**************************************************************************************************
; RAM Definitions
;**************************************************************************************************
;-------------------
; Définit que le programme démarrera au début de la RAM disponible
;-------------------
ORG RAMSTART