< All Topics
Print

08-Afficher un sprite

La pratique

On va maintenant taper notre 1er code du shoot-em-up.

On va enlever la partie du code correspondant à l’affichage du caractère (qui était notre exemple précédent), et donc repartir du fichier sources vierge exemple.asm.

Quelques informations sur le début du code :

  • Le programme est en SCREEN 1, 3 SPRITES vont être créés (la fusée du joueur, l’ennemi/envahisseur, le tir)
  • On charge les formes des SPRITES dans la table TGS
  • On charge les attributs des SPRITES dans la table TAS
  • On va positionner 3 indicateurs qui nous serviront à déplacer les SPRITES plus tard.
  • On spécifie la taille des SPRITES

Voici l’entête, j’ai juste modifié le nom du fichier qui sera généré via FNAME :

; ****************************************************************
; MSX ROM Cartridge Header and Function library
; ****************************************************************
FNAME "test1.ROM"
cpu z80

ORG 4000h

INCLUDE "MSXRom-Include.asm"

db "AB"
DW INIT             ; Fait démarrer la ROM au label INIT
DW 0
DW 0
DW 0
DS 6

SCREEN 1

Passons au début du code proprement dit et par le label (appelé aussi “Etiquette”) ‘INIT:’ (c’est par lui que la .ROM démarrera) :

;------------------------------------------------------------------------------
; Démarrage du programme à INIT
;------------------------------------------------------------------------------
INIT:
LD   A,1            ; 1 pour screen 1
ld   [64687],A      ; (bios)SCRMOD adresse $FCAF :mode courant de l'ecran
call 95             ; (bios) CHGMOD : change le mode l'écran

Pas de modification par rapport aux précédent exemple, on se place en SCREEN 1.


CHARGEMENT DE LA TGS

;------------------------------------------------------------------------------
; Charge la TGS avec la forme des 3 sprites
;------------------------------------------------------------------------------
ld   hl,donnee      ; va chercher les données TGS au 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

Je commence par la fin, on va donner un ordre de transfert de données (les formes des SPRITES) qui seront écrites dans notre code (donc dans la RAM) vers la table TGS (donc la VRAM), on ne va pas pouvoir utiliser un ordre du style “écrire dans la VRAM” car cela est limité à une adresse mémoire et une donnée.
Pour de gros blocs de données entre RAM et VRAM, il existe une routine BIOS qui fait le boulôt : LIDRVM ou en langage assembleur CALL 92 (en décimal) ou CALL $5C (en hexadécimal). Cette routine, pour être déclenchée, a besoin de 3 paramètres en entrée :

  • HL, pour les données des formes de sprites
  • DE, l’adresse de la TGS à partir de laquelle on veut écrire (une donnée = une case mémoire)
  • BC, la longueurde bloc total à transférer (en octets)

Voici d’ailleurs une définition de cette routine :

LDIRVM
Address : #005C
Function : Block transfer to VRAM from memory
Input : BC - blocklength
DE - Start address of VRAM
HL - Start address of memory
Registers: All

Vu que l’on a 3 SPRITES à charger (8 pixels sur 8 pixels donc 8 octets) on a 3 x 8 = 24 données à envoyer en VRAM. On ne va pas les taper juste après la virgule du HL il ne saurait pas le traiter. On va plutôt lui dire où il peut les trouver et les “digérer à son rythme”, pour cela on va créer un nouveau label donnee qui abritera ces 24 données.

Les données en général sont stockées plutôt à la fin du programme codé et chaque ligne doit commencer par un DB.

Ce label donnee je l’ai placé juste avant le pied de page, voici comment il se présente :

;------------------------------------------------------------------------------
; Les données
;------------------------------------------------------------------------------
donnee:
db     24,24,126,126  ; sprite 0 pour la TGS
db     255,255,255,0  ; sprite 0
db     60,126,153,255 ; sprite 1
db     102,60,66,36   ; sprite 1
db     24,24,24,24    ; sprite 2
db     24,24,24,24    ; sprite 2

Pour le SPRITE 0, les 2 premières lignes donnent 8 données qui forment un SPRITE joueur (la fusée) :

Pour le SPRITE 1, les 2 lignes suivantes qui donnent la forme suivante pour l’ennemi :

Pour le SPRITE 2, les 2 dernières lignes qui donnent la forme suivante pour le tir :

Du coup, tout devient simple:

  • On dit à HL de se charger (via l’instruction ‘LD’) en allant chercher les données que l’on a mis en fin de code dont le label est donnee.
  • On charge en DE le début de l’adresse de la TGS (je vous ai dit dans un des premiers articles qu’elle débutait à 14336 en SCREEN 1).
  • On charge en BC la longueur du bloc ‘donnee’
  • On exécute CALL 92 qui fera le boulot vu qu’on lui a donné les entrants.

