PureBasic:Realiser un RPG2D/Les cartes


PureBasic:Realiser_un_RPG2D

<< Précédent | Sommaire | Suivant >>



Prérequis

Difficultée estimée de cette étape: moyen
Pré-requis:

  • Connaissance moyenne de PureBasic (allocations dynamiques, pointeurs et gestion des entrées/sorties)
Le chipset que nous allons utiliser
Le chipset que nous allons utiliser

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.


Sommaire

[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

Besoin de réviser?

Besoin de réviser?


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.

Capture d'écran de la démo
Capture d'écran de la démo