Intégration du miroir horizontal au moteurLe besoin le plus courant dans le jeu vidéo est la symétrie d'axe vertical. Comment allons-nous modifier notre moteur d'animation pour la gérer? D'abord, il faut convenir d'un sens d'affichage dans la structure de nos objets : struct s_animation animation defw positionx defw positiony defw destination defb ; poids fort de la destination hardware reverse defb ; sens d'affichage endstructOn a déjà le code d'inversion depuis [ L'article sur les miroirs et rotations ]. Modifions notre exécution pour gérer la variable "reverse" supplémentaire : ; IY = objet animation animate_execute_step push iy : pop hl 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 ; connexion de la bank contenant les données ld a,(hl) : inc hl : ld xl,a : ld xh,a ; backup du compteur dans XL et XH ld e,(hl) : inc hl : ld d,(hl) : inc hl : push hl : ex hl,de ; HL=sprite data source ld d,(iy+s_animation.destination) ; on n'initialise plus E ! ld b,hi(hsp_conversion) ; poids fort pour la table de conversion ld a,(iy+s_animation.reverse) : dec a : jp z,.unpack_hsp_reverseAssez peu de changements, on n'initialise plus le poids faible de la destination (registre E) car l'affichage en inverse ne part pas de zéro. Enfin, un dernier test avant la copie va nous diriger soit vers la copie classique, soit vers la copie inversée. Si on voulait gérer plus de transformations, il serait judicieux de placer directement un pointeur de saut à cet endroit et l'intégrer dans l'objet. Que manque-t'il pour compléter le moteur? Ah oui! La routine d'affichage inverse, avec une lecture des delta modifiée! ;*************************************************************** .unpack_hsp_reverse ;*************************************************************** ld e,15 ; on part de la FIN de la ligne destination .unpack_hsp_reverse_loop repeat 8,x ld a,(hl) : inc l : ld (de),a : dec e ld c,a : ld a,(bc) : ld (de),a if x<7 : dec e : else ; on décrémente SAUF sur le dernier pixel écrit ld a,e : add 31 : ld e,a ; on revient au point de départ, +16 pour être à la fin de la ligne suivante :) endif rend ; la dernière comparaison change, on ne teste plus le retour à zéro mais le dépassement de 255 jr nc,.unpack_hsp_reverse_loop dec l : inc hl inc d ; next sprite dec xl ; E still at 15 jr nz,.unpack_hsp_reverse_loop ; DE = next sprite + 15La routine de copie termine un peu différemment (adresse du sprite suivant+15) mais cela ne va pas changer nos calculs car on récupère l'indice des sprites à modifier avec le poids fort. Par contre, qui dit copie inversée en symétrie verticale, dit une inversion des deltaX lus, tout simplement une soustraction au lieu d'une addition. C'est la seule différence. ld a,d : sub #40 : sub xh : ld c,xh add a : add a : add a : ld xh,#60 : ld xl,a ld hl,(iy+s_animation.positionx) : ld de,(iy+s_animation.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 .modifyXreverse 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 xor a : sbc hl,bc ; modifier le X avec l'inverse du modificateur ld (ix+0),hl add ix,de inc iy exa : dec a : jr nz,.modifyXreverse pop ix,hl,af .modifyYreverse 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,.modifyYreverse retAvec ce premier jet, Esteban s'affiche bien dans les deux sens... ...mais il est centré sur les coordonnées (avant DeltaX) du premier sprite de notre objet 
On pourrait centrer automatiquement le sprite mais ce n'est pas obligatoirement ce qu'on souhaite. Il ne faut de toutes façons que modifier la coordonnée de départ, sur toutes les étapes attention! On va modifier notremacro de définition des delta pour prendre en compte le centre de gravité sur le premier delta. macro animate_push_delta dekx,deky if animateStepCount==0 animateStepDeltaX{animateStepCount}={dekx}+animateGravityCenter else animateStepDeltaX{animateStepCount}={dekx} endif animateStepDeltaY{animateStepCount}={deky} animateStepCount+=1 if animateStepCount>animateStepCountTarget : assert 0==1,"Trop d'etapes!" : endif mendNotre Esteban s'étale sur 3 sprites en largeur (taille des pixels mode 1) donc le milieu se situe à 1.5 x 32 - 16 1.5 x 32 pour être au milieu des 3 sprites en largeur et 16 en moins car le début d'un sprite se situe sur la gauche et que c'est la taille d'un sprite en résolution mode 2. On ajoute la définition de notre centre de gravité avant de définir le meta-objet.
| animateGravityCenter=1.5*32-16 |
Et voici notre Esteban centré 
Gestion d'une liste d'objets Au plus simple, plutôt que d'appeler notre routine d'exécution plusieurs fois de suite avec différents objets, on va créer une routine qui peut exécuter une liste d'objets. confine 34 animate_object_list defs 34,0 ; 16 objets + 1 terminateur à zéroanimate_execute_list ld hl,animate_object_list .reloop ld e,(hl) : inc l : ld d,(hl) : inc l : ld a,d : or e : ret z push hl : ld iy,de : ex hl,de : call animate_execute_step.skipParam pop hl jr .reloop ; IY = objet animation animate_execute_step push iy : pop hl ; déjà fait quand on lit depuis la liste! .skipParam ld e,(hl) : inc hl : ld d,(hl) : dec hl ; current step in 'animation' ex hl,de : ldi : ldi ; overwrite current step in 'animation' ... On fait deux objets esteban (avec la définition du tag reverse) struct s_animation,estebanObject,1,estebanNage,300,70,#40,0 ; reverse=0 struct s_animation,estebanObjectbis,1,estebanNage,300,100,#44,1 ; reverse=1On référence nos deux objets dans la liste ld hl,estebanObject : ld (animate_object_list),hl ld hl,estebanObjectbis : ld (animate_object_list+2),hlEt c'est tout! Il n'y a plus qu'à faire un seul appel au moteur pour afficher nos meta-sprites call animate_execute_listAjouter, enlever un objet de la liste Est-ce que notre gestion de liste ne serait pas un peu rudimentaire? Je propose de faire quelques fonctions pour ajouter/enlever des objets. D'abord l'ajout, le plus simple, on cherche un emplacement libre dans la liste et on écrit notre pointeur. ; DE=adresse de l'objet animate_add_object ld hl,animate_object_list : ld b,16 ; max 16 objets de 1 .searchSlot ld a,(hl) : inc l : or (hl) : jr z,.addObject : inc l : djnz .searchSlot : ret ; impossible d'ajouter .addObject ld (hl),d : dec l : ld (hl),e ; objet ajouté retLa suppression d'un objet est plus touchy, il faut trouver notre objet et aussi trouver le dernier de la liste. Si notre objet est le dernier de la liste alors on efface l'entrée, terminé. Si notre objet n'est pas le dernier de la liste, il faut copier le dernier à l'emplacement de celui qu'on veut effacer et ensuite effacer le dernier de la liste. ; DE=adresse de l'objet animate_remove_object ld hl,animate_object_list : ld b,16 ; max 16 objets de 1 .searchSlot ld a,(hl) : cp e : jr z,.lowMatch : inc l : inc l : djnz .searchSlot : ret ; pas trouvé .lowMatch inc l : ld a,(hl) : cp d : jr z,.highMatch : inc l : djnz .searchSlot : ret ; pas trouvé .highMatch ld de,hl ; sauvegarde dans DE de l'adresse de l'objet à supprimer dans la liste+1 .findLastObject inc l : ld a,(hl) : inc l : or (hl) : jr nz,.findLastObject ; on ne compte plus, on doit obligatoirement trouver 0 ; HL=adresse du dernier objet vide+1, nous on veut le dernier objet tout court dec l : dec l ; on revient sur l'objet d'avant qui contient quelque chose ld a,l : cp e : jr z,.razEntry ldd : ld a,(hl) : ld (de),a ; on copie le dernier objet de la liste sur celui qu'on enlève ld (hl),0 : inc l : ld (hl),0 : ret ; et on l'efface de la fin de la liste .razEntry ld (hl),0 : dec l : ld (hl),0 : ret ; notre objet était le dernier de la liste, on l'effaceEt voilà, notre moteur est paré pour la flexibilité :) On peut tester que tout fonctionne rapidement. ld de,estebanObject : call animate_add_object ; ajout du premier ld de,estebanObjectbis : call animate_add_object ; ajout du deuxièmeld de,estebanObjectbis : call animate_remove_object ; suppression du dernier ld de,estebanObjectbis : call animate_add_object ; on le rajoute ld de,estebanObject : call animate_remove_object ; suppression du premier avec un suivant ld de,estebanObject : call animate_add_object ; et on le rajoute, l'ordre sera inversé dans la liste Vous pouvez télécharger le [ zip d'exemple ] dans lequel on s'amuse à activer/désactiver les objets les uns à la suite des autres. Toutes les fonctions d'animation du moteur sont regroupées dans un source newAnimate.asm. 
Roudoudou CPCrulez[Content Management System] v8.732-desktop Page créée en 799 millisecondes et consultée 9 foisL'Amstrad CPC est une machine 8 bits à base d'un Z80 à 4MHz. Le premier de la gamme fut le CPC 464 en 1984, équipé d'un lecteur de cassettes intégré il se plaçait en concurrent du Commodore C64 beaucoup plus compliqué à utiliser et plus cher. Ce fut un réel succès et sorti cette même années le CPC 664 équipé d'un lecteur de disquettes trois pouces intégré. Sa vie fut de courte durée puisqu'en 1985 il fut remplacé par le CPC 6128 qui était plus compact, plus soigné et surtout qui avait 128Ko de RAM au lieu de 64Ko. |
|