Copy Link
Add to Bookmark
Report

2d Chapitre 8 - Engin de défilement 2D

eZine's profile picture
Published in 
2d3dfx
 · 18 Feb 2021

CHAPITRE 8 - Engin de défilement 2D


Ce chapitre tente de faire un résumer de l’ensemble de la matière vue dans la section sur la programmation graphique 2D, en présentant un engin de défilement horizontal et vertical. Qui n'a pas jouer à Super Mario Bros. sur le Nintendo dans sa jeunesse? Bon, pas moi, mais tout le monde à une vague idée de ce genre de jeu où l'écran défile de gauche à droite, et/ou de haut en bas.. Les différents "niveaux" étaient constitués de briques, de nuages, de tunnels, et d'autres éléments décoratifs qui étaient tous des images graphiques statiques. Ces images étaient utilisés à répétitions pour créé le décor voulu.
Par exemple:

"-" et "$" sont des bitmaps...
si je veux créer cette forme sur l'écran: ---$$$---

Je vais utiliser 3 fois le tiret, puis trois fois le signe de dollar, puis trois fois le tiret. Il serait stupide de dessiner manuellement 6 fois le bitmap du tiret, quand on peut tout simplement le mettre en mémoire, et le copier sur l'écran. Heureusement, nous avons vu au chapitres 6 comment lire et afficher des bitmaps. Nos fonctions putsprite et liresprite sont idéales pour ce travail. La seule chose qui nous reste à faire est de déterminer quel image devra être afficher sur l'écran. Pour cela, nous allons utiliser un tableau. Ce tableau est d'une grandeur arbitraire, mais disons que nous voulons une assez grande carte, nous allons déclarer une variable Carte de 128x128:

unsigned char Carte[128][128];

Le fait d'utiliser un octet pour chaque tuiles de notre carte nous permet en effet d'avoir le choix entre 256 tuiles différentes, ce qui est, pour notre cas, amplement suffisant. Nous allons également avoir besoin d'un tableau pour nos sprites. Avec cet exemple, j'ai inclue 60 tuiles de 32x32 pixel assez pour remplir un fichier PCX de 320x200. J'ai choisi la taille de 32 arbitrairement, mais cet engin pourrait facilement s'adapter à des tuiles de 16x16, je pensais juste que 32x32 était parfait pour le mode 13h. Nous allons aller les lires avec liresprite, et nous allons les enregistrer dans le tableau de tuiles:

tsprites tiles[60];

Finalement, nous allons avoir besoin de calculer les coordonnées d'écrans. C'est à dire les coordonnées globales en pixels sur la carte. Ces coordonnées représente la position XY du pixel du coin supérieur gauche. Par exemple, une carte de 128 tuiles de 32 pixels de largeur fait (128*32) pixels de largeur en tout. Nous allons utiliser une structure pour enregistrer ces coordonnées:

 struct
 
{
unsigned int X;
unsigned int Y;
} Monde;


Notez que dans la fonction puttuiles, j'ai juste pris la fonction put_sprite et j'ai enlevé la transparence, pour gagner de la vitesse. Donc, ceci initialisé, nous avons besoin d'une fonction qui va dessiner l'écran. Puisque souvent, nous ne voulons pas que l'action remplisse tout l'écran (pour afficher le score, les vies, etc), nous allons définir la taille de l'écran:

#define ECRAN_X 320
#define ECRAN_Y 200

