Les Sprites I) Qu'est ce qu'un sprite ? Un sprite est un graphique généralement de petite taille qui bouge sur un écran, par exemple une balle, un Mario, un Sonic, ou plus généralement tout graphique susceptible de se déplacer sur un écran. Le principe d'affichage d'un sprite n'est pas très compliqué. En effet, il faut tout d'abord savoir comment est codé un graphique en mémoire. Il est codé ligne par ligne, un octet codant 8 pixels (8 bits en noir et blanc => 8 pixels). Pour l'écran, il faut donc : 131/8 = 16.375 soit 17 octets par ligne (on arrondit à cause du chip graphique 8 bits). On utilise donc les constantes (en quartets) : 17*2 = #34d = #22h quartets. On passe donc d'une ligne à une autre en ajoutant 34 quartets à un pointeur. II) Comment afficher un sprite ? Pour les sprites, le principe est le même sauf que les dimensions ne sont pas forcement identiques (en général la dimension du sprite est plus petite). En sachant qu'un pixel est codé par un bit, soit n la largeur en pixel, d'une ligne du sprite. Pour afficher un sprite, on va commencer par pointer dans écran puis écrire les n bits (au bit près) de la première ligne de ce sprite, passer à la ligne suivante, ... et ainsi de suite jusqu'à la dernière ligne du sprite. On considérera que les coordonnées sont dans un bon intervalle, et par conséquent que l'affichage du sprite ne débordera pas de la zone mémoire de écran, que ce soit en haut, en bas, ou pour des raisons esthétiques, à droite ou à gauche (on aurait dans ce cas un bout de sprite à droite et un autre à gauche). Pour afficher notre sprite, nous allons donc créer une routine qui prendra en entrée les coordonnées du sprite dans l'écran. Ainsi on pourra calculer l'offset pour savoir ou afficher le sprite (de la même façon qu'on calcule l'offset pour savoir où afficher un point). Selon les besoins du programme, l'adresse de l'écran sera soit donnée en entrée, soit fixée par la routine. Dans notre cas, D0 pointe sur l'écran. % X : Aa % Y : Ca % D0: @écran *AFF.SPRITE AD0EX % Aa = @ecran et D0 = Y C=C+C A % Ca = 2*X % C+C A % Ne sert qu'en niveau de gris, pour avoir une multiplication par #44h A+C A % Aa = @ecran + 2*X CSL A % Ca = 2*X * # 10h = X * #20h C=C+A A % Ca = X * #20h + @ecran + 2*X = @ecran + X * #22h AD0EX % Aa = Y B=A P % Sauvegarde pour utilisation ultérieure ASRB A % Division par 4 (car 1 quartet = 4 pixels) ASRB A % C=C+A A % Ca = @ecran + X * #22h + Y div 4 D0=C Ainsi D0 pointe sur le quartet où afficher le sprite. Il ne nous reste plus qu'à savoir combien de fois il va falloir décaler le sprite pour obtenir un affichage au bit près. En effet le sprite peut sur un même quartet être affiche a 4 abscisses différentes (1 quartet pour 4 pixels). Il nous suffit de garder les 2 bits de poids faible (ce qui reviens à faire un modulo 4) sur la sauvegarde que l'on a effectuée sur Bp. LC 3 % #3h = #0011b (les deux bits de poids faible) C=C&B P % Cp contient le nombre de décalages a effectuer P=C 0 % On le place dans Cs ... C=P 15 % ... grâce au registre P. On a ainsi dans Cs un compteur qui nous permettra de savoir de combien de bits il faudra décaler notre ligne de sprite pour qu'elle soit affichée à la bonne abscisse. On en déduit donc que le nombre de décalages possible sur une ligne est 4, mais comme il y en a un qui correspond a un décalage nul, il y a au maximum qu'un dessalage de 3 bits. Il faudra donc penser à prendre un champ assez grand (mais pas trop sinon on perd du temps machine). Par exemple : avec un champ B, on peut afficher des grob de 8-3 = 5 pixels de largeur. Avec le champ X, on peut afficher des sprites de 12-3 = 9 pixels de largeur. Avec le champ W, on peut afficher des grob de 64-3 = 61 pixels de largeur. Si vous devez afficher des sprites de plus de 61 pixels il vous faudra faire les décalages sur les 61 premiers pixels, puis de décaler les autres, sur toute la largeur. Il est intéressant de noter que l'utilisation d'un champ WP peut se révéler astucieux pour traiter des sprites de tailles variables. Dans notre cas, notre sprite fait 9 pixels de largeur, il nous faudra au moins 9+3 = 12 bits, idéal pour un champ X (12 bits). Il ne nous reste plus qu'à obtenir l'adresse des donnes du sprite, qu'on peut faire avec un GOSUB, grâce auquel on récupère l'adresse : GOSUB DATAS.SPRITE /SPRITE % A vous de mettre le dessin que vous voulez dans la variable SPRITE *DATAS.SPRITE C=RSTK D1=C % D1 pointe sur le début du sprite P= 4 % Compteur = 16 - nombre de lignes a recopier Remarque : on utilise le champ P comme compteur de ligne. C'est le plus intéressant, mais on est limité à 16 lignes de sprite. Si vous voulez afficher un sprite de plus de 16 lignes, vous devrez utiliser un autre champ, tel qu'un champ B, sur un des registres de calculs (A, B, C ou D) non utilisé par la boucle d'affichage suivante : *COPY.LINE A=0 X % Pour ne pas avoir un reste de registre (inutile dans notre cas) A=DAT1 X % On lit une ligne du sprite D1=D1+ 4 % le sprite fait 4 quartets de largueur, et non 3, a cause du chip, et % du codage en mémoire des grobs (cf. §I) A=C S % On charge le compteur A=A-1 S % et on décale ... GOC DEC.OK A=A+A X % 1 fois (si nécessaire) A=A-1 S % GOC DEC.OK A=A+A X % 2 fois (si nécessaire) A=A-1 S % GOC DEC.OK A=A+A X % 3 fois (si nécessaire) *DEC.OK `AFFICHE D0=D0+ 16 % On passe à la ligne suivante D0=D0+ 16 % 16 + 16 + 2 = 34 D0=D0+ 2 % P=P+1 % On incrémente le compteur (il revient a 0 avec la carry) GONC COPY.LINE % et on boucle ... RTN @ La mise à zéro du champ au début du chargement d'une ligne n'est utile que lorsque votre sprite n'occupe pas la totalité du registre. Par exemple, pour afficher un sprite de 10 pixels de largeur, il faut 10+3=13 bits, donc 4 quartets (dans le pire des cas, mais le pire doit toujours être prévu), que l'on devra mettre à zéro pour que l'on affiche pas n'importe quoi. Cette routine pourra donc être capable (avec quelques modifications) d'afficher des sprites d'une taille différente que celle choisie en exemple (admirez mon don pour le dessin) : Dessin/Grob du bonhomme III) Les différentes façons d'afficher un sprite en noir et blanc Il existe plusieurs façons d'afficher un sprite à l'écran : La première est très simple à réaliser, elle est l'équivalent d'un REPL en RPL. Cette technique est à employer s'il n'y a pas de fond, car il serait en partie effacé lors de l'affichage du sprite, comme le montre cet exemple : Sans fond : Avec fond : Dans la variable `AFFICHE' on met la chaîne suivante : DAT0=A X % On écrit la ligne du sprite dans l'écran @ La seconde technique, pallie l'insuffisance de la première en réalisant un ou logique entre la ligne du sprite et le fond de écran. Là encore, on se retrouve face à un inconvénient : Sans fond : Avec fond : Dans la variable `AFFICHE' on met la chaîne suivante : C=DAT0 X % On lit le fond de l'écran A=A!C X % On réalise le ou logique DAT0=A X % On écrit la ligne du sprite dans l'écran @ La troisième, appelé affichage en XOR, permet de ne pas forcement avoir à effacer l'écran pour afficher les sprites ! En effet, il suffit d'afficher deux fois le sprite aux mêmes coordonnées pour que le sprite soit effacé, s'il n'y a pas de fond bien entendu. Sans fond : Avec fond : Dans la variable `AFFICHE' on met la chaîne suivante : C=DAT0 X % On lit le fond de l'écran B=C X % C=C!A X % B=B&C X % C=C-B X % On réalise le ou exclusif (XOR) DAT0=C X % On écrit la ligne du sprite dans l'écran @ Le top du top, c'est bien sur le transparent : c'est une technique qui permet de différencier, dans le sprite à afficher les points blancs, des points transparents. Cette technique consiste à afficher en OR l'ombre du sprite, pour respecter le fond. Puis d'inverser seulement les pixels que l'on veut en blanc. Il faut donc prévoir en plus du sprite, un masque, correspondant à l'ombre. Mais je vous conseille aussi de faire un petit programme qui vous permettra à partir d'un sprite, de donner un grob composé du masque et des points à inverser. Exemple : Sans fond : Avec fond : Etant donne que l'on utilise désormais un masque, il va falloir modifier la routine à partir du label COPY.LINE : *COPY.LINE % Ne pas oublier de mettre Aa et Ca à zéro si nécessaire C=DAT1 X % On lit une ligne du masque B=C X % On sauvegarde le masque dans Bx D1=D1+ 3 % On passe au sprite A=DAT1 X % On lit une ligne du sprite D1=D1+ 4 % le sprite fait 4 quartets de largueur (cf. §I) A=C S % On charge le compteur A=A-1 S % et on décale ... GOC DEC.OK A=A+A X % 1 fois (si nécessaire) B=B+B X % A=A-1 S % GOC DEC.OK A=A+A X % 2 fois (si nécessaire) B=B+B X % A=A-1 S % GOC DEC.OK A=A+A X % 3 fois (si nécessaire) B=B+B X % *DEC.OK `AFFICHE D0=D0+ 16 % On passe à la ligne suivante D0=D0+ 16 % 16 + 16 + 2 = 34 D0=D0+ 2 % P=P+1 % On incrémente le compteur (il revient a 0 avec la carry) GONC COPY.LINE % et on boucle ... RTN @ Dans la variable `AFFICHE' on met la chaîne suivante : C=DAT0 X % On lit le fond de l'écran C=C!B X % On réalise le ou logique C=C-A X % On met en blanc les pixels que l'on veut DAT0=C X % On écrit la ligne du sprite dans l'écran @ IV) Comment afficher un sprite en niveaux de gris ? Pour l'affichage d'un écran en niveau de gris, je vous recommande vivement de vous référer au premier numéro d'HpGrâal. Si vous ne l'avez pas courrez l'acheter ! Lorsque l'on gère des images en niveaux de gris, nous sommes en fait en face de deux écrans noir et blanc. Il existe deux types de codage d'écrans en niveaux de gris : le double hauteur (le plus commun) et le double largeur (moins répandu, mais parfois redoutablement efficace). Dans le premier cas cela nous donne un GROB 131*128, dans le second un GROB 272*64. Pourquoi 272 ? Parce que 272 = 136 * 2. L'affichage plus ou moins longtemps d'un des deux écrans donne naissance aux niveaux de gris. Dans le cas d'un affichage en double hauteur, le principe reste le même : on s'occupe donc d'abord de la première partie du sprite, que l'on recopiera dans la première partie du grob, idem pour la seconde partie. L'affichage se fait donc en deux temps. On passe d'une partie à l'autre du grob, en incrémentant un pointeur de #880h quartets. Dans le cas d'un affichage en double largeur, le principe reste le même : on s'occupe donc d'abord de la première partie du sprite, que l'on recopiera dans la première partie du grob, idem pour la seconde partie. Mais cette technique a pour avantage de pouvoir passer d'un écran à un autre par une ridicule incrémentation de pointeur de 34 quartets. L'avantage de l'affichage double largueur, c'est qu'on peut faire l'affichage en une fois ! On charge ainsi les deux parties du sprite, que l'on décale, puis que l'on affiche. Comme on travaille en double largueur, il faudra penser à modifier le calcul de l'offset du premier quartet où afficher le sprite, en enlevant le % qui mettait en commentaire l'instruction : C+C A. Il faut aussi penser à modifier la routine à partir du label COPY.LINE : *COPY.LINE A=DAT1 X % On lit une ligne du sprite (1er plan) D1=D1+ 3 % C=DAT1 X % On lit une ligne du sprite (2eme plan) B=C X % On la met dans Bx D1=D1+ 4 % le sprite fait 4 quartets de largueur, et non 3 (cf. §I) A=C S % On charge le compteur A=A-1 S % et on décale ... GOC DEC.OK A=A+A X % 1 fois (si nécessaire) B=B+B X % A=A-1 S % GOC DEC.OK A=A+A X % 2 fois (si nécessaire) B=B+B X % A=A-1 S % GOC DEC.OK A=A+A X % 3 fois (si nécessaire) B=B+B X % *DEC.OK `AFFICHE D0=D0+ 16 % On passe a la ligne suivante D0=D0+ 16 % 16 + 16 + 2 = 34 D0=D0+ 2 % donc au second plan `AFFICHE2 D0=D0+ 16 % On passe à la ligne suivante D0=D0+ 16 % 16 + 16 + 2 = 34 D0=D0+ 2 % donc on revient au premier plan P=P+1 % On incrémente le compteur (il revient a 0 avec la carry) GONC COPY.LINE % et on boucle ... RTN @ Il y a comme pour l'affichage d'un grob en noir et blanc, 4 manières d'afficher un sprite en niveau de gris (Replace, Or, Xor, Transparent). Pour afficher en Replace, dans la variable `AFFICHE' on met la chaîne suivante : DAT0=A X % On écrit la ligne du sprite dans l'écran @ et dans `AFFICHE2' : C=B X % On récupère la ligne du 2nd plan DAT0=C X % On écrit la ligne du sprite dans l'écran @ Pour afficher en Or, dans la variable `AFFICHE' on met la chaîne suivante : C=DAT0 X % On lit le fond de l'écran A=A!C X % On réalise le ou logique (1er plan) DAT0=A X % On écrit la ligne du sprite dans l'écran @ et dans `AFFICHE2' : C=DAT0 X % On lit le fond de l'écran C=C!B X % On réalise le ou logique (2nd plan) DAT0=C X % On écrit la ligne du sprite dans l'écran @ Pour afficher en Xor, dans la variable `AFFICHE' on met la chaîne suivante : C=DAT0 X % On lit le fond de l'écran D=C X % C=C!A X % D=D&C X % C=C-D X % On réalise le ou exclusif (XOR) DAT0=C X % On écrit la ligne du sprite dans l'écran @ et dans `AFFICHE2' : C=DAT0 X % On lit le fond de l'écran D=C X % C=C!B X % D=D&C X % C=C-D X % On réalise le ou exclusif (XOR) DAT0=C X % On écrit la ligne du sprite dans l'écran @ Et enfin, pour afficher un sprite en niveau de gris, en transparent, mêmes remarques que pour le noir et blanc, un petit programme de conversion s'impose. Etant donné que l'on utilise désormais un masque, et deux lignes de sprite, il va encore falloir modifier la routine à partir du label COPY.LINE : *COPY.LINE A=DAT1 X % On lit une ligne du masque D1=D1+ 3 % C=DAT1 X % On lit une ligne du sprite (1er plan) B=C X % On la met dans Bx D1=D1+ 3 % C=DAT1 X % On lit une ligne du sprite (2nd plan) D=C X % On la met dans Dx D1=D1+ 4 % le sprite fait 4 quartets de largueur, et non 3 (cf. §I) A=C S % On charge le compteur A=A-1 S % et on décale ... GOC DEC.OK A=A+A X % 1 fois (si nécessaire) B=B+B X % D=D+D X % A=A-1 S % GOC DEC.OK A=A+A X % 2 fois (si nécessaire) B=B+B X % D=D+D X % A=A-1 S % GOC DEC.OK A=A+A X % 3 fois (si nécessaire) B=B+B X % D=D+D X % *DEC.OK `AFFICHE D0=D0+ 16 % On passe a la ligne suivante D0=D0+ 16 % 16 + 16 + 2 = 34 D0=D0+ 2 % donc au second plan `AFFICHE2 D0=D0+ 16 % On passe à la ligne suivante D0=D0+ 16 % 16 + 16 + 2 = 34 D0=D0+ 2 % donc on revient au premier plan P=P+1 % On incrémente le compteur (il revient a 0 avec la carry) GONC COPY.LINE % et on boucle ... RTN Pour afficher en transparent, dans la variable `AFFICHE' on met la chaîne suivante : C=DAT0 X % On lit le fond de l'écran C=C!A X % C=C-B X % DAT0=C X % On écrit la ligne du sprite dans l'écran @ et dans `AFFICHE2' : C=DAT0 X % On lit le fond de l'écran C=C!A X % C=C-D X % DAT0=C X % On écrit la ligne du sprite dans l'écran @ V) Conclusion Les sprites sont principalement utiles, et utilisés, dans les jeux. Le(s) sprite(s) et le fond en constituent les principaux composants graphiques. Une fois les routines d'affichage de sprites réalisées, il ne vous reste plus, grâce au moteur du jeu, qu'à afficher vos sprites où bon vous semble. Les sprites et le fond doivent être dessinés à l'écran le plus rapidement possible, donc les routines d'affichage de sprites se doivent d'être particulièrement optimisées. On ne réalisera pas de routine générique capable d'afficher n'importe quel graphique de taille quelconque, si le temps machine est plus important que la place mémoire occupée par le programme. Lorsqu'il y a peu de sprites, les données du graphique sont généralement inclues dans la routine et si beaucoup utilisent la même routine d'affichage, il peut être utile de les mettre dans une table et d'afficher celui désiré. CHL