CHARGEMENT DE LA TAS

;-------------------
; 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      ; va chercher 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

J’y ai intégré un petit exercice de manière à progresser au fur et à mesure du code :

Je vous ai dit qu’en SCREEN 1 la table des attributs de SPRITE (TAS) débutait à l’adresse 6912. Sur MSX on pourrait changer cette adresse pour diverses raisons (et d’ailleurs, ces adresses de tables sont succeptibles de changer en fonction des modes SCREEN que l’on utilise).

Admettons que l’on ne sait pas si la TAS est bien positionnée pour débuter à 6912. Je vous ai expliqué précédemment qu’en VRAM, il existait des tables et des registres, et bien chaque table est gérée via un de ces registres.

Pour notre TAS c’est le registre 5.

Toute valeur que l’on écrira dans ce registre sera automatiquement multipliée par 128 et définira l’adresse de début de la table TAS. Donc pour obtenir 6912, on le divise par 128 et cela donne 54.

Finalement, pour définir la TAS à 6912, il suffit d’écrire dans le registre 5 la valeur 54.

C’est l’objet des 3 premières lignes du code ci-dessus jusqu’au CALL 71 (ou 47 en hexa) qui est la routine BIOS qui existe pour écrire dans les registres du VDP :

WRTVDP
Address : #0047
Function : write data in the VDP-register
Input : B - data to write
C - number of the register
Registers: AF, BC

Fin de l’exercice.

Passons au chargement de la TAS : d’abord la procédure va ressembler au PUTSPRITE du BASIC MSX donc cela devrait vous paraître simple et ensuite on va procéder au codage assembleur de la même manière que pour la TGS.

On va donner 4 infos pour chaque SPRITE créé :

  • La position verticale du SPRITE à l’écran
  • La position horizontale du SPRITE à l’écran
  • Le numéro du SPRITE
  • La couleur du SPRITE

Mais avant de décrire le bout de code, il faut préciser une particularité importante des SPRITES, ils ne se localisent pas comme les caractères (32 colonnes sur 24 lignes) mais sur des coordonnées plus fines.

En SCREEN 1 c’est 256 pixels sur 192 pixels :
Du coup, on va pas trop s’attarder sur la méthode car c’est la même que pour la TGS et le commentaire associé au code est explicite. On va plutôt détailler le label donne1 associé (que j’ai placé au niveau du code, après le label donnee et avant le pied de page).

 Résolution graphique SCREEN 1

donne1:
db     170,100,0,15   ; Attributs du sprite 0 (position y, x, n°sprite,couleur)
db     0,0,1,12       ; Attributs du sprite 1 (position y, x, n°sprite,couleur)
db     200,0,2,1      ; Attributs du sprite 2 (position y, x, n°sprite,couleur)

Je vous avais dit qu’il y a 4 informations à rentrer par SPRITE dans la TAS.

Du coup, pour le SPRITE de la 1ere ligne, il aura au 1er affichage à l’écran la coordonnée vertical=170 horizontal=100 pour le SPRITE numéro 0 et qui aura la couleur 15 (blanc), vous avez reconnu le sprite joueur/fusée.

Je vous laisse décrypter les 2 autres lignes pour les 2 autres SPRITES.

Juste pour finir sur ce code, on voit que l’on a 12 données à rentrer, cela tombe bien c’est ce que l’on charge en BC avant de lancer le CALL 92 qui devient familier pour vous maintenant.


3 INDICATEURS DE DEPLACEMENT

Au début de l’article j’ai listé le fait que l’on allait positionner 3 indicateurs nécessaire au déplacement des sprites. On ne va pas les utiliser de suite mais un minimum d’information quand même :

  • indcol : un indicateur de collision
  • indbal : un indicateur de balle tirée
  • inddir : un indicateur de direction pour l’ennemi

Pour que le programme nous autorise à manipuler des valeurs avec ces termes, on doit d’abord les déclarer comme suit :

indcol: EQU 41000       ; défini l'adresse de la variable indcol
indbal: EQU 41001       ; défini l'adresse de la variable indbal
inddir: EQU 41002       ; défini l'adresse de la variable inddir

Ainsi on dit au programme qu’à chaque fois que l’on utilisera indcol cela voudra dire en fait que l’on sollicite l’adresse 41000 de la RAM (un choix arbitraire d’adresse libre en RAM). On pourrait traduire EQU par ‘équivaut’.

Idem pour les 2 autres déclarations. ce code je l’ai placé dans le bas de page, mais vous pourriez le mettre dans l’entête, cela n’a pas grande importance.

Ensuite, je vais assigner des valeurs à ces adresses, pour cela je place le code suivant à la suite du code sur le chargement de la TAS puisque cela fait partie de l’initialisation du jeu :

;------------------------------------------------------------------------------
; Initialise les 3 variables 
; indcol: indicateur de collision 41000 (adresse)
; indbal: indicateur de balle tirée  41001 (adresse)
; inddir: indicateur de direction de l'ennemi 41002 (adresse)
;------------------------------------------------------------------------------
ld   a,1
ld [indcol],a       ; indcol = 1
ld [indbal],a       ; indbal = 1
ld [inddir],a       ; inddir = 1

Rien d’exceptionnel, je charge ces pseudo adresses (pour cela, je les passe entre crochets) avec un A qui vaut 1 puisque dans la 1ere ligne j’ai assigné 1 à l’accumulateur A.


TAILLE DES SPRITES

;------------------------------------------------------------------------------
; Taille des sprites agrandis (registre 1)
;------------------------------------------------------------------------------
ld   c,1            ; registre 1 gère la talle des sprites
ld   b,225          ; 225 c'est la valeur pour agrandir un sprite 8 x 8
call 71             ; WRTVDP Ecrire dans le VDP (VRAM)

Sur MSX on peut agir sur la taille des SPRITES, pour cela on utilise le registre 1 de la VRAM qui accepte 4 valeurs possibles :

SPRITES taille 8 x 8 normal = 224
SPRITES taille 8 x 8 agrandis = 225
SPRITES taille 16 x 16 normal = 226
SPRITES taille 16 x 16 agrandis = 227

Vu que l’on a déclaré dans la TGS des SPRITES de taille 8 x 8, on peut utiliser la valeur 224 ou 225 (vous pouvez vous amuser à changer la valeur pour voir la différence), dans notre jeux on va utiliser 225.

Le code devrait donc vous parler maintenant : on charge ‘C’ avec le numéro du registre 1 et ‘B’ avec la valeur à écrire dans ce registre, ce sont les 2 entrants nécessaires au CALL 71 qui permet d’exécuter un ordre d’écriture dans un registre VRAM.

C’était le dernier bout de code du label INIT:
Si on veut voir le résultat il suffit de rajouter une boucle infinie, pour que le programme enchaine dessus après avoir exécuté toutes les actions du INIT:, comme cela par exemple :

LOOP:
     jp LOOP

Vous pouvez faire F9 dans CONTEXT, pour lancer la compilation, le fichier généré va s’appeler "test1.ROM" et c’est celui-là que vous faites glisser dans l’émulateur blueMSX.

Vous allez voir apparaître ceci :

Mais où est passé le SPRITE représentant la balle (ou le lazer si vous voulez) ?

Le premier réflex est de regarder ce que l’on a déclaré comme coordonnées pour le SPRITE 2 qui le représente dans la TAS.

Et que voit-on ? Position verticale = 200….alors que la résolution nous permet d’aller à un maximum de 191. En le positionnant au delà il disparait dans la bordure de l’écran mais il est déjà présent et il sera plus rapide à positionner correctement lorsqu’on tirera que si on devait le charger dans la TAS puis calculer sa position au moment du tir.

Si vous voulez le voir apparaitre et bien jouez avec ses coordonnées, compilez et essayez votre .ROM dans l’émulateur.

Voici le code complet de cet article :

; ****************************************************************
; MSX ROM Cartridge Header and Function library
; ****************************************************************
FNAME "test1.ROM"
cpu z80

ORG 4000h

INCLUDE "MSXRom-Include.asm"

db "AB"
DW INIT
DW 0
DW 0
DW 0
DS 6

;------------------------------------------------------------------------------
; Démarrage du programme à INIT
;------------------------------------------------------------------------------
INIT:
LD   A,1            ; 1 pour screen 1
ld   [64687],A      ; (bios)SCRMOD adresse $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      ; va chercher les données TGS au 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      ; va chercher 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 41000 (adresse)
; indbal: indicateur de balle tirée  41001 (adresse)
; inddir: indicateur de direction de l'ennemi 41002 (adresse)
;------------------------------------------------------------------------------
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 talle des sprites
ld   b,225          ; 225 c'est la valeur pour agrandir un sprite 8 x 8
call 71             ; WRTVDP Ecrire dans le VDP (VRAM)


LOOP:
     jp LOOP

;------------------------------------------------------------------------------
; 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
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
Table of Contents