Revenir au plan du site

Une routine dédiée par mouvement

Plus simple et permettant de gérer tous les cas sans passer sa vie à faire des comparaisons, on va dédier une fonction par mouvement et l'appeler à chaque fois. On change de mouvement, on change la routine de gestion. Le code principal se raccourci :
halt : halt : halt : halt
; lire le joystick
ld a,#49 : ld bc,#F40E : out (c),c : ld bc,#F6C0 : out (c),c : out (c),0
ld bc,#F792 : out (c),c : dec b : out (c),a : ld b,#F4 : in a,(c)
ld bc,#F782 : out (c),c : dec b : out (c),0
ld hl,(Conrad.routine) : jp (hl) ; saut inconditionnel vers la routine de gestion du mouvement en cours

Il faut maintenant définir toutes les combinaisons possibles en fonction de chaque mouvement.

Sur le diagramme ci-dessous, j'ai mis en évidence toutes les relations d'un mouvement à l'autre, on voit que certains sont plus complexes que d'autres mais la majorité sont en fait (pour cette planche) des mouvements avec peu de connexions. Le jeu original ajoute les chutes, monter ou descendre et c'est beaucoup plus de travail.


Évolution des objets pour gérer plus de données

Avec toutes ces animations, on va exploser largement la mémoire centrale. Nous allons donc modifier nos objets pour qu'ils indiquent la bank/ROM à connecter pour chaque sprite. Et il sera possible plus tard de gérer les données plus finement, c'est à dire optimiser leur placement en mémoire pour bien remplir chaque bank/ROM.
startingindex 0
macro animate_single zelabel,nbanim
curspr=0
repeat {nbanim}-1
defw $+5 ; nos objets font maintenant 5 octets, on passe au suivant
defb {bank}{zelabel}
defw {zelabel}+6*128*curspr
curspr+=1
rend
defw $ ; boucle infinie sur la dernière étape
defb {bank}{zelabel}
defw {zelabel}+6*128*curspr
mend

Idem pour les données lues à l'envers, taille de l'objet 5 et le DEFB de la bank...
startingindex 0
macro animate_single_reverse zelabel,nbanim
curspr=nbanim-1
repeat {nbanim}-1
defw $+5
defb {bank}{zelabel}
defw {zelabel}+6*128*curspr
curspr-=1
rend
defw $ ; infinite loop on last step
defb {bank}{zelabel}
defw {zelabel}+6*128*curspr
mend

Le moteur doit gérer cet octet supplémentaire pour connecter la ROM supérieure (ou de la RAM, vous adaptez comme vous voulez le code).
; A=sprite destination
; HL=animation
animate_execute_step
;.reloop
ld e,(hl) : inc l : ld d,(hl) : dec l ;: ex hl,de ; current step in 'animation'
ex hl,de : ldi : ldi ; overwrite current step in 'animation'
ld b,#DF : ld c,(hl) : inc hl : out (c),c ; ROM connectée
ld d,a : ld e,0 : ld a,(hl) : inc hl : ld h,(hl) : ld l,a ; HL = sprite data source / DE = sprite destination
ld b,hi(hsp_conversion)
ld xl,6 ; meta-sprite de 6 sprites hards
.unpack_hsp
repeat 8
ld a,(hl) : inc l : ld (de),a : inc e : ld c,a : ld a,(bc) : ld (de),a : inc e ; 1 byte => 2 pixels
rend
jr nz,.unpack_hsp
dec l : inc hl
inc d ; next sprite
dec xl
jr nz,.unpack_hsp
ret
; DE = next sprite

Nouvelles macros pour gérer d'autres types d'animations



Pour la marche, nous aurons besoin d'une macro qui s'occupe de reboucler une animation.
startingindex 0
macro animate_loop zelabel,nbanim
startAnim=$
curspr=0
repeat {nbanim}-1
defw $+4 ; nos objets font 4 octets, on passe au suivant
defw {zelabel}+6*128*curspr
curspr+=1
rend
defw startAnim ; retour au début
defw {zelabel}+6*128*curspr
mend

