PureBasic:Realiser un RPG2D/Les cartes
PureBasic:Realiser_un_RPG2D
<< Précédent | Sommaire | Suivant >>
Difficultée estimée de cette étape: moyen
Pré-requis:
- Connaissance moyenne de PureBasic (allocations dynamiques, pointeurs et gestion des entrées/sorties)
Une carte dans un jeu en 2d représente le décor sur lequel le personnage évolue. Il s'agit du fondement même d'un jeu en deux dimensions dans la mesure où la qualité de ce système de cartes va influencer sur le rendu graphique final du jeu.
Le but est de créer via une mosaïque de tuiles (ou tiles) un fond cohérent.
Le schéma de principe est le suivant:
- on charge une image regroupant toutes les tuiles (appellé chipset)
- à chaque tuile on associe un certain nombre de caractéristiques (peut-on traverser la tuile? est-ce que le décor sera animé? etc.) et surtout un numéro.
- on indique pour chaque case du décor quel est le numéro de la tuile correspondante.
[modifier] Création du fichier map.pb et map.pbi
Comme précédemment avec main.pb et main.pbi, vous devez créer deux nouveaux fichiers et insérer le squelette initial sur lequel nous allons développer le système de cartes.
Fichier map.pb
;** map.pb ;** ;** Gestion des cartes. ;** ;** Rôle: ;** - chargement / sauvegarde des cartes ;** - affichage des cartes ;** - génération automatique d'une carte XIncludeFile "map.pbi"
Fichier map.pbi
;** map.pbi ;** ;** Gestion des cartes. ;** ;** Rôle: ;** - chargement / sauvegarde des cartes ;** - affichage des cartes ;** - génération automatique d'une carte #TILE_WIDTH=32 #TILE_HEIGHT=32
[modifier] Le type struct s_map
Il faut désormais définir dans map.h le type que nous allons utiliser pour gérer les cartes.
La structure s_chipset contient les informations caractérisant une tuile.
Structure s_chipset ; coordonnée du tile dans le chipset x.l; y.l; ; indique si l'on peut traverser ou pas le tile collision.b ; indique le numéro du prochain tile dans l'animation ; ou -1 si le tile n'est pas animé. nextTile.l EndStructure
La structure s_map contient l'ensemble des informations se rapportant à une carte.
A partir d'ici , une différence importante avec le code original apparait.
En effet PureBasic ne permet pas l'allocation dynamique d'un tableau dans une structure , il est seulement possible de faire une allocation statique.
Exemple :
Structure s_map n_tile.l chipset.s_chipset[10000] ;Tableau statique EndStructure
Pour y remédier il y a plusieurs solutions, je vais en choisir une, celle qui me semble la plus simple , tout en essayant de conserver au mieux l'organisation du programme original.
Première solution : On pourrait conserver *chipset.s_chipset dans la structure s_map , qui deviendrait un pointeur sur une zone mémoire qui serait allouée dynamiquement.
Structure s_map
n_tile.l
*chipset.s_chipset
surf_chipset.Surface
EndStructure
...
*map\chipset=AllocateMemory(*map\n_tile*SizeOf(s_chipset))
Et ensuite on pourrait utiliser un pointeur auxiliaire pour utiliser cette zone mémoire , par exemple dans la fonction RandomizeMap() ça donnerait :
Procedure RandomizeMap(*map.s_map)
*map\n_tile=6
*map\chipset=AllocateMemory(*map\n_tile*SizeOf(s_chipset))
;Pointeur auxiliaire
*PtAux.s_chipset=*map\chipset
For i=0 To *map\n_tile-1
*PtAux + i*SizeOf(s_chipset) ; place le pointeur sur le tile suivant
*PtAux\x=x
*PtAux\y=y
*PtAux\Collision=0
*PtAux\nextTile=0
x + #TILE_WIDTH
If x>*map\surf_chipset\w-#TILE_WIDTH
x=0;
y + #TILE_HEIGHT
EndIf
Next i
EndProcedure
Deuxième solution :
Utiliser des tableaux qui peuvent être alloués dynamiquement.
Le programme utilise deux tableaux , chipset , et data.Data étant un mot clé en PureBasic , nous nommerons le tableau DataMap.
;Les tableaux seront redimensionnés dans les procédures qui les utilisent Dim chipset(1) ;Tableau contenant les données de chaque tile du chipset Dim DataMap(1) ;Tableau contenant les données de la carte
C'est la solution que j'ai retenue pour l'instant , parce qu'il me semble que l'utilisation d'un tableau sera plus simple à comprendre que les manipulations de pointeurs précédentes.
Autre différence :
Comme nous n'utilisons pas la bibliothèque SDL , nous allons remplacer la structure SDL_Surface par la structure Surface
Structure Surface
Id.l ;Contient l'indentifiant de la surface
w.l ;Largeur de la surface
h.l ;Hauteur de la surface
EndStructure
voici enfin notre structure s_map
Structure s_map
n_tile.l ; nombre de tiles dont est composé la carte
surf_chipset.Surface ; Sprite contenant le chipset
width.l ; largeur de la carte
Height.l ; hauteur de la carte
offsetX.l ; décalage sur l'axe X lors de l'affichage de la carte
offsetY.l ; décalage sur l'axe Y lors de l'affichage de la carte
bg_sound.l ; Identifiant de la musique de fond jouée pendant cette carte
EndStructure
Les deux éléments importants sont chipset() qui est un tableau de n_tile éléments et DataMap() qui est un tableau de width*height éléments. Ce dernier tableau contient la carte proprement dite, c'est à dire pour chaque case quelle tuile va-t-on afficher.
Autre différence avec le programme original . Dans sa version actuelle PureBasic ne permet pas de faire des macros , elles sont prévues avec la version 4.0 , donc patience.
En attendant nous devons remplacer la macro du code original par une procédure.
Cette procédure va simplifier l'accès à une case de notre tableau DataMap():
;Calcul la case du tableau DataMap() correspondant aux coordonnées x et y
Procedure GET_TILE(x,y,*map.s_map)
ProcedureReturn (x+y* *map\width)
EndProcedure
[modifier] Initialisation et libération de la structure
L'initialisation de la carte consiste tout simplement à mettre les pointeurs à NULL et les valeurs numériques à 0.
Dans notre cas , nous n'avons que des valeurs numériques.
Procedure InitMap(*map.s_map)
*map\n_tile=0
*map\width=0
*map\Height=0
*map\offsetX=0
*map\offsetY=0
*map\surf_chipset\Id=0
*map\bg_sound=0
EndProcedure
Lors de la libération de la carte il faut:
- libérer la surface qui contient le chipset
- libérer les tableaux DataMap() et chipset() qui sont alloués dynamiquements
- stopper la musique de fond et la libérer
Procedure FreeMap(*map.s_map)
;Remarque , PureBasic se charge de libérer la mémoire lorsqu'on quitte le programme
;Cette fonction est tout de même conservée pour rester similaire au programme d'origine.
FreeSprite(*map\surf_chipset\Id)
Dim DataMap(0)
Dim chipset.s_chipset(0)
If IsMovie(*map\bg_sound)
StopMovie()
FreeMovie(*map\bg_sound)
EndIf
EndProcedure
[modifier] Génération d'une carte aléatoire
Pour générer une carte aléatoire, nous allons:
- initialiser la carte
- charger la surface (le chemin vers le chipset est passé en paramètre)
- on fixe la couleur de transparence pour la surface
- on détermine le nombre de tiles que l'image comporte
- on alloue le tableau chipset() qui contient les caractéristiques de chaque tile
- on le remplit avec chaque tile présent dans l'image
- on alloue le tableau DataMap() et on le remplit avec des valeurs aléatoires.
Procedure RandomizeMap(*map.s_map, width.l, Height.l, NomChipset.s)
Protected i,x,y
InitMap(*map);
;Charge le chipset et mémorise son identifiant
*map\surf_chipset\Id=LoadSprite(#PB_Any,NomChipset)
If *map\surf_chipset\Id=0
MessageRequester("Erreur","Impossible de charger " + NomChipset,0)
End
EndIf
;Change la couleur transparente du sprite
TransparentSpriteColor(*map\surf_chipset\Id, 8, 33, 82)
;Stock les dimensions du sprite
*map\surf_chipset\w=SpriteWidth(*map\surf_chipset\Id)
*map\surf_chipset\h=SpriteHeight(*map\surf_chipset\Id)
;Calcule le nombre de tiles disponibles sur le chipset
*map\n_tile=(*map\surf_chipset\w/#TILE_WIDTH)*(*map\surf_chipset\h/#TILE_HEIGHT);
;Tableau contenant les caractéristiques de chaque tile du chipset
Dim chipset.s_chipset(*map\n_tile);
x=0
y=0
For i=0 To *map\n_tile-1
chipset(i)\x=x;
chipset(i)\y=y;
chipset(i)\Collision=0;
chipset(i)\nextTile=0;
x + #TILE_WIDTH
If x>*map\surf_chipset\w-#TILE_WIDTH
x=0;
y + #TILE_HEIGHT;
EndIf
Next i
*map\width = width;
*map\Height = Height;
;Tableau contenant les données de la carte
Dim DataMap.l(*map\width * *map\Height)
For x=0 To *map\width-1
For y=0 To *map\Height-1
DataMap(GET_TILE(x,y,*map))=Random(*map\n_tile-1);
Next y
Next x
EndProcedure
[modifier] Affichage d'une carte
L'affichage de la carte est sans conteste le point le plus difficile car il faut gérer le déplacement de la caméra. On parcours l'écran de 32 pixels en 32 pixels en y blittant (copiant) un morceau de l'image contenant le chipset.
Procedure HandleMap(*map.s_map)
Protected x,y,tx,ty,src.point
x=-(*map\offsetX%#TILE_WIDTH)
y=-(*map\offsetY%#TILE_HEIGHT)
tx=*map\offsetX/#TILE_WIDTH
ty=*map\offsetY/#TILE_HEIGHT
While(y<600)
x=-(*map\offsetX%#TILE_WIDTH)
tx=*map\offsetX/#TILE_WIDTH
While(x<800)
src\x=chipset(DataMap(GET_TILE(tx,ty,*map)))\x
src\y=chipset(DataMap(GET_TILE(tx,ty,*map)))\y
ClipSprite(*map\surf_chipset\Id, src\x, src\y,#TILE_WIDTH, #TILE_HEIGHT)
DisplayTransparentSprite(*map\surf_chipset\Id,x,y)
x+ #TILE_WIDTH;
tx + 1;
Wend
y + #TILE_HEIGHT;
ty + 1
Wend
EndProcedure
[modifier] Sauvegarde d'une carte
Il est évident que toutes les cartes ne seront pas "codée" en dur dans notre programme, il faudra les charger à partir de fichiers externes. Pour cela, on peut utiliser les fonctions d'entrée/sortie de PureBasic.
Il s'agit ici de lire l'ensemble du contenu de la carte, y compris les tableaux alloués, et de l'écrire dans un fichier.
Procedure SaveMap(*map.s_map, filename.s, bg_sound.s, chipset.s)
Protected i,x,y
*Buffer=AllocateMemory(256)
If *map=0
ProcedureReturn
EndIf
If OpenFile(0,filename)=0
ProcedureReturn
EndIf
WriteData(@bg_sound,Len(bg_sound));
WriteData(*Buffer,255-Len(bg_sound))
WriteData(@chipset,Len(chipset));
WriteData(*Buffer,255-Len(chipset))
WriteLong(*map\n_tile)
WriteLong(*map\width)
WriteLong(*map\Height)
WriteLong(*map\offsetX)
WriteLong(*map\offsetY)
For i=0 To i<*map\n_tile-1
WriteLong(chipset(i)\x)
WriteLong(chipset(i)\y)
WriteByte(chipset(i)\Collision)
WriteLong(chipset(i)\nextTile)
Next i
For x=0 To *map\width-1
For y=0 To *map\Height-1
WriteLong(DataMap(GET_TILE(x,y,*map)));
Next y
Next x
CloseFile(0)
EndProcedure
Remarque : La fonction SaveMap() est écrite de cette façon afin de rester compatible avec le programme original. Toutefois il serait beaucoup plus simple de l'écrire sous cette forme
Procedure SaveMap2(*map.s_map, filename.s, bg_sound.s, chipset.s)
Protected i,x,y
If *map=0
ProcedureReturn
EndIf
If OpenFile(0,filename)=0
ProcedureReturn
EndIf
WriteData(@bg_sound,Len(bg_sound));
WriteData(@chipset,Len(chipset));
WriteLong(*map\n_tile)
WriteLong(*map\width)
WriteLong(*map\Height)
WriteLong(*map\offsetX)
WriteLong(*map\offsetY)
WriteData(chipset(),*map\n_tile*SizeOf(s_chipset))
WriteData(DataMap(),*map\width * *map\Height*SizeOf(LONG))
CloseFile(0)
EndProcedure
[modifier] Chargement d'une carte
Le chargement d'une carte est l'exacte fonction inverse de la fonction d'écriture, il faut juste en plus allouer les tableaux, charger le chipset et définir la couleur de transparence.
Procedure LoadMap(*map.s_map,filename.s)
Protected i,x,y
If *map=0
ProcedureReturn
EndIf
If OpenFile(0,filename)=0
ProcedureReturn
EndIf
;BG SOUND
;Musique.s=ReadString()
FichierMusique.s=Space(256)
ReadData(@FichierMusique,255)
*map\bg_sound = LoadMovie(#PB_Any,FichierMusique);
If *map\bg_sound=0
MessageRequester("Erreur","Erreur lors du chargement de la carte: impossible de charger " + FichierMusique)
End
EndIf
;chipset
FichierChipset.s=Space(256)
ReadData(@FichierChipset,255)
*map\surf_chipset\Id=LoadSprite(#PB_Any,FichierChipset)
If *map\surf_chipset\Id=0
MessageRequester("Erreur","Impossible de charger " + FichierChipset,0)
End
EndIf
;Change la couleur transparente du sprite
TransparentSpriteColor(*map\surf_chipset\Id, 8, 33, 82)
*map\surf_chipset\Id=LoadSprite(#PB_Any,FichierChipset);
*map\surf_chipset\w=SpriteWidth(*map\surf_chipset\Id)
*map\surf_chipset\h=SpriteHeight(*map\surf_chipset\Id)
*map\n_tile=ReadLong()
*map\width=ReadLong()
*map\Height=ReadLong()
*map\offsetX=ReadLong()
*map\offsetY=ReadLong()
Dim chipset.s_chipset(*map\n_tile)
For i=0 To *map\n_tile-1
chipset(i)\x=ReadLong()
chipset(i)\y=ReadLong()
chipset(i)\Collision=ReadByte()
chipset(i)\nextTile=ReadLong()
Next i
Dim DataMap.l(*map\width * *map\Height)
For x=0 To *map\width-1
For y=0 To *map\Height-1
NoTile=ReadLong()
If NoTile>=*map\n_tile
MessageRequester("Erreur","Carte corrompue à la position => " +Str(x) + " / " + Str(y),0)
NoTile=0;
EndIf
DataMap(GET_TILE(x,y,*map))=NoTile;
Next y
Next x
CloseFile(0)
;Joue la musique
PlayMovie(*map\bg_sound,ScreenID())
EndProcedure
[modifier] Vers un programme utilisable... la boucle de messages
Revenons désormais à main.pb. Il est temps d'ajouter une boucle de jeu sommaire de manière à pouvoir tester notre programme.
Ainsi notre fonction main() va désormais ressembler à ceci:
Procedure Main()
map.s_map
RandomizeMap(@map,100,100,"tiles\mchip0.bmp");
SaveMap(@map,"map\save.map", "musique\Opening1.mid", "tiles\mchip0.bmp");
Repeat
FlipBuffers()
ClearScreen(0,0,0)
MapEvent(@map)
HandleMap(@map)
Until KeyboardPushed(#PB_Key_Escape)
FreeMap(@map)
EndProcedure
Et dans map.pb, la fonction MapEvent va gérer les messages qui intéressent notre système de carte:
Procedure MapEvent(*map.s_map)
If ExamineKeyboard()
If KeyboardPushed(#PB_Key_Right)
If *map\offsetX<*map\width*#TILE_WIDTH-800
*map\offsetX + 1
EndIf
ElseIf KeyboardPushed(#PB_Key_Left)
If (*map\offsetX<>0)
*map\offsetX - 1
EndIf
EndIf
If KeyboardPushed(#PB_Key_Down)
If *map\offsetY<*map\Height*#TILE_HEIGHT-600
*map\offsetY + 1
EndIf
ElseIf KeyboardPushed(#PB_Key_Up)
If *map\offsetY<>0
*map\offsetY - 1
EndIf
EndIf
EndIf
EndProcedure
[modifier] Démo 1: gestion de la carte avec scrolling
Cette première démo permet déjà de générer une carte aléatoire, de l'afficher et de la faire défiler en utilsant les flèches directionnelles.


