lecture des articles lus dans le magasine ST Mag. Ce qui suit est facile et basique, toute
contribution (utile) est la bienvenue.
Ok, c'est parti pour un petit Starfield !
Le but : Un programme facilement lisible et modifiable qui doit tourner à 50 fps compilé.
Des étoiles en déplacement vertical ayant des vitesses différentes.
Chaque étoile sortant de l'écran sera remplacée par une nouvelle générée
aléatoirement.
Le principe va être d'effacer les étoiles affichées précédemment puis de calculer leurs
nouvelles ordonnées en ajoutant la vitesse à y pour enfin les afficher.
En pseudo code ça donne:
- Code: Tout sélectionner
REPEAT
VSYNC ! rafraichissement ecran
FOR star&=1 TO maxstar&
erase(star&) ! a coder
NEXT star&
'
FOR star&=1 TO maxstar&
ADD y.star&,yspeed& ! a
display(star&) ! coder
NEXT star&
UNTIL space_pressed
On voit donc qu'il faut stocker les coordonnées de chaque étoile pour l'effacer à son
ancienne position avant de l'afficher à la nouvelle position.
On se rend compte que du coup il faudra définir la position initiale de chaque étoile
avant que la boucle principale ne tourne.
La manière la plus évidente/simple en GFA de gérer ça sera d'utiliser un tableau : nous
definiront la constante maxstar& qui stockera le nombre d'étoiles que nous déplaceront.
D'après le pseudo code il nous faut un tableau à deux dimensions pour stocker les
coordonnées et la vitesse de chaque étoile.
- Code: Tout sélectionner
maxstar&=50
DIM star&(maxstar&,2)
On stockera donc :
star&(blah&,0)=x&
star&(blah&,1)=y&
star&(blah&,2)=speed&
Où blah& est le numéro de l'étoile dont nous nous occupons.
Remarquez le "&" : il indique au GFA que nous voulons travailler avec des mots/word (-
32768;...;32767). Premièrement parce que nos valeurs (x& par exemple) pourront être plus
grandes que ce qu'un octet/byte ne peut contenir ("|"), deuxièmement parce que le ST
travaille plus vite ( à fortiori en GFA ) avec des mots qu'avec des longs mots ("%") ou
pire des nombres à virgule.
Nous y reviendront mais utiliser ce tableau à deux dimensions bien qu'étant suffisant pour
afficher quelques points, se révèle peu performant. ( En tout cas il y a une autre façon
facile de faire beaucoup plus efficace.)
Pour l'affichage / effaçage des points nous n'utiliserons *pas* PLOT, simplement parce que
nous voulons un résultat propre, rapide et fluide.
Il nous faut avoir au moins une idée rudimentaire de l'organisation des graphismes sur le
ST.
On va travailler en basse résolution cad 320*200 pixels avec 16 couleurs.
Les graphismes sont sockés par bloc de 16 pixels sur 8 octets : 320 pixels sont donc
stockés dans 160 octets , * 200 lignes = 32000 octets pour la taille d'un écran donc.
Pour obtenir les 16 couleurs, les pixels sont codés en 4 plans, on a donc chaque mot qui
stocke les 16 pixels pour chaque plan.
- Code: Tout sélectionner
--------------------------------------------------------
! numb ! bin ! plan 1 ! plan 2 ! plan 3 ! plan 4 !
! 00 ! 0000 ! non ! non | non ! non !
! 01 ! 0001 ! oui ! non ! non ! non !
! 02 ! 0010 ! non ! oui ! non ! non !
! 03 ! 0011 ! oui ! oui ! non ! non !
! 04 ! 0100 ! non ! non ! oui ! non !
! 05 ! 0101 ! oui ! non ! oui ! non !
! 06 ! 0110 ! non ! oui ! oui ! non !
! 07 ! 0111 ! oui ! oui ! oui ! non !
! 08 ! 1000 ! non ! non ! non ! oui !
! 09 ! 1001 ! oui ! non ! non ! oui !
! 10 ! 1010 ! non ! oui ! non ! oui !
! 11 ! 1011 ! oui ! oui ! non ! oui !
! 12 ! 1100 ! non ! non ! oui ! oui !
! 13 ! 1101 ! oui ! non ! oui ! oui !
! 14 ! 1110 ! non ! oui ! oui ! oui !
! 15 ! 1111 ! oui ! oui ! oui ! oui !
--------------------------------------------------------
on comprend que la couleur 0 n'utilise aucun plan, la couleur 1 utilise le plan 1, la
couleur 2 le plan 2, la couleur 3 les plans 1 et 2, etc ...
Disons qu'on veut afficher le 3ème pixel en partant de la gauche avec la couleur 13 il
nous faudra écrire :
&X0010000000000000 à address%
&X0000000000000000 à address%+2 ( 2nd mot )
&X0010000000000000 à address%+4 ( 3ème mot )
&X0010000000000000 à address%+6 ( 4ème mot )
"&X" indique au GFA que nous lui fournissons des valeurs binaires, pour des valeurs
héxadécimales on indiquera "&H" )
Pour afficher les étoiles avec la couleur 1 nous n'aurons qu'à écrire un seul mot dans la
mémoire écran. Comme il est fort probable que plusieurs étoiles se trouvent sur le même
bloc de 16 pixels nous utiliseront l'opérande OR pour "plotter" chaque étoile afin de ne
pas en effacer une qu'on aurait déjà affichée sur le même mot.
Pour gagner du temps machine on va précalculer les 16 positions d'un pixel dans un autre
tableau.
- Code: Tout sélectionner
DIM pxlz&(15)
pxlz&(0)=-32768 !&X1000000000000000=&H8000=-32768
mystar&=&X0100000000000000
pxlz&(1)=mystar&
FOR i&=2 TO 15 !
mystar&=SHR(mystar&,1) ! decalage d'un pixel sur la droite
pxlz&(i&)=mystar&
NEXT i&
Pour écrire ces mots à l'écran on utilisera la commande CARD:
- Code: Tout sélectionner
CARD{screen_address%}=pxlz&(blah&) OR CARD{screen_address%}
Effacer sera encore plus simple, il suffira d'écrire 0 .
On gagnera du temps machine si au lieu de travailler avec des coordonnées on utilise
directement les adresses.
Convertir x en adresse : x.address&=SHL(SHR(x&,4),3)
=> Encore un tableau pour stocker ces adresses:
- Code: Tout sélectionner
DIM xadr.ar&(320)
for i&=0 to 319
xadr.ar&(i&)=SHL(SHR(i&,4),3)
next i&
x offset : xofst&=x& AND 15
Convertir y en addresse : y.address&=y&*160
Vitesse en addresse : y.speed&=speed&*160
On aura donc:
- Code: Tout sélectionner
CARD{screen_address%+x.address&+y.address&}=pxlz&(xofst&) OR CARD{screen_address%
+x.address&+y.address&}
Si on écrit à l'écran pendant que le ST rafraichit ce dernier on aura des bugs
d'affichage, pour eviter cela nous allons recourir à l'éternelle technique du double
buffer:
Pendant que le ST affiche l'écran (ecran physique), nous ecrirons dans un second buffer
identique en taille=32000 octets (écran logique), ensuite nouos mettrons à jour l'adresse
vidéo après avoir SWAPpé (=échangé) les adresses écran logique et physique et utiliserons
VSYNC pour attendre la fin du balayage et passage à la prochaine VBL.
- Code: Tout sélectionner
scrbuf%=MALLOC(64000+256) ! Alloue la memoire pour 2 ecrans
phy%=AND(ADD(scrbuf%,255),&HFFFFFF00) ! adresse ecran physique
log%=ADD(phy%,32000) ! adresse ecran logique
Sur STf l'adresse écran doit se situer sur une adresse multiple de 256 ce qui explique le
+256 sur la taille mémoire allouée et le fait que l'on mette l'octet de phy% à zéro (
AND&HFFFFFF00).
Notre routine, avant d'afficher les nouvelles étoiles doit les effacer, mais il faut
maintenant prendre en compte le double buffering .
En effet, il nous faudra connaitre la position de chaque étoile que nous voudrons effacer
non pas une mais deux VBL plus tôt :
1ère VBL: affiche buffer 1, on écrit dans le buffer 2 l'étoile est à y.
2ème VBL: affiche buffer 2, on écrit dans le buffer 1 l'étoile est à y+yspeed
3ème VBL: affiche buffer 1, on écrit dans le buffer 2, l'étoile est à y+yspeed+yspeed
Comme la routine peut avoir créé une nouvelle étoile dans le cas où la précédente serait
sortie de l'écran nous ne pouvons pas nous contenter de soustraire 2*yspeed, au lieu de
cela nous utiliserons (encore) 3 tableaux et utiliseront SWAP pour les intervertir, comme
pour les adresses écran.
- Code: Tout sélectionner
DIM current.star&(maxstar&,3)
DIM middle.star&(maxstar&,3)
DIM old.star&(maxstar&,3)
Assez de blabla basico théorique, passons au code complet.
Quelques mots à propose de PROCEDURE inits:
Le programme avant de tourner vous demandera combien d'étoiles vous souhaitez qu'il
affiche, le maximum en 1 VBL (programme compilé) est d'environ 75.
On utilisera le mode superviseur afin d'écrire directement dans certains registres mémoire
du ST.
Le buffer alloué avec MALLOC sera effacé (rempli de 0), evitant que quelque donnée
résidant en mémoire ne se situe dans ledit bloc et vienne parasiter nos données.
Le pus facile/rapide en GFA est d'utiliser RC_COPY.
Certains pourront remarquer qu'il n'y a pas d'appel à la fonction XBIOS(5) (qui sert à
mettre en place les adresses écran logique et physique) dans la boucle principale mais
écriture dans les registres mémoire correspondants.
Il n'est pas du tout indispensable de procéder de cette façon mais comme mes programmes
sont principalement destinés au STE, j'ai pris l'habitude de coder de cette manière.
Appuyer sur Alternate pendant que le programme tourne changera la couleur de fond en bleu,
la partie de l'écran en bleu représentant le temps machine encore disponible avant la fin
de la VBL.
- Code: Tout sélectionner
RESERVE 10000
'
@inits
REPEAT
VSYNC
CARD{&HFFFF8240}=0 ! fond noir
' clear oldstars
FOR i&=0 TO maxstar&
xadr&=old.star&(i&,0)
yadr&=old.star&(i&,2)
CARD{ADD(log%,ADD(xadr&,yadr&))}=0 ! efface les anciennes etoiles
NEXT i&
'
' calc new coords and display star
FOR i&=0 TO maxstar&
yadr&=middle.star&(i&,2)
yspeed&=middle.star&(i&,3)
newy%=ADD(yadr&,yspeed&)
IF newy%>31999 ! hors de l'ecran
x&=RAND(319) ! nouvelle etoile randomisee
xadr&=xadr.ar&(x&)
xofst&=x& AND 15 ! offset
current.star&(i&,0)=xadr&
current.star&(i&,1)=xofst&
current.star&(i&,2)=0 ! y nouvelle etoile = 0
current.star&(i&,3)=(RAND(8)+1)*160 ! y speed
'
dest%=ADD(log%,xadr&) ! y=0 donc address=x address
CARD{dest%}=CARD{dest%} OR pxlz&(xofst&)
ELSE
xadr&=middle.star&(i&,0)
xofst&=middle.star&(i&,1)
'
current.star&(i&,0)=xadr&
current.star&(i&,1)=xofst&
current.star&(i&,2)=newy%
current.star&(i&,3)=yspeed&
'
dest%=ADD(log%,ADD(xadr&,newy%))
CARD{dest%}=CARD{dest%} OR pxlz&(xofst&)
ENDIF
NEXT i&
tuch&=BYTE{&HFFFFFC02}
IF tuch&=&H38 ! appui sur Alt
CARD{&HFFFF8240}=&HF ! CPU restant
ENDIF
SWAP middle.star&(),old.star&() ! swap stars data arrays old=middle
SWAP current.star&(),middle.star&() ! middle=current
SWAP phy%,log%
BYTE{&HFFFF8201}=SHR(phy%,16)
BYTE{&HFFFF8203}=SHR(phy%,8)
'
UNTIL tuch&=&H39 ! Spacebar pressed
'
@fin
'
PROCEDURE inits
CLS
INPUT "Max Stars",maxstar& ! combien d'etoiles ? screen
DIM pxlz&(15) ! tableau des 16 positions d'un pixel
DIM xadr.ar&(320) ! tableau conversion X en adresse
DIM current.star&(maxstar&,3) ! tableau stockant X adresse/ X ofset/ Y
adresse / Y speed pour chaque etoile
DIM middle.star&(maxstar&,3) ! meme chose
DIM old.star&(maxstar&,3) ! idem <= on swappera ces 3 tableaux
pour avoir les anciennes positions des etoiles
scrbuf%=MALLOC(64000+256+32) ! 1 buffer pour les 2 ecrans +
sauvegarde de la palette
phy%=AND(ADD(scrbuf%,255),&HFFFFFF00) ! phy% sur un octet nul
log%=ADD(phy%,32000) ! log% 32000 octets plus loin
palette%=ADD(starzbuf%,64000) ! palette% apres log%
super%=GEMDOS(32,L:0) ! supervisor mode
'
HIDEM
BYTE{&HFFFFFC02}=&H12 ! no mouse
rez|=XBIOS(4) ! resolution sauvee
xb2%=XBIOS(2) ! adresse ecran sauvee
'
for i&=0 to 319
xadr.ar&(i&)=SHL(SHR(i&,4),3) ! conversion X en adresse
next i&
BMOVE &HFFFF8240,palette%,32 ! ancienne palette sauvee
BYTE{&HFFFF8260}=0 ! basse resolution
VSYNC
~XBIOS(5,L:log%,L:phy%,-1) ! met en place les ecrans
VSYNC
RC_COPY phy%,0,0,320,400 TO phy%,0,0,0 ! effaceles buffers phy% et log%
VSYNC
SETCOLOR 1,&H777 ! etoiles blanches, oh my god !
FOR i&=0 TO maxstar&
x&=RAND(319)
xadr&=SHL(SHR(x&,4),3) ! adresse
xofst&=x& AND 15 ! offset
middle.star&(i&,0)=xadr&
middle.star&(i&,1)=xofst&
middle.star&(i&,2)=RAND(199)*160 ! y adr
middle.star&(i&,3)=(RAND(8)+1)*160 ! y speed
'
old.star&(i&,0)=0
old.star&(i&,1)=0
old.star&(i&,2)=0
old.star&(i&,3)=0
NEXT i&
pxlz&(0)=-32768 ! -32768=&H8000=&X1000000000000000
mystar&=&X0100000000000000
pxlz&(1)=mystar&
FOR i&=2 TO 15
mystar&=SHR(mystar&,1) ! 1 pixel decale a droite
pxlz&(i&)=mystar&
NEXT i&
RETURN
'
PROCEDURE fin
SHOWM
BYTE{&HFFFFFC02}=&H8 ! rend la souris ! mouse alive
~MFREE(scrbuf%)
BMOVE palette%,&HFFFF8240,32 ! remet ancienne palette
~XBIOS(5,L:xb2%,L:xb2%,rez|)
~GEMDOS(32,L:super%) ! user mode
EDIT
RETURN
J'ai écrit cet article après des échanges constructifs avec Thomas, l'auteur de plusieurs
jeux en GFA (anarcho ride / frogs / randomazer ...).
Ma première routine utilisait CARD pour lire/écrire dans les buffers au lieu d'utiliser
les tableaux.
On a pas mal discuté sur la rapidité en temps machine de ces tableaux et Thomas a proposé
une routine plus rapide que celle que j'avais écrite en utilisant des tableaux à une
dimension. Après tests il s'avère que les tableaux à deux dimensions prenaient énormément
plus de temps que d'utiliser plusieurs tableaux simples. Bien que cela lui convienne
parfaitement, j'avaonçais que ça faisait trop de tableaux/variables à gérer pour moi. Il
faut un tableau pour x adr, x ofst, y, yspeed et tout cela multiplié par 3 (
current/mid/old).
L'intégriste du CARD que je suis se mit en tête d'optimiser la routine originale tirant
parti de chaque technique. Le code qui suit affichera 150 étoiles chaque VBL.
Le but étant d'éviter les calculs (additions) au maximum.
Au lieu d'utiliser plusieurs tableaux, les données seront stockées dans un unique buffer:
- Code: Tout sélectionner
INPUT "Max Stars",maxstar&
DIM pxlz&(15)
DIM xadr.ar&(320)
scrbuf%=MALLOC(64000+256+maxstar&*8*3+32)
phy%=AND(ADD(scrbuf%,255),&HFFFFFF00)
log%=ADD(phy%,32000)
starzbuf%=ADD(log%,32000) !MALLOC(maxstar&*8*3) (1 mot X / 1 mot offset / 1
mot Y / 1 mot speed)*3 buffers
palette%=ADD(starzbuf%,maxstar&*8*3) !MALLOC(32)
Un seul buffer donc, et des pointeurs pour chaque "buffer dans le buffer".
La quantité de mémoire nécessaire est alors maxstar&*8*3: on a besoin de stocker current /
mid / old positions des étoiles (=>*3) et un bloc de data pour une étoile fait 8 octets
(4*2).
Les pointeurs:
- Code: Tout sélectionner
starz%=starzbuf%
mid.starz%=ADD(starzbuf%,maxstar&*8)
old.starz%=ADD(starzbuf%,maxstar&*8*2)
dernier point, au lieu d'utiliser une boucle FOR/NEXT j'ai opté pour REPEAT/UNTIL,
définissant une variable qui pointe dans le buffer dans lequel je veux taper et lui
ajoutant 8 à chaque étoile jusqu'à ce qu'elle pointe à la fin du buffer.
- Code: Tout sélectionner
RESERVE 10000
'
@inits
REPEAT
VSYNC
CARD{&HFFFF8240}=0 ! black backgroud
' clear oldstars
old.starz.pointer%=old.starz%
old.starz.pointer.max%=ADD(old.starz%,maxstar.lg&)
REPEAT
xadr&=CARD{old.starz.pointer%}
yadr&=CARD{ADD(old.starz.pointer%,4)}
CARD{ADD(log%,ADD(xadr&,yadr&))}=0 ! clear previous starz
ADD old.starz.pointer%,8
UNTIL old.starz.pointer%=old.starz.pointer.max%
'
' calc new coords and display star
starz.pointer%=starz%
starz.pointer.max%=ADD(starz%,maxstar.lg&)
mid.starz.pointer%=mid.starz%
REPEAT
yadr&=CARD{ADD(mid.starz.pointer%,4)}
yspeed&=CARD{ADD(mid.starz.pointer%,6)}
newy%=ADD(yadr&,yspeed&)
IF newy%>31999 ! outside screen
x&=RAND(319) ! new random star
xadr&=xadr.ar&(x&)
xofst&=x& AND 15 ! offset
CARD{starz.pointer%}=xadr&
CARD{ADD(starz.pointer%,2)}=xofst&
CARD{ADD(starz.pointer%,4)}=0
CARD{ADD(starz.pointer%,6)}=MUL(ADD(RAND(8),1),160) !MUL(ADD(x& AND 7,1),160) !
(RAND(8)+1)*160
'
dest%=ADD(log%,xadr&)
CARD{dest%}=CARD{dest%} OR pxlz&(xofst&)
ADD starz.pointer%,8
ADD mid.starz.pointer%,8
ELSE
xadr&=CARD{mid.starz.pointer%}
xofst&=CARD{ADD(mid.starz.pointer%,2)}
'
CARD{starz.pointer%}=xadr&
CARD{ADD(starz.pointer%,2)}=xofst&
CARD{ADD(starz.pointer%,4)}=newy%
CARD{ADD(starz.pointer%,6)}=yspeed&
'
dest%=ADD(log%,ADD(xadr&,newy%))
CARD{dest%}=CARD{dest%} OR pxlz&(xofst&)
ADD starz.pointer%,8
ADD mid.starz.pointer%,8
ENDIF
UNTIL starz.pointer%=starz.pointer.max%
tuch&=BYTE{&HFFFFFC02}
IF tuch&=&H38 ! Alt pressed
CARD{&HFFFF8240}=&HF ! show CPU left
ENDIF
SWAP mid.starz%,old.starz%
SWAP starz%,mid.starz%
SWAP phy%,log%
BYTE{&HFFFF8201}=SHR(phy%,16)
BYTE{&HFFFF8203}=SHR(phy%,8)
'
UNTIL tuch&=&H39 ! Spacebar pressed
'
@fin
'
PROCEDURE inits
CLS
INPUT "Max Stars",maxstar&
DIM pxlz&(15)
DIM xadr.ar&(320)
scrbuf%=MALLOC(64000+256+maxstar&*8*3+32)
phy%=AND(ADD(scrbuf%,255),&HFFFFFF00)
log%=ADD(phy%,32000)
starzbuf%=ADD(log%,32000) !MALLOC(maxstar&*8*3) ! (1 word X /
1 word offset / 1 word Y / 1 word speed)*3 buffers
palette%=ADD(starzbuf%,maxstar&*8*3) !MALLOC(32)
super%=GEMDOS(32,L:0) ! supervisor mode
'
HIDEM
BYTE{&HFFFFFC02}=&H12 ! plus de souris ! no mouse
rez|=XBIOS(4)
xb2%=XBIOS(2)
'
for i&=0 to 319
xadr.ar&(i&)=SHL(SHR(i&,4),3)
next i&
BMOVE &HFFFF8240,palette%,32 ! saves oldpal
BYTE{&HFFFF8260}=0 ! low REZ
VSYNC
~XBIOS(5,L:log%,L:phy%,-1) ! set screens
VSYNC
RC_COPY phy%,0,0,320,400 TO phy%,0,0,0 ! clears both phy% and log% buffers
VSYNC
SETCOLOR 1,&H777
mid.pointer&=maxstar&*8
old.pointer&=maxstar&*8*2
FOR i|=0 TO maxstar&-1
x&=RAND(319)
xadr&=SHL(SHR(x&,4),3) ! address
xofst&=x& AND 15 ! offset
CARD{starzbuf%+mid.pointer&}=xadr&
CARD{starzbuf%+mid.pointer&+2}=xofst&
CARD{starzbuf%+mid.pointer&+4}=RAND(199)*160 ! y adr
CARD{starzbuf%+mid.pointer&+6}=(RAND(8)+1)*160 ! y speed
'
CARD{starzbuf%+old.pointer&}=0
CARD{starzbuf%+old.pointer&+2}=0
CARD{starzbuf%+old.pointer&+4}=0
CARD{starzbuf%+old.pointer&+6}=0
ADD mid.pointer&,8
ADD old.pointer&,8
NEXT i|
maxstar.lg&=SHL(maxstar&-1,3) ! X8
starz%=starzbuf%
mid.starz%=ADD(starzbuf%,maxstar&*8)
old.starz%=ADD(starzbuf%,maxstar&*8*2)
pxlz&(0)=-32768 !&X1000000000000000
mystar&=&X0100000000000000
pxlz&(1)=mystar&
FOR i&=2 TO 15 !2 TO 30 STEP 2
mystar&=SHR(mystar&,1) ! 1 pixel shift to the right
pxlz&(i&)=mystar&
NEXT i&
RETURN
'
PROCEDURE fin
SHOWM
BYTE{&HFFFFFC02}=&H8 ! rend la souris ! mouse alive
~MFREE(scrbuf%)
~MFREE(starzbuf%)
BMOVE palette%,&HFFFF8240,32 ! restore old palette
~XBIOS(5,L:xb2%,L:xb2%,rez|)
~GEMDOS(32,L:super%) ! user mode
EDIT
RETURN