Revenir au plan du site

Positionnement individuel des sprites sur un meta-objet

Jusqu'à présent, nous avons utilisé (pour Conrad) des meta-sprites composés de sprites unitaires alignés et organisés systématiquement de la même façon. C'est beaucoup plus pratique, tant que le personnage ne bouge pas, on n'a pas à faire bouger nos sprites. La gestion des séquences est simplifiée et leur création aussi.

Sauf qu'on n'a pas toujours autant de sprites qu'on voudrait à disposition et il y a par ailleurs un gâchis évident des sprites. Pour illustrer ce qu'on peut faire comme modifications à notre système de gestion, voici un sprite issu d'un jeu en cours de création. Comme Conrad, il occupe 6 sprites hard, cette fois organisés à l'horizontale.



C'est ainsi que Titan m'a envoyé la planche, avec Esteban bien centré, prenant ses aises avec les resources de la machine. Maintenant voici une version où je décale certains blocs multiples de 16 pixels à la manière d'un taquin.

- Je décale tous les sprites de 4 pixels vers le bas (opération faite sur l'ensemble de la planche)
- Je décale les 4 sprites les plus à gauche de 5 pixels vers le bas.
- Enfin, je décale le sprite tout en haut à gauche de deux pixels vers la gauche.

Et voici le résultat :


Extrait de la planche (non complète ici) annotée avec les modifications.


En résumé et DANS MON CAS, j'ai 3 transformations à appliquer sur mon meta-sprite de référence :
- Déplacer les deux sprites de gauche vers le bas.
- Déplacer le sprite du milieu vers le bas (même si c'est souvent pareil que les deux autres, j'ai UN cas particulier à gérer).
- Enfin, le sprite en haut à gauche à décaler vers la gauche.

Jusqu'à présent (avec Conrad), on avait une routine spécifique qui ne s'occupait que des X, la revoici :
; HL=position X du côté gauche
.SetX
ld de,32 ; 16 pixels mode 1 == 32 pixels mode 2 en précision d'affichage
ld (#6000),hl : ld (#6010),hl : ld (#6020),hl : add hl,de
ld (#6008),hl : ld (#6018),hl : ld (#6028),hl
ret

Comme on le voit, lorsqu'on bouge un meta-sprite, il faut de toutes façons ré-écrire chacune des coordonnées, calculer les positions des sprites adjacents, etc.

Autant alors définir chacun de nos sprites de façon relative au premier qui sera notre référence. Dans le cas d'Esteban, nous avons par défaut :

- premier sprite (0/0)
- deuxième sprite (0/16)
- sprite du milieu (32/16)
- sprite des pieds (64/16)

Ce qui, en relatif, deviendrait 0/0, 0/16, 32/0, 32/0. Pourquoi passer sur du relatif vous me demanderez? Hé bien pour éviter de recharger à chaque fois la référence qui peut se situer n'importe où sur l'écran, pas comme notre 0/0 de cet exemple!



Voici une routine générique qui fonctionne dans tous les cas. Elle utilise les informations de placement avec d'abord l'ensemble des décalages X et ensuite ceux en Y.
; IX=info du premier sprite dans l'ASIC
; IY=info de placement des sprites
; HL=x DE=y du premier sprite
; A=nombre de sprites
SetMetaSprite
; **** d'abord boucler sur les X ************
push af,de,ix : ld de,8
.modifyX
exa
ld c,(iy+0)
ld a,c : add a : sbc a : ld b,a ; faire une valeur 16 bits signées à partir de C
add hl,bc ; modifier le X
ld (ix+0),hl
add ix,de
inc iy
exa : dec a : jr nz,.modifyX
pop ix,hl,af
.modifyY
exa
ld c,(iy+0)
ld a,c : add a : sbc a : ld b,a ; faire une valeur 16 bits signées à partir de C
add hl,bc ; modifier le X
ld (ix+2),hl
add ix,de
inc iy
exa : dec a : jr nz,.modifyY
ret

Maintenant, comment pourrait-on intégrer nos données de placement aux routines des articles précédents?

On va modifier notre structure qui contient la définition d'un mouvement et comme il n'est plus possible d'automatiser la création d'un mouvement, on va se créer une macro pour pousser petit à petit chaque étape. Il faudra ajouter les modificateurs de coordonnées après chaque étape à la main (bouuuuuuh, en attendant mieux dans l'article suivant).

startingindex 0
macro animate_push_step addr,meta,zebank,spraddr
; si l'adresse n'est pas nulle, on la force, sinon on calcule l'adresse de la cellule suivante
if {addr} : defw {addr} : else : defw $+6+2*{meta} : endif
defb #80|{zebank},{meta}
defw {spraddr}
mend

Revenons à notre Esteban, ses 4 sprites, et l'animation complète qui reboucle avec 11 étapes!


Pour rappel, nos coordonnées relatives donnent par défaut 0/0, 0/16, 32/0, 32/0, donc en écrivant d'abord les X, ensuite les Y...
defb 0,0,32,32,0,16,0,0

estebanNage
animate_push_step 0,4,{bank}hsp_estebanNage,hsp_estebanNage
; 1ère étape, les 3 premiers sprites sont décalés de 1 pixel vers le bas
defb 0,0,32,32,-1,16,0,1
...
; la suite dans le code source plus bas ;)

C'est pas marrant, c'est une petite gymnastique cérébrale mais il suffit de suivre les flèches vertes ^_^

Et si on affichait tout ça maintenant? Jetez un oeil à la routine animate_execute_step légèrement modifiée. On sauvegarde la valeur courante des données d'animation pour appeler SetMetaSprite dans la foulée.

Voici le source final, vous pouvez télécharger le [ ZIP ] avec les binaires, le snap, ...
startingindex 0
macro animate_push_step addr,meta,zebank,spraddr
; si l'adresse n'est pas nulle, on la force, sinon on calcule l'adresse de la cellule suivante
if {addr} : defw {addr} : else : defw $+6+2*{meta} : endif
defb #80|{zebank},{meta}
defw {spraddr}
mend

macro animate_push_coord dekx,deky
defb {dekx},{deky}
mend
i
buildsna
SNASET CPC_TYPE,4 ; 6128+
bankset 0
org #38
ei : ret
org #100
run #100

;*** RMR2 tags ***
ASICOFF equ 0 : ROM0000 equ 0 : ROM4000 equ %01000 : ROM8000 equ %10000 : ASICON equ %11000
ROM0 equ 0 : ROM1 equ 1 : ROM2 equ 2 : ROM3 equ 3 : ROM4 equ 4 : ROM5 equ 5 : ROM6 equ 6 : ROM7 equ 7
macro RMR2 tags : ld a,{tags}+%10100000 : ld b,#7F : out (c),a : mend

ld sp,#100 ; la pile avant le code
ld bc,#7F80+%0100 : out (c),c ; MODE 0 + ROM_UP

UnlockAsic
ld bc,#BCFF : out (c),c : out (c),0
ld hl,%1001000011101010
.loop
out (c),c
ld a,h : rlca : ld h,l : ld l,a
srl c:res 3,c : and #88 : or c
ld c,a
cp #4D
jr nz,.loop
ld a,#CD
out (c),a : out (c),a
RMR2 ASICON

ld hl,#6400 : .toBlack ld (hl),#333 : inc l : jr nz,.toBlack
ld hl,palette
ld de,#6422
ld bc,64
ldir ; on envoie toutes les couleurs d'un coup, fond, border, sprites
ei

; activer 4 sprites en ratio mode 1
ld a,%1001 : ld (#6004),a : ld (#600C),a : ld (#6014),a : ld (#601C),a

;***************************************************************
reloop
;***************************************************************
ld h,5
.wait
ld b,#F5 : .waitNoVBL in a,(c) : rra : jr c,.waitNoVBL
ld b,#F5 : .waitVBL in a,(c) : rra : jr nc,.waitVBL
dec h : jr nz,.wait

ld hl,animation : ld a,#40 : call animate_execute_step

jr reloop


; A=sprite destination
; HL=animation
animate_execute_step
;.reloop
ld e,(hl) : inc hl : ld d,(hl) : dec hl ; 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 : ld d,a ; Dx=adresse du sprite hard
ld a,(hl) : inc hl : ld xl,a : ld xh,a ; backup du compteur dans XH
ld a,(hl) : inc hl : ld e,(hl) : inc hl : push hl : ld h,e : ld l,a : ld e,0
; HL = sprite data source / DE = sprite destination
ld b,hi(hsp_conversion)
.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
; DE = next sprite
ld a,d : sub #40 : sub xh : ld c,xh
add a : add a : add a : ld xh,#60 : ld xl,a
ld hl,(positionx) : ld de,(positiony)
pop iy : ld a,c
; IX=info du premier sprite dans l'ASIC
; IY=info de placement des sprites
; HL=x DE=y du premier sprite
; A=nombre de sprites
SetMetaSprite
; **** d'abord boucler sur les X ************
push af,de,ix : ld de,8
.modifyX
exa
ld c,(iy+0)
ld a,c : add a : sbc a : ld b,a ; faire une valeur 16 bits signées à partir de C
add hl,bc ; modifier le X
ld (ix+0),hl
add ix,de
inc iy
exa : dec a : jr nz,.modifyX
pop ix,hl,af
.modifyY
exa
ld c,(iy+0)
ld a,c : add a : sbc a : ld b,a ; faire une valeur 16 bits signées à partir de C
add hl,bc ; modifier le X
ld (ix+2),hl
add ix,de
inc iy
exa : dec a : jr nz,.modifyY
ret

align 256
hsp_conversion
sprdata=0
repeat 256
defb sprdata>>4 : sprdata+=1
rend

palette defw #000,#040,#060,#4B2,#0D0,#6D4,#65D,#76E,#BB9,#96F,#C6D,#BF6,#FE9,#DCF,#FFF

align 256 : hsp_estebanNage incbin 'hsp_esteban.bin'

animation defw estebanNage
positionx defw 260
positiony defw 90

align 2
estebanNage
animate_push_step 0,4,{bank}hsp_estebanNage,hsp_estebanNage
; 1ère étape, les 3 premiers sprites sont décalés de 1 pixel vers le bas
defb 0,0,32,32,-1,16,0,1
animate_push_step 0,4,{bank}hsp_estebanNage,hsp_estebanNage+(4*128)*1
; étapes suivantes, même topo, décalage des 3 premiers et réajustement inverse sur le dernier
defb 0,0,32,32,-1,16,0,1
animate_push_step 0,4,{bank}hsp_estebanNage,hsp_estebanNage+(4*128)*2
defb 0,0,32,32,-2,16,0,2
animate_push_step 0,4,{bank}hsp_estebanNage,hsp_estebanNage+(4*128)*3
defb 0,0,32,32,-4,16,0,4
animate_push_step 0,4,{bank}hsp_estebanNage,hsp_estebanNage+(4*128)*4
defb 0,0,32,32,-4,16,0,4
animate_push_step 0,4,{bank}hsp_estebanNage,hsp_estebanNage+(4*128)*5
defb 0,0,32,32,-5,16,0,5
; 7è étape, 3 premiers sprites ajustés de 5 vers le bas PLUS la tête de 2 vers la gauche (à compenser sur le 2è sprite)
animate_push_step 0,4,{bank}hsp_estebanNage,hsp_estebanNage+(4*128)*6
defb 2,-2,32,32,-5,16,0,5
animate_push_step 0,4,{bank}hsp_estebanNage,hsp_estebanNage+(4*128)*7
defb 3,-3,32,32,-5,16,0,5
animate_push_step 0,4,{bank}hsp_estebanNage,hsp_estebanNage+(4*128)*8
defb 2,-2,32,32,-5,16,0,5
animate_push_step 0,4,{bank}hsp_estebanNage,hsp_estebanNage+(4*128)*9
defb 2,-2,32,32,-4,16,0,4
; 11è étape, deux premiers sprites décalés de 2 vers le bas, 3è sprite de 3 vers le bas, compensation sur le dernier
animate_push_step estebanNage,4,{bank}hsp_estebanNage,hsp_estebanNage+(4*128)*10
defb 0,0,32,32,-2,16,-1,3

Et voici le résultat en mode debug avec l'émulateur CPCEC patché pour le debug :)


Suggestion de présentation :p



Rendez-vous dans [ l'article suivant ] pour améliorer notre jeu de routines