Pour dessiner l'écran, nous avons besoin de savoir 2 choses: quelles tuiles nous devons dessiner et quel clipping, s'il y en a, devons nous effectuer. Pour ce qui est du premier problème, celui de savoir quelles tuiles doit être dessiner sur l'écran, il suffit de savoir quelle tuile X,Y sera dans le coin supérieur gauche, car les autres tuiles seront dessiner à partir de celle-la. Pour ce, il suffit de diviser les coordonnées d'écran par 32 (taille d'une tuile):

unsigned char TuileX = Monde.X/32;
unsigned char TuileY = Monde.Y/32;

Pour le deuxième problème, celui du clipping, nous allons vérifier si la tuile supérieure gauche en a besoin, car les autres tuiles subiront un décalage correspondant. Pour se faire, nous prenons le numéro de la tuile supérieure gauche X, Y que nous avons trouvé précédemment, et nous lui soustrayons les coordonnées du pixel supérieur gauche de l'écran.

short int Xoff = Monde.X-TuileX*32;
short int Yoff = Monde.Y-TuileY*32;

EX: View.X et View.Y, donc les coordonnées d'écrans, sont (10,10)

2d Chapitre 8 - Engin de défilement 2D
Pin it

Donc tuiles supérieur = 10/32 (arrondi par C) à 0.
TuileX = 0
TuileY = 0

Comme vous pouvez voir sur le petit dessin ASCII, la tuile sera clippée de 10, sur l'axe X et l'axe Y. Notre formule le calcule:

int Xoff = 10 - 0*32 = 10
int Yoff = 10 - 0*32 = 10

Donc il y aura 10 pixels de clippé sur l'axe X de la tuile supérieure gauche et 10 pixel de clippé sur son axe Y. Maintenant nous sommes prêt à dessiner les tuiles sur l'écran. En résolution 320x200, il peut y avoir maximum de 11 tuiles, entières ou clippés, sur l'axe X, et 8 tuiles sur l'axe Y. Nous allons envoyé à notre fonction puttuile le clipping de notre première tuile, et pour que chaques tuiles soient dessinées correctement, nous allons devoir soustraire son offset de départ, car chaques tuiles est maintenant décalées par cet offset. Le clipping en tant que tel sera effectué par les vérifications de puttuile. Pour déterminer quelle tuile sera dessiner, nous allons voir dans notre tableau Carte, aux coordonnées X,Y, que l'on incrémente dans la boucle principale:

 for (int y=0;y<8;y++) 
for (int x=0;x<11;x++)
puttuile(32*x-Xoff,32*y-Yoff,tiles[Carte[TileX+x][TileY+y]]);


La carte doit cependant être créée avant de pouvoir tester l’engin. La première étape est donc d’écrire un éditeur de carte. Celui que j’ai utiliser pour créer mon engin se trouve dans le Kit de construction de jeu, disponible sur mon site dans la section Téléchargements. Il reprend les fonctions vues dans ce chapitre et les exploites de la même façon, sauf qu’il permet de créer et de sauvegarder des cartes de tailles différentes.

CONCLUSION


Vous remarquerez que ce chapitre est court comparé à la matière qu’il comporte. C’est qu’en vérité tout cela est beaucoup plus simple que ça n’en à l’air, donc j’ai inclus un code source très facile à comprendre et cela devrait vous aider à réaliser votre propre engin 2D. Celui que j’ai crée n’est pas efficace du tout en ce qui concerne le clipping (il ne faut pas vérifier inutilement si on doit clipper CHAQUE tuiles que l’on dessine!), et sûrement que vous pouvez arriver à un meilleur résultat.

Également, vous remarquerez que ce programme nécessite l’écriture d’un éditeur de cartes. Mon éditeur n’est pas très complexe, il utilise à 95% les fonctions vues dans ce chapitre. Cependant, il n’est pas inclus avec ce cours. Vous pouvez en télécharger une copie avec le kit de développement de jeux, disponible section Téléchargements.

Ce chapitre clôt la série d’articles sur la programmation 2D. Cette brève introduction est tout ce qu’il vous faudra pour comprendre le reste des tuteurs sur le site. En résumé, nous avons vu :

  • Le clipping sur les sprites et tuiles
  • Les concepts de coordonnées d’écran et du monde
  • Comment construire un monde basé sur un set de tuile


2dchap8.cpp

  
//----------------------------------------------------------------------//
// FICHIER : 2DCHAP8.CPP //
// AUTEUR : Shaun Dore //
// DESCRIPTION : Engin de defilement 2D (scrolling engine) //
// DATE DE MODIFICATION : 07-11-98 //
// COMPILATEUR : Borland Turbo C++ Real Mode 16-bit compiler //
// NOTES : Compiler avec modele memoire Compact //
//----------------------------------------------------------------------//

//----------------------------------------------------------------------//
// Fichiers includes //
//----------------------------------------------------------------------//

#include <mem.h>
#include <conio.h>
#include <stdio.h>

//----------------------------------------------------------------------//
// Declaration des constantes //
//----------------------------------------------------------------------//

#define ECRAN_X 220 // Largeur de la surface de jeu
#define ECRAN_Y 198 // Hauteur de la surface de jeu
#define TUILE_X 32 // Largeur des tuiles
#define TUILE_Y 32 // Hauteur des tuiles
#define CARTE_X 64 // Largeur de la carte
#define CARTE_Y 64 // Hauteur de la carte

#define HAUT '\x48'
#define BAS '\x50'
#define GAUCHE '\x4B'
#define DROITE '\x4D'
#define ESCAPE 27

//----------------------------------------------------------------------//
// Structures de donnees //
//----------------------------------------------------------------------//

struct // Structure pour la souris
{
int X;
int Y;
int bouton1;
int bouton2;
} mouse;

typedef struct tsprite // Structure pour un sprite (simplifie)
{
char *graphics;
};

struct // Structure pour contenir la position de l'ecran
{
int X;
int Y;
} Monde;

//----------------------------------------------------------------------//
// Variables globales //
//----------------------------------------------------------------------//

char *ecran = (char *) 0xA0000000L; // Memoire video
char *virtuel = new char[64000L]; // Ecran virtuel
char *bitmap; // Utiliser pour charger les images
unsigned char Carte[CARTE_X][CARTE_Y]; // Carte
unsigned char touche; // Clavier
tsprite tuiles[60]; // Set de tuiles
tsprite curseur; // Curseur
int BITMAP_X; // Largeur de l'image (pour loadpcx)

//----------------------------------------------------------------------//
// Fonctions graphiques //
//----------------------------------------------------------------------//
void init_graphics(void)
{
asm {
MOV AX,0x13
INT 0x10
}
memset(virtuel,0,64000L);
}


void close_graphics(void)
{
asm {
MOV AX,0x03
INT 0x10
}
delete []bitmap;
delete []virtuel;
}


void clrvirt(void)
{
memset(virtuel,0,64000L);
}


void cpyvirt(void)
{
memcpy(ecran,virtuel,64000L);
}


void putpixel(int x, int y, unsigned char coul)
{
virtuel[(y << 8) + (y << 6)+x] = coul;
}


void hline(int x1, int y, int x2, unsigned char coul)
{
memset(virtuel+x1+(y<<8)+(y<<6),coul,(x2-x1)+1);
}


void vline(int x1, int y1, int y2, unsigned char coul)
{
int offset = (y1<<8)+(y1<<6)+x1;
for(int i=y1;i<y2;i++)
{
virtuel[offset] = coul;
offset += 320;
}
}


void border(short x1, short y1, short x2, short y2, unsigned char coul)
{
hline(x1,y1,x2,coul);
hline(x1,y2,x2,coul);
vline(x1,y1,y2,coul);
vline(x2,y1,y2,coul);
}


//----------------------------------------------------------------------//
// Fonctions de palette 8-bit //
//----------------------------------------------------------------------//
void syncretrace(void)
{
while (!(inp(0x3DA) & 8));
}


void setpal(unsigned char coul,unsigned char r,unsigned char g,unsigned char b)
{
outp (0x03C8,coul);
outp (0x03C9,r);
outp (0x03C9,g);
outp (0x03C9,b);
}


void lirepal(unsigned char coul,unsigned char &r,unsigned char &g,unsigned char &b)
{
outp (0x03C7, coul);
r = inp (0x03C9);
g = inp (0x03C9);
b = inp (0x03C9);
}

//----------------------------------------------------------------------//
// Fonctions pour charger et afficher les graphiques //
//----------------------------------------------------------------------//
int loadpcx(char *nomfich, unsigned long taille, char *bitmap)
{
unsigned char data, nb_octets, palette[768];
unsigned long index = 0;
unsigned int index_rle;
FILE *pcxfile;
if (!(pcxfile = fopen(nomfich, "rb"))) return 0;
fseek(pcxfile, -768, SEEK_END);
fread(&palette, 768, 1, pcxfile);
for (int coul=0;coul<=255;coul++)
setpal(coul,palette[coul*3]>>2,palette[coul*3+1]>>2,palette[coul*3+2]>>2);
fseek(pcxfile, 128, SEEK_SET);
do
{
fread(&data, 1, 1, pcxfile);
if ((data & 0xC0) == 0xC0)
{
nb_octets = (data & 0x3F);
fread(&data, 1, 1, pcxfile);
for (index_rle=1; index_rle<=nb_octets; index_rle++)
bitmap[index++] = data;
}
else bitmap[index++] = data;
} while(index < taille);
fclose(pcxfile);
return 1;
}


void liresprite(int x1,int y1,int largeur,int hauteur, tsprite *sprite)
{
sprite->graphics = new char[TUILE_X*TUILE_Y];

for (int y=0; y<hauteur; y++)
for (int x=0; x<largeur; x++)
sprite->graphics[(y*largeur)+x] = bitmap[(y1+y)*BITMAP_X+x+x1];
}

void putsprite(int x1,int y1,tsprite *sprite)
{
unsigned char octet;
for (int y=0; y<TUILE_Y; y++)
for (int x=0; x<TUILE_X; x++)
{
octet = sprite->graphics[y*TUILE_X+x];
if (octet) virtuel[(y1+y)*320+x+x1] = octet;
}
}

//----------------------------------------------------------------------//
// init_mouse - Initialise la souris (si driver present) //
//----------------------------------------------------------------------//
void init_souris()
{
asm{
XOR AX,AX
INT 0x33
}

asm {
mov ax,0x07
mov cx,0
mov dx,640-(2*TUILE_X)
int 0x33
}

asm {
mov ax,0x08
mov cx,0
mov dx,200-TUILE_Y
int 0x33
}

mouse.X = ECRAN_X/2;
mouse.Y = ECRAN_Y/2;
mouse.bouton1 = 0;
mouse.bouton2 = 0;
}

//----------------------------------------------------------------------//
// mouse_xy - Mise a jour de l'etat de la souris //
//----------------------------------------------------------------------//
void mouse_xy()
{
int x,y,boutons=0;

asm{
MOV AX, 0x03
INT 0x33
MOV [x], CX
MOV [y], DX
MOV [boutons], BX
}
mouse.Y = y;
mouse.X = x >> 1;
if (boutons == 0) { mouse.bouton1 = 0; mouse.bouton2 = 0; }
if (boutons == 1) { mouse.bouton1 = 1; mouse.bouton2 = 0; }
if (boutons == 2) { mouse.bouton1 = 0; mouse.bouton2 = 1; }
if (boutons == 3) { mouse.bouton1 = 1; mouse.bouton2 = 1; }
}

//----------------------------------------------------------------------//
// Fonctions de l'engin 2D //
//----------------------------------------------------------------------//
void deplace_souris()
{
// Si click sur la zone de la mini-carte
if (mouse.bouton1)
{
// Transforme les coordonnees d'ecran en coordonnees du Monde
if ((mouse.X>240) && (mouse.X<304) && (mouse.Y > 120) && (mouse.Y < 184))
{
Monde.X = (mouse.X-240)*TUILE_X;
Monde.Y = (mouse.Y-120)*TUILE_Y;
}
}
// Deplacement de la carte si curseur au bord de l'ecran
if ((mouse.X<5) && (Monde.X >= 5)) Monde.X-=5;
if ((mouse.X>280) && (Monde.X < (CARTE_X-8)*TUILE_X)) Monde.X+=5;
if ((mouse.Y<5) && (Monde.Y >= 5)) Monde.Y-=5;
if ((mouse.Y>160) && (Monde.Y < (CARTE_Y-8)*TUILE_Y)) Monde.Y+=5;
}

void erreur(int no)
{
close_graphics();
switch(no)
{
case 1 : printf("ERREUR : Incapable de charger la carte en memoire!\n");break;
case 2 : printf("ERREUR : Incapable de charger les graphiques!\n");break;
default: printf("ERREUR : Erreur de nature inconnue!\n"); break;
}
asm {MOV AX,0x4C00; INT 0x21};
}


int loadcarte()
{
FILE *F;
if(!(F=fopen("tiles.map", "rb"))) return 0;
for(int y=0;y<CARTE_Y;y++)
for(int x=0;x<CARTE_X;x++)
Carte[x][y]=(unsigned char)fgetc(F);
fclose(F);
return 1;
}


void dessine_tuile(int x1,int y1,tsprite sprite)
{
unsigned char octet;

int Xdeb = 0;
int Ydeb = 0;
int Xfin = TUILE_X;
int Yfin = TUILE_Y;

if (x1 > ECRAN_X-Xfin) Xfin = ECRAN_X-x1;
if (x1 < 0) Xdeb += -x1;
if (y1 > ECRAN_Y-Yfin) Yfin = ECRAN_Y-y1;
if (y1 < 0) Ydeb += -y1;

for (int y=Ydeb; y<Yfin; y++)
for (int x=Xdeb; x<Xfin; x++)
virtuel[(y1+y)*320+x1+x]=sprite.graphics[y*TUILE_X+x];
}


void dessine_ecran()
{
unsigned char TileX = Monde.X/TUILE_X;
unsigned char TileY = Monde.Y/TUILE_Y;
short Xoff = Monde.X-TileX*TUILE_X;
short Yoff = Monde.Y-TileY*TUILE_Y;

for (int y=0;y<8;y++)
for (int x=0;x<8;x++)
dessine_tuile(TUILE_X*x-Xoff,TUILE_Y*y-Yoff,tuiles[Carte[TileX+x][TileY+y]]);
}


void initialisations()
{
Monde.X = 0;
Monde.Y = 0;
BITMAP_X = 320;
bitmap = new char[64000L];

init_souris();
if(!(loadcarte())) erreur(1);
if(!(loadpcx("tileset.pcx",64000L,bitmap))) erreur(2);

for (char y=0; y<6; y++)
for (char x=0; x<10;x++)
liresprite((x*TUILE_X),(y*TUILE_Y),TUILE_X,TUILE_Y,&tuiles[y*10+x]);

delete[] bitmap;
bitmap = new char[TUILE_Y*TUILE_X];
BITMAP_X=TUILE_Y;

if(!(loadpcx("curseur.pcx",TUILE_Y*TUILE_X,bitmap))) erreur(2);
liresprite(0,0,TUILE_X,TUILE_Y,&curseur);
delete[] bitmap;

bitmap = new char[64000L];
if(!(loadpcx("menu.pcx",64000L,bitmap))) erreur(2);
}

// Prototype d'une carte reduite
void minicarte()
{
unsigned char col;
int l=Monde.X/TUILE_X-1;
int h=Monde.Y/TUILE_Y-1;

for (int y=0;y<64;y++)
{
col=0;
for (int x=0;x<64;x++)
{
if (Carte[x][y] < 18) col = 23;
if (Carte[x][y] == 18) col = 33;
if (Carte[x][y] == 17) col = 93;
if (Carte[x][y] > 35) col = 87;
putpixel(240+x,120+y,col);
}
}
border(240+l, 120+h, 248+l, 130+h,255);
}


void boucle_principale()
{
deplace_souris(); // Deplacement?
memcpy(virtuel,bitmap,64000L); // Arriere-plan
dessine_ecran(); // Dessiner la carte
minicarte(); // Dessiner la minicarte
putsprite(mouse.X,mouse.Y,&curseur); // Afficher le curseur
syncretrace(); // Retrace verticale
cpyvirt(); // Copie sur l'ecran physique
}

//----------------------------------------------------------------------//
// Fonction MAIN //
//----------------------------------------------------------------------//
void main()
{
init_graphics();
initialisations();

do
{
boucle_principale();
mouse_xy();
} while (!mouse.bouton2);

close_graphics();
printf("Shaun Dore\ndores@diveotron.ca\n http://pages.infinit.net/shaun/ \n");
}

curseur.pcx
Pin it
curseur.pcx
menu.pcx
Pin it
menu.pcx
tileset.pcx
Pin it
tileset.pcx
← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT