GANDALF TUTORIAL
( Toutes les routines graphiques en assembleur bien commentées )

Préambule

Si l'assembleur permet un gain en vitesse très important par rapport au RPL, c 'est au niveau du graphisme que celui-ci est le plus évident en effet l'assembleur nous donne la possibilité de gérer au mieux l'affichage chose impossible en RPL .

Routine d'affichage d'un point à un endroit précis

Le principe:
.On veut allumer un pixel de l'écran aux coordonnées que l'on veut. Pour cela il faut tout d'abord récupérer l'adresse de l'écran ( celle qui pointe sur le pixel haut gauche de l'écran ) , puis on doit calculer le décalage a apporter à cette adresse pour quelle pointe sur le pixel désiré à 4 prés car une adresse pointe un quartet donc 4 bits correspondant à quartes pixels.
Sachant qu'une ligne ( pour un grob de la taille de l'écran ) comporte 136/4=34 quartets, la première partie du calcul consiste à faire Y*34 , bon la on est sur la ligne de notre pixel maintenant il reste à se déplacer sur cette ligne jusqu'au groupe de 4 pixels qui contient notre pixel, il suffit donc de faire X/4. L'étape suivante à pour but d'allumer le bon pixel, l'astuce pour y parvenir et de regarder les deux bits de poids faible de X car en effet si l'on a #00b alors notre pixel est le premier des quatre , si on a #01b c'est le deuxième , #10b pour le troisième et #11b pour le quatrième.

.Ce qui donne le code suivant :
on pointe sur "@écran+Y*34+X/4" puis on met a "1" le bit 0 de A comme ça "LA 1" enfin on teste le bit 0 de X si il est à "1" alors on réalise un décalage a gauche ( multiplication par 2 ) et si le bit 1 de X si il est à "1" alors on réalise deux décalages à gauche ( multiplication par 2 ) .

" % le coin haut gauche de l'écran étant le point (0,0) 

DCCP 2  X_Position 	% deux quartets pour stocker le décalage horizontal (en pixels)
DCCP 2  Y_Position	% deux quartets pour stocker le décalage vertical (en pixels) 
DCCP 5  Ecran		% cinq quartets pour stocker l'adresse de l'écran
 SAVE  			% sauve de contexte

D0=(5)X_Position 	% on initialise X_Pos à 131/2 -> #42h
LC 42  DAT0=C.B
D0=(5)Y_Position 	% on initialise X_Pos à 64/2 -> #20h
LC 20  DAT0=C.B
SCREEN 			% routine qui retourne dans  A.A l'adresse actuelle de l'écran
D0=(5)Ecran
DAT0=A.A		% on sauvegarde cette adresse

*La_boucle_principale 	% ici commence notre boucle d'affichage

D0=(5)Y_Position 	% on récupère notre position Y
C=0.A			% attention C=0.A est très important  car on va additionner notre décalage à une 
C=DAT0.B		% adresse de 5 quartets donc notre décalage doit être codé aussi sur 5 quartets
A=C.A  A+A.A		% on fait Y*2
CSL.A  C+C.A  A+C.A	%  et  (Y*16)*2 puis Y*2+(Y*16)*2=> 34*Y ( voir TUTorial  multiplication )

D0=(5)X_Position 	% on récupère notre position X
C=0.A			% attention (même remarque)
C=DAT0.B   B=C.B	% adresse de 5 quartets et on sauve pour la suite X dans le registre B
CSRB.A CSRB.A		% on fait  X/4
A+C.A			% on a  "Y*34+X/4"

D0=(5)Ecran 		% on récupère notre Ecran
C=DAT0.A		% notre adresse Ecran sur 5 quartets
A+C.A   D0=A		% voila D0 pointe sur "@écran+Y*34+X/4" donc le bon groupe de quartes pixels