Pour le tir, c'est le type "ping-pong" qui va lire les sprites dans un sens, puis dans l'autre et s'arrêter.
macro animate_single_pong zelabel,nbanim
assert {nbanim}>1,'ping pong seulement avec 2 animations ou plus'
curspr=0
repeat {nbanim}
defw $+5
defb {bank}{zelabel}
defw {zelabel}+6*128*curspr
curspr+=1
rend
curspr-=2
repeat {nbanim}-2
defw $+5
defb {bank}{zelabel}
defw {zelabel}+6*128*curspr
curspr-=1
rend
defw $ ; on s'arrête après etre revenu au point de départ
defb {bank}{zelabel}
defw {zelabel}+6*128*curspr
mend

New ultimate mega animation engine power boost turbo 2 plus ultra

Est-ce que vous êtes chauds?

On va réécrire légèrement la gestion de Conrad pour factoriser un peu nos actions, car de façon générale (je n'ai pas mis la marche), on exécute un mouvement puis on gère à nouveau le clavier. Ainsi, chacun de nos mouvements se contentera d'appeler cette routine et au besoin, de gérer quelques touches.

L'initialisation a besoin d'un nombre d'étapes (dans A) et de la routine de gestion à appeler (dans HL) une fois le mouvement terminé. Ensuite c'est la routine qui décompte les "pas" qui se charge de décrémenter son compteur et quand il arrive à zéro, on bascule sur la routine de gestion.
; fonction générique pour exécuter un mouvement bloquant de n étapes, suivi d'une routine de gestion
.executeMouvementInit ld (.executeMouvement+1),a : ld (.executeMouvementNext+1),hl : ld hl,.executeMouvement : ld (.routine),hl
.executeMouvement ld a,#12 : dec a : ld (.executeMouvement+1),a : jp nz,.execute
.executeMouvementNext ld hl,#1234 : ld (.routine),hl : ld a,b : jp (hl)
.execute ld a,#40 : ld hl,.animation : call animate_execute_step : jp mainLoop
.animation defw conradDroite ; animation par défaut à droite
.routine defw .attendreDroite ; defaut

Les mouvements vont alors se définir de façon un peu répétitives, il faut connecter les actions en accord avec ce qu'on veut. Le code est répétitif, j'ai envie de dire, ça tombe bien, si vous êtes courageux, cette partie pourrait même s'automatiser dans un moteur de jeu :)
;*********
Conrad
;*********
.attendreDroite
ld hl,conradDroite : ld (.animation),hl
ld hl,.attendreDroiteManage : ld (.routine),hl
.attendreDroiteManage
bit 1,a : jp z,.assisDroite
bit 2,a : jp z,.retournerGauche
;bit 3,a : jp z,.marcherDroite
bit 4,a : jp z,.degainerDeboutDroite
jp .execute

.attendreGauche
ld hl,conradGauche : ld (.animation),hl
ld hl,.attendreGaucheManage : ld (.routine),hl
.attendreGaucheManage
bit 1,a : jp z,.assisGauche
;bit 2,a : jp z,.marcherGauche
bit 3,a : jp z,.retournerDroite
bit 4,a : jp z,.degainerDeboutGauche
jp .execute

;-----------------------------------------------------------------
.assisDroite
ld hl,conradAssisDroite : ld (.animation),hl ; animation
ld a,5 : ld hl,.assisDroiteManage : jp .executeMouvementInit
.assisDroiteManage
bit 1,a : jp z,.execute ; si on continue d'appuyer sur BAS, on reste assis
ld hl,conradRemonteDroite : ld (.animation),hl ; animation
ld a,5 : ld hl,.attendreDroite : jp .executeMouvementInit

.assisGauche
ld hl,conradAssisGauche : ld (.animation),hl ; animation
ld a,5 : ld hl,.assisGaucheManage : jp .executeMouvementInit
.assisGaucheManage
bit 1,a : jp z,.execute ; si on continue d'appuyer sur BAS, on reste assis
ld hl,conradRemonteGauche : ld (.animation),hl ; animation
ld a,5 : ld hl,.attendreGauche : jp .executeMouvementInit

;-----------------------------------------------------------------
.retournerGauche
ld hl,conradRetournerGauche : ld (.animation),hl ; animation
ld a,5 : ld hl,.attendreGauche : jp .executeMouvementInit

.retournerDroite
ld hl,conradRetournerDroite : ld (.animation),hl ; animation
ld a,5 : ld hl,.attendreDroite : jp .executeMouvementInit

;-----------------------------------------------------------------
.degainerDeboutDroite
ld hl,conradDegaineDroite : ld (.animation),hl ; animation
ld a,8 : ld hl,.attendreDroiteGun : jp .executeMouvementInit
.attendreDroiteGun
bit 1,a : jp z,.assisDroiteGun
bit 2,a : jp z,.retournerGunGauche
bit 4,a : jp z,.rengainerDeboutDroite
bit 5,a : jp z,.tirerDroite
jp .execute
.rengainerDeboutDroite
ld hl,conradRengaineDroite : ld (.animation),hl ; animation
ld a,8 : ld hl,.attendreDroite : jp .executeMouvementInit

.degainerDeboutGauche
ld hl,conradDegaineGauche : ld (.animation),hl ; animation
ld a,8 : ld hl,.attendreGaucheGun : jp .executeMouvementInit
.attendreGaucheGun
bit 1,a : jp z,.assisGaucheGun
bit 3,a : jp z,.retournerGunDroite
bit 4,a : jp z,.rengainerDeboutGauche
bit 5,a : jp z,.tirerGauche
jp .execute
.rengainerDeboutGauche
ld hl,conradRengaineGauche : ld (.animation),hl ; animation
ld a,8 : ld hl,.attendreGauche : jp .executeMouvementInit

.retournerGunGauche
ld hl,conradRetournerGunGauche : ld (.animation),hl ; animation
ld a,10 : ld hl,.attendreGaucheGun : jp .executeMouvementInit
.retournerGunDroite
ld hl,conradRetournerGunDroite : ld (.animation),hl ; animation
ld a,10 : ld hl,.attendreDroiteGun : jp .executeMouvementInit

.tirerDroite
ld hl,conradTirDroite : ld (.animation),hl ; animation
ld a,19 : ld hl,.attendreDroiteGun : jp .executeMouvementInit
.tirerGauche
ld hl,conradTirGauche : ld (.animation),hl ; animation
ld a,19 : ld hl,.attendreGaucheGun : jp .executeMouvementInit

;-----------------------------------------------------------------
.assisDroiteGun
ld hl,conradAssisGunDroite : ld (.animation),hl ; animation
ld a,6 : ld hl,.attendreDroiteAssisGun : jp .executeMouvementInit
.attendreDroiteAssisGun
bit 5,a : jp z,.tirerAssisDroite
bit 2,a : jp z,.retournerGunAssisGauche
bit 1,a : jp z,.execute ; tant qu'on appuie bas, on reste bas
ld hl,conradRemonteGunDroite : ld (.animation),hl ; animation
ld a,6 : ld hl,.attendreDroiteGun : jp .executeMouvementInit
.assisGaucheGun
ld hl,conradAssisGunGauche : ld (.animation),hl ; animation
ld a,6 : ld hl,.attendreGaucheAssisGun : jp .executeMouvementInit
.attendreGaucheAssisGun
bit 5,a : jp z,.tirerAssisGauche
bit 3,a : jp z,.retournerGunAssisDroite
bit 1,a : jp z,.execute ; tant qu'on appuie bas, on reste bas
ld hl,conradRemonteGunGauche : ld (.animation),hl ; animation
ld a,6 : ld hl,.attendreGaucheGun : jp .executeMouvementInit

.retournerGunAssisGauche
ld hl,conradRetournerGunAssisGauche : ld (.animation),hl ; animation
ld a,8 : ld hl,.attendreGaucheAssisGun : jp .executeMouvementInit
.retournerGunAssisDroite
ld hl,conradRetournerGunAssisDroite : ld (.animation),hl ; animation
ld a,8 : ld hl,.attendreDroiteAssisGun : jp .executeMouvementInit

.tirerAssisDroite
ld hl,conradTirAssisDroite : ld (.animation),hl ; animation
ld a,13 : ld hl,.attendreDroiteAssisGun : jp .executeMouvementInit
.tirerAssisGauche
ld hl,conradTirAssisGauche : ld (.animation),hl ; animation
ld a,13 : ld hl,.attendreGaucheAssisGun : jp .executeMouvementInit



Je vous ai fait un gros ZIP à télécharger [ ICI ] il contient les sources et binaires nécessaires...




On se retrouve dans [ l'article suivant ] pour ajouter la marche et déplacer Conrad ;)