Avant de commencer nous allons jeter un oeil au fichier "SPRITE.PC1", nous y retrouvons les sprites d'animation de notre Beast ainsi que les masques associés pour chaque position. Je rappel rapidement le principe du masque, il sert à "éteindre" les pixels non transparent du sprite lorsqu'on l'applique au décor de fond afin de rendre transparent les pixels du sprite ayant la couleur 0. Pour cela il suffit de combiner le masque et le fond par un ET logique, ce qui mettra à 0 les pixels du fond qui seront écrasés par le sprite, puis de combiner le sprite et le fond avec un OU logique pour copier les pixels du sprite sur les pixels "éteint" du fond. Bien évidemment toutes ces opérations complexes seront réalisés grace au Blitter.
Ouvrez le fichier "SOTB_02.S", on repart bien sur du fichier précédent et on va ajouter une fonction d'initialisation des sprite "InitSprite" qui va avoir trois objectifs, tout d'abord charger et décompacter notre image, exactement comme on le faisait pour le décor du fond. Puis nous mettrons à jour notre banque de sprites dont le format d'origine est simple, un premier mot indiquant le nombre de sprites de la banques, puis pour chaque sprite les coordonnées haut/gauche du sprite et celles du masque, en voici un extrait :
BeastSpriteBank:
dc.w 6 ; Nombre de sprites
Beast1:
dc.w 0,44 ; Sprite left, top
dc.w 0,96 ; Mask left, top
Beast2:
...etc
La routine d'initialisation de la banque "SetupSpriteBank" a pour objectif de remplacer les
coordonnées des objets par leurs adresses, car ce sont elles qui seront utilisées par le Blitter.
Enfin nous sauvegardons le décor de fond dans un buffer afin de l'utiliser lors de la
restauration du fond entre deux affichages du sprite, c'est le role de la fonction "SaveBeastBg".
Ok, tout est prêt, l'animation du décor ne change pas depuis la dernière fois et est
toujours prise en charge par la VBL et les interruptions du TimerB. Le sprite est
lui animé en grande partie dans le corps de notre boucle principale :
MainLoop:
move.w #$0,FlagVBL ; Flag VBL a 0
WaitVBL:
tst.w FlagVBL ; Attends la VBL
beq.s WaitVBL
RASTER $700
jsr AnimateBeast ; Controle l'animation du sprite
jsr DrawBeast ; Affiche le sprite
RASTER $000
WaitRestoreSprite:
cmpi.w #3,FlagGrass ; Attends la fin de l'affichage du sprite pour restaurer le fond
blt.s WaitRestoreSprite
RASTER $077
jsr RestoreBeast ; Restaure le fond d'écran
RASTER $000
cmpi.b #KBD_SPACE,ACIA_BASE+ACIA_RDR ; Test l'appui sur ESPACE
bne MainLoop
La première étape consiste à gérer l'animation de la bête, en gros il faut afficher les différents sprites de la course les uns à la suite des autres pour donner l'illusion du mouvement. La fonction "AnimateBeast" joue ce rôle, pour cela on s'appuie sur trois informations qui sont stockées dans une table d'animation "BeastAnimTable", tout d'abord un compteur de frame qui indique de combien d'images (frame) notre animation se compose, dans notre cas 6 images sont utiles pour rendre l'animation de course. Puis, pour chaque frame nous spécifions le nombre de VBL pendant lesquels la frame doit être affichée et enfin l'adresse du sprite correspondant à la frame en cours. En voici un extrait :
BeastAnimTable:
dc.w 6 ; Nombre de frames de l'animation
dc.w 8 ; Nombre de VBL d'affichage du sprite
dc.l Beast1 ; Adresse du sprite
dc.w 8 ; Nombre de VBL d'affichage du sprite
dc.l Beast2 ; Adresse du sprite
...etc
A chaque VBL, notre fonction va regarder si il y'a une nouvelle frame à jouer, si tel est le
cas il faut aussi controler que l'on a pas atteint la fin de la table, auquel cas on revient
à la première frame, puis on met à jour le compteur de VBL de la frame ainsi que l'adresse
du sprite en cours d'affichage, il ne nous reste plus qu'à afficher le sprite.
C'est la fonction "DrawBeast" à qui revient cette lourde tache, je dis lourde parceque ce n'est pas aussi simple qu'on l'imagine. En effet, notre sprite doit s'afficher sur un décor en mouvement et de plus avec des tranches qui scrollent à des vitesses différentes. Donc notre sprite couvre trois niveau de scrolling, le niveau de la montagne et deux niveaux de l'herbe, il va donc falloir gérer un offset de décalage différent en fonction du niveau sur lequel le morceau de sprite s'affiche. Nous nous reposons pour cela sur une table qui est mise à jour à chaque VBL par la fonction "PrepareBeastAnim", cette dernière va calculer pour chaque tranche du sprite l'offset de décalage en fonction de la position courante du niveau de scrolling du décor, la première tranche couvre le décor de la montagne et les deux suivantes les deux premiers niveaux de l'herbe. Au final notre table va contenir trois informations, l'offset décalage du sprite, l'adresse de destination du sprite et l'adresse pour la restauration du fond.
La suite consiste simplement à initialiser les registres du Blitter avec les bonnes valeurs, il faut cependant savoir que lorsqu'il doit effectuer des décalage au pixel, le Blitter doit travailler plan par plan, ce qui revient à répéter la même opération 4 fois pour couvrir les 4 plans d'un écran en 16 couleurs. Le fonction commence par initialiser les registres qui ne changeront pas au cours de l'opération, d'abord "HINCSRC" qui indique au Blitter combien d'octets séparent deux mots consécutif d'un même plan, puis "VINCSRC" qui indique combien d'octets sont à ajouter à l'adresse de fin d'une ligne pour arriver au premier mot de la ligne suivante, dans notre cas un sprite faisant 32 pixel de large, pour passer au début de la ligne suivante il faut ajouter "LINE_SIZE-16" octets. Le registre suivant "MMASK" concerne le masque central qui sera invariablement égal à -1 (tous les bits sont significatifs). Comme pour la source il faut définir les incréments pour la destination "HINCDST" et "VINCDST", on indique ensuite dans "HSIZE" le nombre de mots à traiter, alors là vous allez me dire mais pourquoi traiter 3 mots (48 pixels) alors que notre sprite ne faite que 32 pixels ? Bonne remarque, en fait lorsque le Blitter effectue un décalage au pixel il doit lire un mot de plus pour y stocker les bits sortant du dernier mot (rappelez vous, le Blitter décale vers la droite) d'où les mots nécessaires pour 32 pixels. On défini ensuite l'opération demi-teinte (non utilisée ici). On peut enfin commencer à traiter nos trois morceaux de sprite. Voici un extrait du code :
.SetBlitRegister:
move.w #8,BLT_HINCSRC(a5) ; Source increment X
; On copie un seul plan à chaque fois donc un incrément de 8
move.w #LINE_SIZE-16,BLT_VINCSRC(a5) ; Source increment Y
move.w #$ffff,BLT_MMASK(a5) ; Middle mask source
move.w #8,BLT_HINCDST(a5) ; Dest increment X
move.w #PLAYFIELD_SIZE-16,BLT_VINCDST(a5) ; Dest increment Y
move.w #3,BLT_HSIZE(a5) ; Width size in word
move.b #2,BLT_HTOP(a5) ; Half-tone operation
.BlitBeast1:
move.w (a4),d3 ; Le décalage à faire
move.w #$ffff,d4 ; Masque par défaut
lsr.w d3,d4 ; Masque gauche
move.b d3,BLT_MODE(a5) ; Shifting
move.w d4,BLT_LMASK(a5) ; Left mask source
not.w d4 ; Inverse le masque
move.w d4,BLT_RMASK(a5) ; Right mask source
.BlitMask1:
move.l 4(a3),d5 ; Adresse source masque
move.l 2(a4),d6 ; Adresse destination
move.b #1,BLT_LOGICOP(a5) ; Logic operation
move.w #BEAST1_HEIGHT,d7 ; Hauteur du sprite
bsr BlitIt ; Lance le blitter
.BlitSprite1:
move.l (a3),d5 ; Adresse source sprite
move.l 2(a4),d6 ; Adresse destination
move.b #7,BLT_LOGICOP(a5) ; Logic operation
move.w #BEAST1_HEIGHT,d7 ; Hauteur du sprite
bsr BlitIt ; Lance le blitter
.BlitBeast2:
...etc
Il faut d'abord définir les masques gauche et droite qui serviront à éliminer les pixels
rendus inutiles suite au décalage, on alimente les registres "LMASK"
et "RMASK" avec ces valeurs,
sans oublier d'indiquer le décalage à faire dans le registre "MODE", on peut alors copier
le masque en appliquant l'opération logique ET entre la source et la destination grace
au registre "LOGICOP". Il ne reste plus qu'à définir les adresses source et destination
ainsi que le nombre de lignes à copier et on peut appeler la fonction "BlitIt".
Cette fonction va simplement répéter l'opération de copie pour les quatres plans de l'image en ajustant à chaque fois les adresses source et destination qui seront inscritent dans les registres "HSRCADDR" et "HDSTADDR", on indique le nombre de lignes à copier dans le registre "VSIZE" puis on démarre le Blitter en mode exclusif (sans partage de bus) grace au registre "CONTROL", la petite petite boucle ".RestartBlit" est une astuce pour lancer le Blitter plus rapidement, un genre de spam du flag de démarrage du Blitter. Voici le code :
BlitIt:
move.w #SCREEN_DEPTH-1,d4 ; 4 plans à faire
.NextPlan:
move.l d5,BLT_HSRCADDR(a5) ; Adresse source
move.l d6,BLT_HDSTADDR(a5) ; Adresse destination
move.w d7,BLT_VSIZE(a5) ; Hauteur en nombre de lignes
move.b #$C0,BLT_CONTROL(a5) ; Positionne le blitter en mode exclusif
addi.l #2,d5 ; Passe au plan suivant
addi.l #2,d6 ; Passe au plan suivant
.RestartBlit:
bset.b #7,BLT_CONTROL(a5) ; Démarre le blitter
nop
bne.s .RestartBlit
dbf d4,.NextPlan ; On blit le plan suivant
rts
La copie se poursuit avec le sprite en utilisant cette fois ci l'opération logique OU entre
la source et la destination, et voilà notre première tranche d'affichée. Il ne reste plus
qu'à faire la même chose avec les tranches 2 et 3 pour afficher l'ensemble du sprite.
En principe dans une animation de sprite on enchaine les séquence toujours de la même façon.
D'abord on restaure le fond d'écran avant de copier le nouveau sprite, mais dans notre cas
nous allons ruser un peu, nous allons restaurer le fond après la copie du sprite, celà
nous évitera de gérer deux adresses différentes (une de restauration et une de copie), mais
pour que l'on puisse voir le sprite nous allons attendre que le faisceau d'électron est
atteind la dernière ligne du sprite avant de l'effacer, sinon on ne le verrai même pas
apparaitre à l'écran, c'est une interruption du TimerB qui va nous positionner un flag "FlagGrass"
pour nous dire que le faisceau d'électrons est arrivé au bout de la dernière ligne du sprite.
On peut alors appeler la fonction "RestoreBeast" pour restaurer le fond d'écran, nous aurions
pu utiliser une nouvelle fois le Blitter mais on va faire ça à l'ancienne à coup de movem.l
,
il faut copier 24 octets pour chaque ligne du sprite en tenant compte des trois morceaux
d'origine. On obtient donc ceci :
RestoreBeast
lea BeastSprite,a4 ; Nos infos pour le sprite
.RestoreBeast1:
move.l 6(a4),a5 ; Buffer de sauvegarde
move.l 2(a4),a6 ; Destination
REPT (BEAST1_HEIGHT-1)
movem.l (a5)+,d1-d6
movem.l d1-d6,(a6)
adda.l #PLAYFIELD_SIZE-24,a5
adda.l #PLAYFIELD_SIZE,a6
ENDR
movem.l (a5)+,d1-d6
movem.l d1-d6,(a6)
.RestoreBeast2:
...etc
Vite on va louper le train
Et voilà c'est terminé pour l'affichage du sprite, ce n'était pas une mince affaire mais celà nous a permit de mieux découvrir les possibilités du Blitter. Dans le prochain chapitre nous verrons comment interragir à l'aide du joytsick.