C=B.B			% on récupère notre X (plus rapide qu'une nouvelle lecture en mémoire)
LA   1			% on met à "1" le bit 0
?CBIT=1.0		%  si le bit 0 est à "1" ( codage pour un bit  1 ou 3 )
-> { A+A.P }		% dans ce cas un décalage
?CBIT=1.1		%  si le bit 1 est à "1" ( codage pour un bit  2 ou 3 )
-> { A+A.P A+A.P}	% dans ce cas deux décalages

DAT0=A.P		% et voila on écrit la contenu de A.P qui allume donc le pixel BRAVO !!!

LC 001 OUT=C=IN		% si on presse DROP
?CBIT=1.6 ->
{ LOADRPL }		% alors on quitte le programme
GOTO  La_boucle_principale
@"

Bon c'est clair la première chose qu'on dit un voyant ce source c'est MAMAN !! c'est un truc de fou moi je préfères le RPL et son << {#42h #20h} PIXON >> :) bon fait un jeu en RPL, bien tu reviendra vite fait vers l'ASM si tu veux le réaliser tel qu'il est dans tes rêves...

Bon si on veut afficher une image maintenant à la place de notre pixel...

Routine d'affichage "d'un sprite" à un endroit précis

Bon, en fait y a presque rien à changer il suffit de récupérer de récupérer l'adresse ou se trouve notre sprite on peut faire ça facilement avec A=PC GOINC Sprite .... bon vous voulez que je remette tout pfff ...

" % le coin haut gauche de l'écran étant le point (0,0) 
DCCP 2  X_Position 	% deux quartets pour stocker le décalage horizontal (en pixels)
DCCP 2  Y_Position	% deux quartets pour stocker le décalage vertical (en pixels) 
DCCP 5  Ecran		% cinq quartets pour stocker l'adresse de l'écran
 SAVE  			% sauve de contexte
D0=(5)X_Position 	% on initialise X_Pos à 131/2 -> #42h
LC 42  DAT0=C.B
( ....)
D0=(5)Ecran 		% on récupère notre Ecran
C=DAT0.A		% notre adresse Ecran sur 5 quartets
A+C.A   D0=A		% voila D0 pointe sur "@écran+Y*34+X/4" donc le bon groupe de quartes pixels

%%  c'est ici la nouveauté
A=PC   GOINC Sprite	% on récupère l'adresse du sprite ( voir la fin du source ) 
A=A+C.A  D1=A		% et hop la voila pointé par D1

LC  3   D=C.P		% D nous servira de compteur , notre sprite à 4 lignes donc "compteur=nbr ligne-1"
C=B.B			% on récupère notre X (plus rapide qu'une nouvelle lecture en mémoire)
{
A=0.B			% car même si une ligne ne fait que 4 pixels après décalage elle peut faire 3 pixel de plus
A=DAT1.P		% on lit une ligne du sprite 
?CBIT=1.0		% et on décale ..
-> { A+A.B }		% pour être
?CBIT=1.1		% décalé de façon
-> { A+A.B A+A.B }	% à afficher au
DAT0=A.B		% bon endroit :)
D0+34  D1+4		% on passe à la ligne de l'écran et du sprite suivante
D-1.P UPNC }		% on affiche les quartes lignes

LC 001 OUT=C=IN		% si on presse DROP
?CBIT=1.6 ->
{ LOADRPL }		% alors on quitte le programme
GOTO  La_boucle_principale

*Sprite
$60F0F060  		%  donné d'un grob de 4*4  pour l'avoir faire  GROB 4 4 60F0F060
@"
Routine de déplacement "d'un sprite" à un endroit précis

.Bon alors la c'est très simple, il suffit de rajouter des lignes du type:
(!!!!ATTENTION!!!! de pas le faire sans avoir fini de lire ce paragraphe )

LC 040 OUT=C=IN	% si on presse DROITE
?CBIT=1.0 ->
{ D0=(5)X_Position	 % on augmente la valeur X de "1"
   C=DAT0.B C+1.B
  DAT0=C.B }		% alors déplacement à droite 

 LC 040 OUT=C=IN	% si on presse GAUCHE
?CBIT=1.2 ->
{ D0=(5)X_Position	 % on diminue la valeur X de "1" 
  C=DAT0.B C-1.B
DAT0=C.B }		% alors déplacement à gauche 

de même pour les déplacements verticaux mais pour D0=(5)Y_Position ...
!!!!!ATTENTION!!!!
déjà le programme ne fait presque rien donc il est très rapide c'est a dire que ton sprite se déplace trop vite pour être contrôlable et comme on ne teste pas pour savoir si le sprite se trouve toujours dans les limites de l'écran on risque de partir écrire n'importe ou en mémoire... pour résoudre ce problème déjà on peut mettre une temporisation du type :
LC 0F000  { C-1.A UPNC }
ensuite il faut tester si X et Y restent dans les limites, le plus simple est de faire ça ( blocage par la limite )
LC 040 OUT=C=IN	% si on presse DROITE
?CBIT=1.0 ->
{ D0=(5)X_Position 	%  si on n'est pas en limite droite
   C=DAT0.B   LA  7F	% "Largeur écran-largeur sprite" alors
   ?A!=C.B ->  {  C+1.B }	% on augmente la valeur X de "1"
DAT0=C.B }		% alors déplacement à droite 

 LC 040 OUT=C=IN	% si on presse GAUCHE
?CBIT=1.2 ->
{ D0=(5)X_Position 	%  si on n'est pas en limite gauche
   C=DAT0.B		% "0" alors
   ?A!=0.B ->  {  C-1.B }	% on diminue la valeur X de "1" 
 DAT0=C.B }		% alors déplacement à gauche 

voila et il faut procéder de la même façon pour les déplacements verticaux ...

.Sinon on peut faire comme dans HPBubble c'est à dire téléportation de l'autre coté de l'écran

LC 040 OUT=C=IN	% si on presse DROITE
?CBIT=1.0 ->
{ D0=(5)X_Position 	%  si on n'est pas en limite droite
 C=DAT0.B C+1.B LA  7F	% "Largeur écran-largeur sprite" alors
   ?A=C.B ->  {  C=0.B }% on augmente la valeur X de "1"
DAT0=C.B }		% alors déplacement à droite 

 LC 040 OUT=C=IN	% si on presse GAUCHE
?CBIT=1.2 ->
{ D0=(5)X_Position 	%  si on n'est pas en limite gauche
   C=DAT0.B   C-1.B 	% "0" alors
   ?A=0.B ->  {  LC 7F }% on diminue la valeur X de "1" 
 DAT0=C.B }		% alors déplacement à gauche 

Mais il reste toujours un problème

.Ceux qui ont réalisé les exemples précèdent ont du s'en rendre compte en effet lorsque le sprite se déplace sur l'écran il laisse une traînée derrière lui cela vient du fait que l'écran n'est pas effacé entre deux affichages du sprite.
On peut réaliser facilement l'effacage de l'écran de la manière suivante, en écrivant des "0" sur les 64 lignes de 131 pixels ... non 136 oui tout a l'heure j'ai déjà parlé de 136 pixels alors que l'écran de la HP n'en a que 131 de large et bien ce la viens du fait des quartets ( 4 pixels ) on ne peut avoir qu'un multiple de quatre pixels. Donc pour 34 quartets ça nous fait 136 pixels . Les 136-131=5 pixels sont bien la mais non affichés.
heuu... ha oui effacer l'écran bien voila à mettre juste après la temporisation pour que le moment où l’image est là soit plus long que le moment ou il est effacé.

D1=(5)Ecran
A=0.W
LC 87
{         	% on efface l'écran
DAT1=A.W  	% soit 136*64 pixels
D1+16 		% ou 136*64/4 quartets 
C=C-1.B		% et donc (136*64/4)/16 groupes de 16 quartets
UPNC }		% d'ou une boucle répétée 88  et compteur =87
INTON LOADRPL
Mais toujours problème puisque notre effacage de l'écran et visible et se traduit par un clignotement du sprite:
un problème ... encore une solution le double Buffering

Le double buffering

.Technique qui consiste à fabrique l'image à affiché dans une zone mémoire a part et juste mettre a jour l'affichage en recopiant l'image terminé ainsi ne sont affichée que des images où le sprite est présent: l'image est nette.

.Le truc c’est donc de trouver en mémoire une zone mémoire bon la solution que j’utilise est d’appeler la routine MakeSTR qui réserve en mémoire un espace donné par C.A et retourne dans D0 un pointeur vers cette zone on a donc :

 

DCCP 5 Mémoire
LC 00890	% on donne la taille ici pour un écran de 131*64  avec une marge de sécurité
GOSBVL 05B7D	% (ou bien GOSBVL MAKE$N )
A=D0		% D0=Position 
D0=(5)Mémoire	%
DAT0=A.A	% et on sauvegarde
voila on ajoute ces deux lignes au source précédant et on utilise l’adresse “Mémoire” à la place de “Ecran” pour le calcul puis une fois que l’image est finie on copie cette image à l’écran.

Avec un autre GRob comme fond d’écran ( méthode imparfaite )

.Bon là c’est déjà mieux maintenant ce que l’on voudrait ce serait que notre sprite puisse évoluer sur un décors donc on utilise soit un GRob pré calculé soit un grob réalisé précédemment par le programme.

La plus besoin d’effacer a chaque fois l’écran il suffit de procéder de la manière suivante :
on recopie le GRob décors dans notre zone mémoire puis on calcul et affiche notre sprite au bon endroit enfin on fait la copie a l’écran .... et on recommence.

Avec un autre GRob comme fond d’écran

.Le problème est que si notre sprite n’a pas une forme carré ou même simplement si il se déplace pixel par pixel il se trouve entouré d’une zone blanche qui efface le décors. La méthode pour contrer ce problème est d'utiliser une fonction d'affichage de type "ou " (OR). On prend d’une part la zone du décors qui se trouve sous le sprite d’autre part le sprite lui même on fait notre OU qui a pour effet de laisser tous les pixels allumés dans leur états allumé (d’ou conservation du fond ) et on affiche le résultat a l’écran.

Le principe du Masque (pour la transparence)

.Bon mais c’est pas top comme méthode si l’on a un sprite qui n’est pas plein , comment faire la différence entre un zone du sprite qui est transparente d’une qui ne l’ai pas et bien franchement je me l’a suis posé longtemps cette question et une nuit la révélation ( je blague pas la ) il me faut deux sprites évidement un qui représente le personnage par exemple et un autre (masque) qui représente le pourtour du sprite donc qui nous donne les zones de transparences donc notre séquence d’affichage devient:
copie du décors en Mémoire , utilisation du sprite masque pour effacer dans le décors la forme interne du personnage, puis on affiche le personnage et on balance sur l’écran .

Là, on a un bon résultat avec un sprite qui peut se déplacer sur un décors....

Reste a régler un petit détaille la Vsync

.Cette valeur ne peut être que lue et est codée sur 6 bits en #128h . Elle indique le numéro de la ligne en cour d’affichage et est décroissante . Le rafraîchissement complet de l’écran se fait en 1/64 ieme de seconde.
Donc pour avoir l’image la plus nette possible il faut attendre que la Vbl passe à 0 ( rafraîchissement de la ligne 64 en cours puis des que la Vbl est différente de zéro un réalise le transfert Mémoire vers écran.
Cela peut se coder ainsi :

 

D0=00128 
*VBL	% on attend que vbl=0
A=DAT0.B
ABIT=0.6 ABIT=0.7
?A!=0.B -> VBL
*VBL1		% des que vbl != 0 
A=DAT0.B
ABIT=0.6 ABIT=0.7
?A=0.B -> VBL1
		% on peut passer a l’affichage

Gandalf_le_mage (Florent), le samedi 3 février 2001
email : gandalf_le_mage@yahoo.com
site d’origine : Gandalf-home.fr.st