Beast in the place

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.