10-10
Gestion du sprite de la voiture
10-10-1
Introduction
Nous voici
donc devant un sujet délicat qui est celui des sprites. Tous les jeux d’actions
ont leur lot de petits bonhommes, de vaisseaux ou autres petits dessins qui
s’affichent rapidement à l’écran. Au travers de la voiture, nous allons voir
comment gérer et afficher de tels dessins.
10-10-2
Représentation graphique des sprites
Comme nous
l’avons vu dans le chapitre 10-2, la voiture
comprend 8 angles + 4 positions par angle. Ces 4 positions correspondent aux
offsets des pixels dans les octets.
Afin
d’uniformiser et donc de simplifier le traitement de ces sprites, ils ont tous
une taille de 16x10 pixels, soient 4x10 octets (en mode 1, 1 octet = 4 pixels).
Ainsi, quand on les dessine on prend en compte tous les 16x10 pixels. Mais
alors une question se pose : la voiture frôle les bords de piste et passe
sur les marquages verts sans que l’on voit qu’il s’agit d’un rectangle, comment
cela se peut-il ? et bien dans tous les sprites, il y a toujours une
couleur « transparente » ou couleur de fond. Cette couleur n’a bien
sure pas de réalité physique. En fait quand on dessine la voiture pixels par
pixels, on remplace cette couleur par celle du fond de l ‘écran, d’où
l’impression de transparence. Cette couleur est de préférence la n°0, car on se
rappelle bien que le microprocesseur est plus sensible au 0 qu’à n’importe quel
autre nombre. Ici c’est le orange qui servira donc de transparence et c’est
pour cela que la voiture et l’explosion n’en contiennent pas.
Cette
couleur transparente possède aussi une autre fonction qui intervient dans la
détection de collision. Quand la voiture rentre dans le décor, c’est que l’un
de ses pixels vert, blanc ou rouge s’est superposé au bord de piste. On ne
calculera donc pas les collisions sur les pixels oranges et donc plus
généralement sur la couleur n°0.
10-10-3
Calcul des adresses et offsets écran
Certes,
les vecteurs systèmes offrent tout un tas de solutions pour dessiner des pixels
ou tester des couleurs de fond en fonction de coordonnées (X,Y). Mais ces
vecteurs ne sont généralement pas assez rapides pour des jeux gourmands en
graphismes et en vitesse. Dans ce cas, il faut directement attaquer la mémoire
vidéo avec les contraintes que ça implique. Si encore la voiture se déplaçait
en fonction des caractères texte ça irait, mais là elle se déplace de pixels en
pixels.
Ici la
voiture est bien gérée en (X,Y), mais avant de la dessiner on convertit ces
coordonnées en 3 valeurs d’adresses :
1. PLFOND :
pointeur sur l’adresse de la ligne écran du bord haut/gauche du sprite.
2. SEGMENT : ou
n° d’octet sur lequel se trouve le bord haut/gauche du sprite.
3. PIXEL : n°
du pixel sur lequel se trouve le bord dans l’octet courant (0,1,2 ou 3).
PLFOND est
un pointeur sur l’adresse de la ligne écran du bord haut/gauche du sprite.
C’est un peu compliqué à expliquer comme ça, mais il contient une adresse dans
laquelle on trouve l’adresse de la ligne écran. Pour mieux comprendre, prenons
le tableau suivant :
LIGNES DW &C000,&C800,&D000,&D800,&E000,&E800,&F000,&F800 ; 0-7
DW &C050,&C850,&D050,&D850,&E050,&E850,&F050,&F850 ; 8-15
DW &C0A0,&C8A0,&D0A0,&D8A0,&E0A0,&E8A0,&F0A0,&F8A0 ; 16-23
DW &C0F0,&C8F0,&D0F0,&D8F0,&E0F0,&E8F0,&F0F0,&F8F0 ; 24-31
DW &C140,&C940,&D140,&D940,&E140,&E940,&F140,&F940 ; 32-39
DW &C190,&C990,&D190,&D990,&E190,&E990,&F190,&F990 ; 40-47
DW &C1E0,&C9E0,&D1E0,&D9E0,&E1E0,&E9E0,&F1E0,&F9E0 ; 48-55
DW &C230,&CA30,&D230,&DA30,&E230,&EA30,&F230,&FA30 ; 56-63
DW &C280,&CA80,&D280,&DA80,&E280,&EA80,&F280,&FA80 ; 64-71
DW &C2D0,&CAD0,&D2D0,&DAD0,&E2D0,&EAD0,&F2D0,&FAD0 ; 72-79
DW &C320,&CB20,&D320,&DB20,&E320,&EB20,&F320,&FB20 ; 80-87
DW &C370,&CB70,&D370,&DB70,&E370,&EB70,&F370,&FB70 ; 88-95
DW &C3C0,&CBC0,&D3C0,&DBC0,&E3C0,&EBC0,&F3C0,&FBC0 ; 96-103
DW &C410,&CC10,&D410,&DC10,&E410,&EC10,&F410,&FC10 ; 104-111
DW &C460,&CC60,&D460,&DC60,&E460,&EC60,&F460,&FC60 ; 112-119
DW &C4B0,&CCB0,&D4B0,&DCB0,&E4B0,&ECB0,&F4B0,&FCB0 ; 120-127
DW &C500,&CD00,&D500,&DD00,&E500,&ED00,&F500,&FD00 ; 128-135
DW &C550,&CD50,&D550,&DD50,&E550,&ED50,&F550,&FD50 ; 136-143
DW &C5A0,&CDA0,&D5A0,&DDA0,&E5A0,&EDA0,&F5A0,&FDA0 ; 144-151
DW &C5F0,&CDF0,&D5F0,&DDF0,&E5F0,&EDF0,&F5F0,&FDF0 ; 152-159
DW &C640,&CE40,&D640,&DE40,&E640,&EE40,&F640,&FE40 ; 160-167
DW
&C690,&CE90,&D690,&DE90,&E690,&EE90,&F690,&FE90 ; 168-175
DW &C6E0,&CEE0,&D6E0,&DEE0,&E6E0,&EEE0,&F6E0,&FEE0 ; 176-183
DW &C730,&CF30,&D730,&DF30,&E730,&EF30,&F730,&FF30 ; 184-191
DW &C780,&CF80,&D780,&DF80,&E780,&EF80,&F780,&FF80 ; 192-199
Vous
l’aurez compris, ce tableau contient toutes les adresses des lignes de l’écran
de 0 à 199. Il devient donc très simple de calculer une adresse de ligne en
fonction de la coordonnée Y du bord haut/gauche de la voiture :
PLFOND =
LIGNES + 2 x Y
Adresse
ligne Y = (PLFOND)
Exemple :
Y = 1
=> PLFOND = LIGNES + 2
Adresse
ligne Y = (LIGNES + 2) = &C800
Ainsi,
PLFOND contient l’adresse LIGNES + 2 x Y, et le contenu de cette adresse est
l’adresse écran de la ligne Y. Mieux encore, en incrémentant PLFOND de 2, on
obtient l’adresse de la ligne écran juste en dessous :
Adresse
ligne Y + 1 = (PLFOND + 2)
Ainsi,
calculer toutes les adresses des lignes du sprite se résume à une histoire
d’incrément de 2.
Maintenant
que l’on a l’adresse des lignes, il faut quand même connaître l’octet
correspondant à la coordonnée X. Ceci permettra d’avoir l’adresse écran
complète du bord haut/gauche du sprite à l ‘écran. En plus, il faut
ajouter ce numéro à chaque fois que l’on récupère l’adresse de la ligne
suivante.
SEGMENT =
X / 4
On isole
ensuite le n° de pixel en ne conservant que les 2 bits de poids faible de X :
PIXEL = X
AND 3
10-10-4
Recherche du sprite en mémoire
Les sprites de la voiture font tous TSPRITE octets et sont rangés en mémoire à partir de l’étiquette VOITURE. Il n’y a d’ailleurs qu’une seule étiquette pour toute la liste de sprites. Ils sont ensuite rangés de la manière suivante :
Donc le calcul pour trouver l’adresse mémoire du sprite correspondant à l’angle et au pixel courant est :
Adresse sprite
= VOITURE + TSPRITE x (4 x angle + n° pixel)
10-10-5
Détection de collision
Quand la
voiture touche le bord de la piste, elle explose ! mais comment fait-on
pour savoir que la voiture a touché ? et bien c’est simple, si au moins un
des pixels visibles de la voiture se superpose à un pixel blanc ou rouge, c’est
que le bord de piste est atteint. Bon en pratique c’est moins simple que ça,
car il faut se rappeler du codage des pixels dans un octet de mémoire vidéo en
mode 1. Soient les 4 pixels de couleur 2 (%10) suivant :
|
B1 |
B0 |
Pixel C0 |
1 |
0 |
Pixel C1 |
1 |
0 |
Pixel C2 |
1 |
0 |
Pixel C3 |
1 |
0 |
|
OCTET EN MODE 1 |
|||||||
|
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
Codage |
C0-B0 |
C1-B0 |
C2-B0 |
C3-B0 |
C0-B1 |
C1-B1 |
C2-B1 |
C3-B1 |
Résultat |
0 |
0 |
0 |
0 |
1 |
1 |
1 |
1 |
Les poids
forts des couleurs se trouvent à droite, et les poids faibles à gauche. Il y a
un certain avantage cependant, car les couleurs 2 (blanc) et 3 (rouge) possèdent
un bit de poids fort à 1, alors que les couleurs 0 (orange) et 1 (vert) ont un
bit de poids fort à 0. Prenons le cas des deux octets suivant :
Voiture |
2 |
2 |
1 |
0 |
= % |
0 |
0 |
1 |
0 |
1 |
1 |
0 |
0 |
Fond |
0 |
0 |
2 |
3 |
= % |
0 |
0 |
0 |
1 |
0 |
0 |
1 |
1 |
Là le
pixel vert de la voiture touche la bordure blanche, il y a donc collision. Pour
le voir, on réalise plusieurs opérations logiques :
10-10-6
Dessin du sprite avec transparence
Si un
pixel de la voiture est orange, on recopie le pixel de fond, sinon on dessine
le pixel de la voiture. Encore une fois, on travaille sur des octets.
Exemple :
Voiture |
2 |
2 |
1 |
0 |
= % |
0 |
0 |
1 |
0 |
1 |
1 |
0 |
0 |
Fond |
0 |
1 |
1 |
1 |
= % |
0 |
1 |
1 |
1 |
0 |
0 |
0 |
0 |
Résultat |
2 |
2 |
1 |
1 |
= % |
0 |
0 |
1 |
1 |
1 |
1 |
0 |
0 |
Les 2
premiers pixels de fond ne sont pas pris en compte. Le 3ème reste vert car
celui de la voiture l’est aussi. Le 4ème est enfin pris en compte car celui de
la voiture est orange. Pour ce faire, on réalise aussi plusieurs opérations
logiques :
10-10-7
Etude du programme
Le morceau
de programme chargé d’afficher le sprite est assez conséquent, mais comme nous l’avons
vu, il y a beaucoup d’opérations à faire. Tout d’abord on regarde s’il faut
restituer le fond de la voiture. Ce n’est le cas que si la voiture ne vient pas
d’être placée sur la grille de départ. Dans ce cas, c’est le sous-programme RESTFOND qui se charge de cette restitution. Sinon
le programme saute à DESVOIT :
LD
A,(DRAPFOND) ; Restitution de fond d'écran?
OR
A
JR
Z,DESVOIT ; Non => dessin de la voiture directement
CALL RESTFOND ;
Restitution du fond d'écran de la voiture
Le programme calcule ensuite PLFOND, le pointeur sur l’adresse de la ligne haute du sprite. PLFOND = LIGNES + 2 x YVOITURE.
DESVOIT LD A,(YVOITURE)
LD L,A
LD H,0
ADD HL,HL
; HL = 2 x Y pour un pointage sur 16 bits
LD
DE,LIGNES
ADD
HL,DE ; HL
pointe l'adresse de la ligne courante
LD
(PLFOND),HL ; mémorisation du pointeur de
ligne du fond
Puis le n° de segment (ou octet) et le n° de pixel sont calculés dans la foulée. SEGMENT = XVOITURE / 4, PIXEL = XVOITURE AND 3.
LD
DE,(XVOITURE) ; DE = X
LD A,E
AND %00000011 ; A = N°
de pixel
LD (PIXEL),A ;
Mémorisation de ce n°
SRL D
RR E
SRL D
RR
E ; DE = X/4 = segment
de la coordonnée X
LD
(SEGMENT),DE ; Segment mémorisé
Le
programme calcule ensuite l’adresse mémoire du sprite de la voiture
correspondant à l’angle et au n° de pixel courant. Adresse = VOITURE + TSPRITE
x (4 angle + n° de pixel). La multiplication se fait par boucles d’addition,
sachant que (4 angle + n° de pixel) est au maximum égal à 31.
LD
HL,VOITURE ; HL pointe les sprites de la
voiture
LD A,(ANGLE)
SLA A
SLA A
LD B,A
LD A,(PIXEL)
ADD B ;
A = 4 x angle + n° de pixel
JR
Z,DESVOIT2 ; A = 0? Oui => DESVOIT2
LD
DE,TSPRITE ; DE = taille d'un sprite en
octet
DESVOIT1
ADD HL,DE
; HL = VOITURE + TSPRITE x (4 angle + n°
pixel)
DEC
A
JR
NZ,DESVOIT1
DE pointe alors l’adresse du sprite. B et C contiennent respectivement la hauteur en lignes et la largeur en octets du sprite. Ils serviront ensuite de compteurs pour le balayage complet du sprite. HL = PLAFOND pour le pointage sur les adresses des lignes écrans du sprite affiché. IX pointe la zone de mémorisation du fond d’écran du sprite FOND.
DESVOIT2
EX DE,HL
; DE pointe sur le sprite courant
LD
A,1
LD
(DRAPFOND),A ; Fond actif
LD
B,HSPRITE ; B = hauteur du sprite pour
comptage
LD
C,LOSPRITE ; C = largeur en octets pour
comptage
LD
HL,(PLFOND) ; HL = pointeur de ligne du fond
LD
IX,FOND ; IX = pointeur sur la
mémoire de fond du sprite
On arrive
ensuite à la boucle principale du programme, celle qui scanne le sprite octets
par octets. HL et DE sont sauvegardés puis permettent le calcul du bord gauche
de toutes les lignes du sprite. En gros, HL = (PLFOND) + SEGMENT = adresse de
ligne courante + n° de segment.
DESVOIT3
PUSH HL ;
mémorisation du pointeur d'adresse de la ligne courante
PUSH
DE ; mémorisation du
pointeur sur le sprite
LD E,(HL)
INC HL
LD
D,(HL) ; DE =
adresse de la ligne courante
EX
DE,HL ;
puis HL
LD
DE,(SEGMENT) ; DE = n° de segment de X
ADD
HL,DE ; HL =
adresse écran de la ligne de sprite courante
POP
DE ; Restitution du
pointeur sur le sprite
BC est sauvegardé pour pouvoir faire quelques calculs. On récupère l’octet de fond d’écran courant et on le mémorise.
DESVOIT4
PUSH BC ;
sauvegarde des compteurs de scanning du sprite
LD
A,(HL) ; A =
octet de fond d'écran courant
LD
(IX),A ;
mémorisation de l'octet de fond
INC
IX
Le programme fait alors une détection de collision comme expliquée plus haut. Si au moins un pixel de la voiture a touché le bord, OBSTACLE est mis à 1.
AND
&0F ; Poids faible pour
analyse du blanc et rouge (coul. 2 & 3)
LD B,A
; B = poids faible
RLCA
RLCA
RLCA
RLCA ; A = Inversion poids
faible <-> poids fort
OR
B ; poids fort = poids
faible (masque de couleurs 3)
LD
B,A ;
recopie dans B
LD
A,(DE) ; A = octet courant du sprite
AND
B ; à l'emplacement du
masque = couleur 0 (transparente)?
JR
Z,DESVOIT5 ; Oui => DESVOIT5
LD
A,1
LD
(OBSTACLE),A ; non => indicateur d'obstacle
rencontré
Puis dessine l’octet à l’écran avec gestion de la transparence :
DESVOIT5
LD A,(DE)
; A = octet courant du sprite
LD
C,A ;
puis C
RLCA
RLCA
RLCA
RLCA ;
Inversion poids faible <-> poids fort
OR
C ; A = masque des
couleurs de sprite à conserver
CPL ;
inversion pour masque des couleurs de fond à supprimer
LD
B,A ; B
= masque des couleurs à supprimer
LD
A,(HL) ; A =
octet de fond d'écran courant
AND
B ; suppression des
couleurs génantes
OR
C ; ajout des couleurs
du sprite
LD
(HL),A ; affichage du résultat
Les compteurs B et C sont restitués. DE et HL sont incrémentés pour passer à l’octet suivant. Tant que la ligne de sprite courante n’est pas entièrement scannée, on reboucle en DESVOIT4
POP
BC ; restitution des
compteurs de scanning du sprite
INC
DE ; incrément du pointeur
sur le sprite
INC
HL ; incrément du segment
d'écran courant
DEC
C ; ligne de sprite
complètement scannée?
JR
NZ,DESVOIT4 ; non => DESVOIT4
Sinon on réinitialise C, on restitue HL et on l’incrémente de 2 pour pointer sur l’adresse de la ligne écran juste en dessous.
LD
C,LOSPRITE ; C = largeur en octets du
sprite
POP HL ;
Restitution du pointeur d'adresse de ligne courante
INC
HL
INC
HL ; HL pointe l'adresse de
la ligne suivante
DJNZ
DESVOIT3 ; Rebouclage tant qu'il reste au moins une ligne