PureBasic:Realiser un RPG2D/Les événements et les PNJ


PureBasic:Realiser_un_RPG2D

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



Prérequis

Difficultée estimée de cette étape: assez difficile
Pré-requis:

  • Bonne connaissance de PureBasic (allocations dynamiques, pointeurs et gestion des entrées/sorties)

Une étape importante a été franchie: notre héros se déplace sur un décor avec cohérence. Mais il manque encore quelques ingrédients essentiels à un bon RPG: un peu d'action!

Pour cela, nous allons gérer un PNJ, des dialogues et des systèmes de téléportation. Un peu à l'étroit? Nous ajouterons le changement de carte à la liste des tâches à réaliser durant cette sixième étape.


Sommaire

[modifier] Ajouter un PNJ

Besoin de réviser?

Besoin de réviser?

Pour ajouter un PNJ, c'est très simple: il suffit d'ajouter un autre sprite à l'écran. Avec l'aide du système de sprites programmé durant la précédente étape, cela va être très simple!

Dans le charset, notre PNJ est le personnage en haut à gauche.
Dans le charset, notre PNJ est le personnage en haut à gauche.

Tout d'abord, modifions les données statiques:

Procedure SpriteStaticData(*libsprite.s_lib_sprite)
    
    Protected *Ptsprite.s_sprite,*Ptanim.s_anim,*Ptframe.s_frame
    
    *libsprite\n_sprite = 2
    *libsprite\sprites=AllocateMemory(*libsprite\n_sprite*SizeOf(s_sprite))
    
    ; Sprite 0: player 
    ;Pas de changement ici
         
    ;/ Sprite 1: pnj
    MemSprite=*Ptsprite\surf\Id
    *Ptsprite + SizeOf(s_sprite)
    *Ptsprite\x=100
    *Ptsprite\y=128
    
    *Ptsprite\offsetG=5
    *Ptsprite\offsetD=15 ; 15
    *Ptsprite\offsetH=20
    *Ptsprite\offsetB=3 ; 10
    
    *Ptsprite\surf\Id=MemSprite ; utilise le même sprite que le sprite 0
    ;*Ptsprite\surf\Id=LoadSprite(#PB_Any,"sprites\player.bmp")
    ;TransparentSpriteColor(*Ptsprite\surf\Id,0,0,0)
    *Ptsprite\width=24
    *Ptsprite\height=32
    *Ptsprite\n_anim=4
    *Ptsprite\cur_anim=#ANIMATION_BAS
    *Ptsprite\ispaused_anim=0
    *Ptsprite\anim=AllocateMemory(*Ptsprite\n_anim*SizeOf(s_anim))
    
    ; anim 0: haut 
    *Ptanim=*Ptsprite\anim
    *Ptanim\cur_frame=0
    *Ptanim\n_frame=3
    *Ptanim\lastfrmupdate=ElapsedMilliseconds()
    *Ptanim\delaybetweenframes = 100
    *Ptanim\frame=AllocateMemory(*Ptanim\n_frame*SizeOf(s_frame))
    *Ptframe=*Ptanim\frame
    *Ptframe\x=0
    *Ptframe\y=0
    *Ptframe + SizeOf(s_frame)
    *Ptframe\x=24
    *Ptframe\y=0
    *Ptframe + SizeOf(s_frame)
    *Ptframe\x=48
    *Ptframe\y=0

    ; anim 1: droite 
    *Ptanim + SizeOf(s_anim)
    *Ptanim\cur_frame=0
    *Ptanim\n_frame=3
    *Ptanim\lastfrmupdate=ElapsedMilliseconds()
    *Ptanim\delaybetweenframes = 100
    *Ptanim\frame=AllocateMemory(*Ptanim\n_frame*SizeOf(s_frame))
    *Ptframe=*Ptanim\frame
    *Ptframe\x=0
    *Ptframe\y=32
    *Ptframe + SizeOf(s_frame)
    *Ptframe\x=24
    *Ptframe\y=32
    *Ptframe + SizeOf(s_frame)
    *Ptframe\x=48
    *Ptframe\y=32
    
    ; anim 2: bas
    *Ptanim + SizeOf(s_anim)
    *Ptanim\cur_frame=0
    *Ptanim\n_frame=3
    *Ptanim\lastfrmupdate=ElapsedMilliseconds()
    *Ptanim\delaybetweenframes = 100
    *Ptanim\frame=AllocateMemory(*Ptanim\n_frame*SizeOf(s_frame))
    *Ptframe=*Ptanim\frame
    *Ptframe\x=0
    *Ptframe\y=64
    *Ptframe + SizeOf(s_frame)
    *Ptframe\x=24
    *Ptframe\y=64
    *Ptframe + SizeOf(s_frame)
    *Ptframe\x=48
    *Ptframe\y=64
    
    ; anim 3: gauche 
    *Ptanim + SizeOf(s_anim)
    *Ptanim\cur_frame=0
    *Ptanim\n_frame=3
    *Ptanim\lastfrmupdate=ElapsedMilliseconds()
    *Ptanim\delaybetweenframes = 100
    *Ptanim\frame=AllocateMemory(*Ptanim\n_frame*SizeOf(s_frame))
    *Ptframe=*Ptanim\frame
    *Ptframe\x=0
    *Ptframe\y=96
    *Ptframe + SizeOf(s_frame)
    *Ptframe\x=24
    *Ptframe\y=96
    *Ptframe + SizeOf(s_frame)
    *Ptframe\x=48
    *Ptframe\y=96
EndProcedure

Vous pouvez modifier la fonction principale afin de générer un nouveau fichier .sprite:

    ;** Les sprites **
    InitSprites(@libsprite)
    SpriteStaticData(@libsprite)
    SaveSprites(@libsprite, "sprites\player.bmp", "map\01.sprite")

    ;LoadSprites(@libsprite,"map\01.sprite")

Une fois le programme exécuté avec cette version, un nouveau fichier est généré, il ne reste plus qu'à réinverser les modifications pour obtenir un PNJ sur notre carte!

    ;** Les sprites **
    ;InitSprites(@libsprite)
    ;SpriteStaticData(@libsprite)
    ;SaveSprites(@libsprite, "sprites\player.bmp", "map\01.sprite")

    LoadSprites(@libsprite,"map\01.sprite")

Cette manipulation très simple vous permet de générer n'importe quel fichier de sprite et donc de concevoir n'importe quelle carte avec autant de sprites que vous le souhaitez!


[modifier] Gestion du déplacement des PNJ


Le comportement des PNJ type villageois dans un RPG suit souvent ce principe:

SI il n'y a pas d'obstacle devant nous ALORS
   avancer tout droit
SINON
   changer de direction
FIN SI
répéter l'opération

Il en résulte un comportement bête mais efficace: le sprite parcourt la carte sans s'arrêter.

Pour obtenir ce résultat, il faut légèrement modificer la gestion des sprites dans la fonction HandleSprites()

Procedure HandleSprites(*libsprite.s_lib_sprite,*map.s_map)
    
    Protected i,oldx,oldy,anim,*Ptsprite.s_sprite
    
    ; centre la vue sur le joueur 
    *Ptsprite=*libsprite\sprites
    
    If(*Ptsprite\x<(800/2))
        *map\offsetX=0     
    Else
        
        *map\offsetX=*Ptsprite\x-(800/2)
        If *map\offsetX>*map\width*#TILE_WIDTH-800 
            *map\offsetX=*map\width*#TILE_WIDTH-800
        EndIf 
        
    EndIf
    
    
    If *Ptsprite\y<(600/2)
        *map\offsetY=0     
    Else
        
        *map\offsetY=*Ptsprite\y-(600/2)
        If *map\offsetY>*map\height*#TILE_HEIGHT-600 
            *map\offsetY=*map\height*#TILE_HEIGHT-600
        EndIf
        
    EndIf    
    
    ; gestion du joueur 
    HandleSprite(*Ptsprite, *map)      
     
    ; gestion des pnj 
    For i=1 To *libsprite\n_sprite-1
        
        *Ptsprite=*libsprite\sprites + i * SizeOf(s_sprite)
        
        If *Ptsprite\ispaused_anim=0
            
            oldx=*Ptsprite\x
            oldy=*Ptsprite\y
            
            ; on fait se déplacer le pnj en continuant dans sa direction... 
            Select *Ptsprite\cur_anim
                
                Case #ANIMATION_DROITE
                    SpriteMove(*Ptsprite, *Ptsprite\x+1, *Ptsprite\y, *map)
                    
                Case #ANIMATION_GAUCHE
                    If *Ptsprite\x>0
                        SpriteMove(*Ptsprite, *Ptsprite\x-1, *Ptsprite\y, *map)
                    EndIf   
                Case #ANIMATION_HAUT
                    If *Ptsprite\y>0
                        SpriteMove(*Ptsprite, *Ptsprite\x, *Ptsprite\y-1, *map)
                    EndIf    
                Case #ANIMATION_BAS
                    SpriteMove(*Ptsprite, *Ptsprite\x, *Ptsprite\y+1, *map)
                    
            EndSelect
            
            If oldx=*Ptsprite\x And oldy=*Ptsprite\y
                
                anim=*Ptsprite\cur_anim
                While anim=*Ptsprite\cur_anim
                    anim=Random(*Ptsprite\n_anim-1)
                Wend 
                
                SpriteChangeAnim(*Ptsprite, anim, 0)
   
            EndIf
            
        EndIf  
        
        HandleSprite(*Ptsprite, *map)
        
    Next
   
EndProcedure

On découpe la gestion des sprites en deux cas:

  • le numéro du sprite est zéro: dans ce cas, il s'agit du joueur et nous n'avons pas à influencer son comportement
  • le numéro du sprite est différent de zéro: il s'agit d'un pnj, si l'animation n'est pas en pause, nous allons nous occuper de le déplacer.

Il suffit ensuite de réutiliser la fonction SpriteMove() pour faire bouger le sprite.


[modifier] Le sytème de gestion des événements

Avant de commencer, qu'est-ce qu'un événement dans un rpg?

Il s'agit de toutes les interactions possibles entre le joueur et le jeu durant la partie. Quelques exemples:

  • un dialogue entre un pnj et notre héros
  • un changement de carte
  • une téléportation dans la carte actuelle
  • l'affichage du menu de l'auberge ou bien d'un marchand
  • une bataille
  • un changement de musique
  • un effet graphique: pluie, neige, tremblement de terre, éclair, etc.
  • etc.

Ce ne sont évidemment que quelques exemples. Ce système est conçu pour que vous puissiez rajouter n'importe quel autre événement non-géré actuellement. Cinq événements différents seront prévus dans cette étape. Seule l'événement téléportation sera totalement fonctionnel à la fin de cette partie, les autres suivront dans les prochaines étapes.


Ces événements peuvent être liés:

  • à la carte: l'événement se déclenche quand on passe sur un tile déterminé
  • à un sprite: l'événement se déclenche quand on passe près d'un sprite donné
  • ou bien il peut s'agir d'un événement global. Il peut se déclencher potentiellement à tout moment.


D'autres paramétrages sont intégrés au système:

  • l'événement se déclenche si l'on appuie sur espace au lieu de se déclencher automatiquement
  • l'événement se déclenche X fois sur 100
  • l'événement est unique: il n'arrivera qu'une fois.

Un son peut être associé à l'événement et l'événement peut également changer l'animation actuelle du joueur.


En combinant plusieurs événements, ce système permet de réaliser n'importe quelle opération complexe.


[modifier] Création des fichiers events.pb et events.pbi


Ajoutez les deux fichiers au projet:

events.pb

;** event.pb
;**
;** Gère le système d'événements
;**
;** Rôle:
;** - initialise et libère les événements
;** - gère le déclenchement et le déroulenement des événements

XincludeFile "event.pbi"

events.pbi

;** event.pbi
;**
;** Gère le système d'événements
;**
;** ATTENTION: à initialiser après la carte et les sprites.
;**            à libérer avant la carte et les sprites.
;**
;** Rôle:
;** - initialise et libère les événements
;** - gère le déclenchement et le déroulenement des événements


[modifier] Implémentation des types de donnée nécessaires

Différents types vont être nécessaire:

  • enum e_event_type qui définit le type d'un événement.
  • enum e_event_action qui définit quelle action permet de déclencher un événement.
  • struct s_lib_event qui définit l'ensemble des événements associés à une carte.
;Type d'évènement : e_event_type
Enumeration
    #EVENT_NULL
    #EVENT_TELEPORT
    #EVENT_DIALOG   
    #EVENT_BATTLE
    #EVENT_SHOP
    #EVENT_INN
EndEnumeration

;définit quelle action permet de déclencher un événement : e_event_action
Enumeration
    #EVENT_ACTION_AUTO
    #EVENT_ACTION_ONKEYPRESS
EndEnumeration

Structure s_event

 type.l
 onaction.l
 proba.b
   
 *param.LONG
   
  player_anim.l

 *sound.l
   
is_unique.b
   
 *Next.s_event
EndStructure

Structure s_lib_event 
    global_event.s_event
    *map_event.s_event
    *sprite_event.s_event
EndStructure

Plusieurs choses sont à remarquer:

  • chaque événement pointe vers ses paramètres via un pointeur générique (*param.LONG)
  • les événements constituent une liste chaînée où chaque élément pointe vers le suivant via le champ next
  • la bibliothèque d'événement indique que l'on peut classer les événements en trois catégories:
    • les événements globaux
    • les événements associés à la carte (on devra allouer un tableau de même taille que la carte pour associé un événement par tuile)
    • les événements associés à un sprite (on devra allouer un tableau de même taille que le tableau contenant tous les sprites)

; paramètres des événements 
Structure s_event_param_teleport 
    filename.s
    startX.l
    startY.l
EndStructure

Structure s_event_param_dialog 
  dialog.s
EndStructure

Structure s_event_param_battle 
EndStructure

Structure s_event_param_shop 
EndStructure

Structure s_event_param_inn 
EndStructure

Ces structures sont les paramètres associés à chaque type d'événement. Le champ param pointe vers l'une de ces structures selon le type de l'événement. Un événement de type EVENT_TELEPORT verra son champ param pointer vers un struct s_event_param_teleport par exemple.


Plus précisément, on peut s'apercevoir que pour définir un changement de carte, le triplet (nom de la nouvelle carte, nouvelle position en X, nouvelle position en Y) est nécessaire.

Pour les dialogues, seul le dialogue est nécessaire.

Quant aux autres événements, ils auront besoins de paramètres, mais ils ne sont pas encore implémentés.

XIncludeFile "map.pbi"
XIncludeFile "sprite.pbi"

; Remarque : je garde le principe des macros , quand PureBasic permettra de les gérer 
; il suffira de faire une recherche sur le nom des fonctions suivantes dans le code
; pour les remplacer par la macro qui va bien  
 
;# define GET_MAPEVENT(x,y,map,event) event->map_event[(x)+(y)*map->width]
Procedure GET_MAPEVENT(x,y,*map.s_map,*event.s_event)
    ProcedureReturn x+y**map\width
EndProcedure    
    
;# define RND_EVENT_SUCCESS(event) ( ((event).proba==100) || ( (rand()%101) < (event).proba) )
Procedure RND_EVENT_SUCCESS(*event.s_event)
    If *event\proba=100 Or Random($7FFFFFFF)%101 < *event\proba
        ProcedureReturn  1
    Else
        ProcedureReturn 0
    EndIf    
EndProcedure  
  
;# define IS_NEAR(x1,x2,y1,y2,dist_max) ( (Abs((x1)-(x2))<dist_max) && (Abs((y1)-(y2))<dist_max) )
Procedure IS_NEAR(x1,x2,y1,y2,dist_max)
    If Abs(x1-x2)<dist_max And Abs(y1-y2)<dist_max
        ProcedureReturn 1
    Else
        ProcedureReturn 0
    EndIf    
EndProcedure   
  • GET_EVENT() permet d'accéder rapidement à un événement associé à la carte
  • RND_EVENT_SUCCESS() permet de savoir si l'événement a lieu en tenant compte de la probabilité qu'il arrive
  • IS_NEAR indique si deux points sont suffisamment proche l'un de l'autre pour déclencher un événement.


[modifier] Initialisation et libération de la structure


L'initialisation de la librairie alloue les tableaux statiques nécesaires et appelle InitEvent() pour initialiser chaque événement alloué.

Procedure InitLibEvent(*libevent.s_lib_event, *map.s_map,*libsprite.s_lib_sprite)
    
    Protected  i,*Ptsprite_event.s_event,*Ptmap_event.s_event
    
    InitEvent(*libevent\global_event)
    *libevent\map_event=AllocateMemory(*map\width**map\height*SizeOf(s_event))
    For i=0 To (*map\width**map\height)-1
        *Ptmap_event=*libevent\map_event + i * SizeOf(s_event)
        InitEvent(*Ptmap_event)
        *libevent\sprite_event=AllocateMemory(*libsprite\n_sprite*SizeOf(s_event))
    Next i
    
    For i=0 To *libsprite\n_sprite-1
        *Ptsprite_event=*libevent\sprite_event + i * SizeOf(s_event)
        InitEvent(*Ptsprite_event)
    Next i    
    
EndProcedure
        
Procedure InitEvent(*event.s_event)
    *event\type=#EVENT_NULL
    *event\onaction=#EVENT_ACTION_AUTO
    *event\proba=0
    *event\param=#Null
    *event\player_anim=-1
    *event\sound=#Null
    *event\is_unique=0
    *event\Next=#Null
EndProcedure

La libération procède inversement: on libère chaque événement et on libère ensuite les tableaux alloués.

Procedure FreeLibEvent(*libevent.s_lib_event,*map.s_map,*libsprite.s_lib_sprite)
    Protected i,*Ptsprite_event.s_event,*Ptmap_event.s_event
    FreeEvent(*libevent\global_event)
    For i=0 To (*map\width**map\height)-1
        *Ptmap_event=*libevent\map_event + i * SizeOf(s_event)
        FreeEvent(*Ptmap_event)
    Next i    
    FreeMemory(*libevent\map_event)
    For i=0 To *libsprite\n_sprite-1
        *Ptsprite_event=*libevent\sprite_event + i * SizeOf(s_event)
        FreeEvent(*Ptsprite_event)
    Next i    
    FreeMemory(*libevent\sprite_event)
EndProcedure
        
Procedure FreeEvent(*event.s_event)
    
    *Suivant.s_event
     
    If *event=0 
        ProcedureReturn
    EndIf
     
    *event=*event\Next
    While *event
        
        *Suivant=*event\Next
        If *event\sound 
            FreeSound(*event\sound)
        EndIf    
    
        Select *event\type
        
        Case #EVENT_NULL
            
        Case #EVENT_TELEPORT
            FreeMemory(*event\param)
            
        Case #EVENT_DIALOG
            FreeMemory(*event\param)
            
        Case #EVENT_BATTLE
            FreeMemory(*event\param)
            
        Case #EVENT_SHOP:
            FreeMemory(*event\param)
            
        Case #EVENT_INN
            FreeMemory(*event\param)
    
        EndSelect
        FreeMemory(*event)
        
        *event=*Suivant
    Wend
EndProcedure

FreeEvent est plus complexe que l'initialisation car si l'événement n'est pas vide, il faut libérer le paramètre associé à l'événement ainsi que les données qu'il contient.


[modifier] Chargement des données statiques

Les données statiques créent trois événements: deux associés à la carte et un autre associé à notre PNJ.

Procedure EventStaticData(*libevent.s_lib_event,*map.s_map,*libsprite.s_lib_sprite)
    
    Protected *Ptmap_event.s_event,*Ptsprite_event.s_event
    Protected *Ptparam_dialog.s_event_param_dialog,*Ptparam_telport.s_event_param_teleport
    
    *Ptmap_event=*libevent\map_event + GET_MAPEVENT(2,2,*map,*libevent) * SizeOf(s_event)
    *Ptmap_event\type=#EVENT_DIALOG
    *Ptmap_event\onaction=#EVENT_ACTION_AUTO
    *Ptmap_event\proba=100
    *Ptmap_event\player_anim=-1
    *Ptmap_event\sound=#Null
    *Ptmap_event\is_unique=1
    *Ptmap_event\Next=#Null
    *Ptmap_event\param=AllocateMemory(SizeOf(s_event_param_dialog))
    *Ptparam_dialog=*Ptmap_event\param
    *Ptparam_dialog\dialog="Test de dialogue" + #LFCR$ + "Sur plusieurs lignes..."
        
    *Ptmap_event=*libevent\map_event + GET_MAPEVENT(2,3,*map,*libevent) * SizeOf(s_event)
    *Ptmap_event\type=#EVENT_TELEPORT
    *Ptmap_event\onaction=#EVENT_ACTION_ONKEYPRESS
    *Ptmap_event\proba=100
    *Ptmap_event\player_anim=-1
    *Ptmap_event\sound=LoadSound(#PB_Any,"son\tornade.wav")
    *Ptmap_event\is_unique=0
    *Ptmap_event\Next=#Null
    *Ptmap_event\param=AllocateMemory(SizeOf(s_event_param_teleport))
    *Ptparam_telport=*Ptmap_event\param
    *Ptparam_telport\filename="map\empty"
    *Ptparam_telport\startX=128
    *Ptparam_telport\startY=128
        
    ;Sprite 1
    *Ptsprite_event=*libevent\sprite_event + SizeOf(s_event)
    *Ptsprite_event\type=#EVENT_TELEPORT
    *Ptsprite_event\onaction=#EVENT_ACTION_AUTO
    *Ptsprite_event\proba=100
    *Ptsprite_event\player_anim=-1
    *Ptsprite_event\sound=LoadSound(#PB_Any,"son\tornade.wav")
    *Ptsprite_event\is_unique=0
    *Ptsprite_event\Next=#Null
    *Ptsprite_event\param=AllocateMemory(SizeOf(s_event_param_teleport))
    *Ptparam_telport=*Ptsprite_event\param
    *Ptparam_telport\filename=#Null$
    *Ptparam_telport\startX=128
    *Ptparam_telport\startY=128

EndProcedure


[modifier] Gestion des événements

Pour gérer les événements, il faut parcourir tous les événements et tester s'ils remplissent toutes les conditions pour être déclenchés.

Si c'est le cas, on appelle la fonction DoEventXXX où "XXX" est le type de l'événement déclenché (DoEventTeleport pour les téléportations par exemple).

Le premier paramètre de cette fonction est l'événement qui s'est déclenché. Les modules principaux du jeu (carte, sprites et événements) sont également passé en paramètre pour que la fonction puisse influencer n'importe quelle partie du programme.


[modifier] Programmation de chaque événement

Nous allons commencer par nous attaquer aux fonctions spécifiques à chaque événement.

; Code spécifique à chaque événement 
Procedure DoEventTeleport(*event.s_event,*libevent.s_lib_event,*map.s_map,*libsprite.s_lib_sprite)
    
    Protected file_map.s, file_sprite.s, file_event.s
    Protected *param.s_event_param_teleport
    
    *param=*event\param
    
    If *param\filename=#Null$
        
        SpriteMove(*libsprite\sprites, *param\startX, *param\startY, *map)
        
    Else 
        file_map=*param\filename + ".map"
        file_sprite=*param\filename + ".sprite"
        file_event=*param\filename + ".event"

        FreeMap(*map)
        FreeSprites(*libsprite)
        FreeLibEvent(*libevent,*map,*libsprite)
        
        LoadSprites(*libsprite, file_sprite)
        
        LoadMap(*map, file_map)
         
        loadEvent(*libevent,*map,*libsprite,file_event)
        
    EndIf
EndProcedure
    
Procedure DoEventDialog(*event.s_event,*libevent.s_lib_event,*map.s_map,*libsprite.s_lib_sprite)
    
    *param.s_event_param_dialog
    *param=*event\param
    
    ; ne nous occupons pas du rendu à l'écran pour l'instant 
    StartDrawing(ScreenOutput())
    DrawText(*param\dialog)
    StopDrawing()
    
EndProcedure
    
Procedure DoEventBattle(*event.s_event,*libevent.s_lib_event,*map.s_map,*libsprite.s_lib_sprite)
EndProcedure
    
Procedure DoEventShop(*event.s_event,*libevent.s_lib_event,*map.s_map,*libsprite.s_lib_sprite)
EndProcedure
    
Procedure DoEventInn(*event.s_event,*libevent.s_lib_event,*map.s_map,*libsprite.s_lib_sprite)
EndProcedure

Si aucun nom de carte n'est fourni à l'événement de téléportation, il agit comme un simple déplacement du sprite sur la carte. Il faut également remarquer que les extensions de fichier sont automatiquement ajoutées, ainsi pour charger la carte foo.map, filename doit contenir maps\\foo. Les fichiers: foo.map, foo.sprite et foo.event seront chargés respectivement par le module de carte, de sprite et d'événement.


[modifier] Gestion des déclenchements


La fonction qui gère les événements déclenchés se contente de jouer le son et d'appeller la bonne fonction avec les paramètres adéquats. Si l'événement est unique, on procède ensuite à sa suppression.

Cette fonction retourne un booléen: si la fonction retourne vrai (1), alors on doit interrompre le traitement des événéments après l'appel de cette fonction. En effet, si la carte change, on doit interrompre le traitement sous peine de s'exposer à une erreur de segmentation car la liste que l'on parcourt a été libérée et réallouée.

Procedure.l DoEvent(*event.s_event,*libevent.s_lib_event,*map.s_map,*libsprite.s_lib_sprite)
    
    If *event=#Null
        ProcedureReturn 0
    EndIf
    
    If *event\type=#EVENT_NULL
        ProcedureReturn 0
    EndIf
      
    If *event\sound
        PlaySound(*event\sound)
    EndIf        
    Select *event\type
        
        Case #EVENT_TELEPORT
            DoEventTeleport(*event, *libevent, *map, *libsprite)
            ProcedureReturn 1
            
        Case #EVENT_DIALOG
            DoEventDialog(*event, *libevent, *map, *libsprite)
            
        Case #EVENT_BATTLE
            DoEventBattle(*event, *libevent, *map, *libsprite)
            
        Case #EVENT_SHOP
            DoEventShop(*event, *libevent, *map, *libsprite)
            
        Case #EVENT_INN
            DoEventInn(*event, *libevent, *map, *libsprite)
            
    EndSelect    
    
    If *event\is_unique
        
        ; suppr. de l'événement 
        Select *event\type
            
            Case #EVENT_NULL
                
            Case #EVENT_TELEPORT
                FreeMemory(*event\param)
                
            Case #EVENT_DIALOG
                FreeMemory(*event\param)
                
            Case #EVENT_BATTLE
                FreeMemory(*event\param)
                
            Case #EVENT_SHOP
                FreeMemory(*event\param)
                
            Case #EVENT_INN
                FreeMemory(*event\param)
                
        EndSelect
        *event\type=#EVENT_NULL
        *event\onaction=#EVENT_ACTION_AUTO
    EndIf
    
    ProcedureReturn 0
    
EndProcedure


De la même façon que le module de gestion de la carte et des sprites, ce module se met à jour via un appel à la fonction handle. Il s'agit ici de déterminer si un événement de quelque sorte qu'il soit doit se déclencher. Si tel est le cas, DoEvent() est appelé.

Dans cette fonction, on ne s'occupe que des événement à déclenchement automatique (sans avoir besoin d'appuyer sur espace). Les autres seront gérés par Event().

Procedure HandleEvent(*libevent.s_lib_event,*map.s_map,*libsprite.s_lib_sprite)
	Protected tx, ty, i
    Protected *event.s_event
    Protected *Ptsprite0.s_sprite,*Ptspritei.s_sprite
    
    ; Test des événements qui se produisent automatiquement 
    ;/ Global 
    *event=*libevent\global_event
    While *event
        If *event\onaction=#EVENT_ACTION_AUTO And RND_EVENT_SUCCESS(*event)
            If DoEvent(*event, *libevent, *map, *libsprite)
                ProcedureReturn
            EndIf    
        EndIf
        *event=*event\Next
    Wend

    ;/ map   
    *Ptsprite0=*libsprite\sprites
    tx  = (2**Ptsprite0\x+2**Ptsprite0\offsetG+#TILE_WIDTH-*Ptsprite0\offsetD)/(2*#TILE_WIDTH);
    ty  = (2**Ptsprite0\y+2**Ptsprite0\offsetH+#TILE_HEIGHT-*Ptsprite0\offsetB)/(2*#TILE_HEIGHT);

    *event=*libevent\map_event + GET_MAPEVENT(tx,ty,*map,*libevent) * SizeOf(s_event)
    While *event
        If *event\onaction=#EVENT_ACTION_AUTO And RND_EVENT_SUCCESS(*event)
            If DoEvent(*event, *libevent, *map, *libsprite)
                ProcedureReturn
            EndIf
        EndIf
        *event=*event\Next
    Wend
     
    ;/ SPRITE 
    ; inutile de tester 0, c'est le joueur! 
    For i=1 To *libsprite\n_sprite-1
        
        *event=*libevent\sprite_event + i * SizeOf(s_event)
        *Ptspritei=*libsprite\sprites + i * SizeOf(s_sprite)
        
        While *event
            If IS_NEAR(*Ptsprite0\x,*Ptspritei\x,*Ptsprite0\y,*Ptspritei\y,#TILE_WIDTH/2)
                If *event\onaction=#EVENT_ACTION_AUTO And RND_EVENT_SUCCESS(*event) 
                    If DoEvent(*event, *libevent, *map, *libsprite)
                        ProcedureReturn
                    EndIf
                EndIf    
            EndIf
            *event=*event\Next
        Wend 
    Next i       
EndProcedure
La tuile principale associée à un sprite est celle située au centre du rectangle constitué par les marges.
La tuile principale associée à un sprite est celle située au centre du rectangle constitué par les marges.


La fonction Event() intercepte les messages dont le système a besoin. Il s'agit ici de savoir si la touche espace est appuyée. Si c'est le cas, on réalise tous les tests associés aux événements qui se déclenchent lors de l'appui sur cette touche.


Procedure Event(*libevent.s_lib_event,*map.s_map, *libsprite.s_lib_sprite)
    
    Protected tx, ty, i
    Protected *event.s_event
    Protected *Ptsprite0.s_sprite,*Ptspritei.s_sprite
    
    If ExamineKeyboard()
        
        If KeyboardReleased(#PB_Key_Space)
            
            ; Test des événements qui se produisent quand on appuie sur une touche 
            ;/ Global 
            *event=*libevent\global_event
            While *event
                If *event\onaction=#EVENT_ACTION_ONKEYPRESS And RND_EVENT_SUCCESS(*event)
                    If DoEvent(*event, *libevent, *map, *libsprite)
                        ProcedureReturn
                    EndIf
                EndIf    
                *event=*event\Next
            Wend
        
            ;/ map    
            *Ptsprite0=*libsprite\sprites
            tx  = (2**Ptsprite0\x+ 2**Ptsprite0\offsetG+ #TILE_WIDTH-*Ptsprite0\offsetD)/(2*#TILE_WIDTH)
            ty  = (2**Ptsprite0\y+ 2**Ptsprite0\offsetH+ #TILE_HEIGHT-*Ptsprite0\offsetB)/(2*#TILE_HEIGHT)
            
            *event=*libevent\map_event + GET_MAPEVENT(tx,ty,*map,*libevent) * SizeOf(s_event)
            While *event
                If *event\onaction=#EVENT_ACTION_ONKEYPRESS And RND_EVENT_SUCCESS(*event)
                    If DoEvent(*event, *libevent, *map, *libsprite)
                        ProcedureReturn
                    EndIf
                EndIf    
                *event=*event\Next
            Wend
        
            ;/ SPRITE 
            ; inutile de tester 0, c'est le joueur! 
            For i=1 To *libsprite\n_sprite-1
                
                *event=*libevent\sprite_event + i * SizeOf(s_event)
                *Ptspritei=*libsprite\sprites + i * SizeOf(s_sprite)
                
                While *event
                    If IS_NEAR(*Ptsprite0\x,*Ptspritei\x, *Ptsprite0\y,*Ptspritei\y,#TILE_WIDTH/2)
                        If *event\onaction=#EVENT_ACTION_ONKEYPRESS And RND_EVENT_SUCCESS(*event)
                            If DoEvent(*event, *libevent, *map, *libsprite)
                                ProcedureReturn
                            EndIf
                        EndIf  
                    EndIf   
                    *event=*event\Next
                Wend
            Next i
        EndIf
                
    EndIf
EndProcedure

[modifier] Sauvegarde des événements

La sauvegarde des éléments est une fonction longue qui reprend le même principe que celles précédemment étudiées. Le problème est ici compliqué par la présence des paramètres: selon ce dernier, les informations à écrire ne sont plus les mêmes! De plus, on précise avant chaque liste d'événement combien la liste en compte.

Procedure SaveEvent(*libevent.s_lib_event,*map.s_map,*libsprite.s_lib_sprite,bg_sound.s,filename.s)
	Protected *event.s_event
  	Protected  n_event, x, y, i
   
    If OpenFile(0,filename)=0
    	ProcedureReturn
    EndIf
     
     
    ;/ Global 
    n_event=0
    *event=*libevent\global_event
     
    While *event
    	n_event + 1
    	*event=*event\Next
    Wend
    WriteLong(n_event)
     
    *event=*libevent\global_event
    While *event
       	WriteEvent(*event, bg_sound)
    	*event=*event\Next
	Wend
     
    ;/ map    
    For x=0 To *map\width-1
    	For y=0 To *map\height-1
       		n_event=0
		    *event=*libevent\map_event + GET_MAPEVENT(x,y,*map,*libevent) * SizeOf(s_event)

    		While *event
    			n_event + 1
    			*event=*event\Next
    		Wend
    		WriteLong(n_event)
      		*event=*libevent\map_event + GET_MAPEVENT(x,y,*map,*libevent) * SizeOf(s_event)
    		While *event
        		WriteEvent(*event, bg_sound)
    			*event=*event\Next
    		Wend
    	Next y
	Next x   
	
    ;/ SPRITE 
    For i=0 To *libsprite\n_sprite-1
    	n_event=0
	    *event=*libevent\sprite_event + i * SizeOf(s_event)

    	While *event
    		n_event + 1
    		*event=*event\Next
    	Wend
      	WriteLong(n_event)
        *event=*libevent\sprite_event + i * SizeOf(s_event)
    	While *event
        	WriteEvent(*event, bg_sound)
    		*event=*event\Next
    	Wend
    Next i
     
     
    CloseFile(0)
EndProcedure

Cette fonction écrit un événement dans le fichier.

Procedure WriteEvent(*event.s_event,bg_sound.s)
	Protected *Ptparam_dialog.s_event_param_dialog,*Ptparam_telport.s_event_param_teleport
    *buffer=AllocateMemory(256)
    
    WriteLong(*event\type)
    If *event\type=#EVENT_NULL
        ProcedureReturn
    EndIf 
    
    WriteLong(*event\onaction)
    WriteByte(*event\proba)
    WriteLong(*event\player_anim)
    WriteByte(*event\is_unique)
    
    WriteData(@bg_sound,Len(bg_sound))
    WriteData(*buffer,63-Len(bg_sound))
    
    Select *event\type
     
    Case #EVENT_NULL
     
    Case #EVENT_TELEPORT
    	*Ptparam_telport=*event\param
    	If *Ptparam_telport\filename<>#Null$
    		WriteData(*Ptparam_telport\filename,Len(*Ptparam_telport\filename))
	  	EndIf  
    	WriteData(*buffer,63-Len(*Ptparam_telport\filename))
    	WriteLong(*Ptparam_telport\startX)
    	WriteLong(*Ptparam_telport\startY)
    
    Case #EVENT_DIALOG
    	*Ptparam_dialog=*event\param
    	If *Ptparam_dialog\dialog<>#Null$
	    	WriteData(*Ptparam_dialog\dialog,Len(*Ptparam_dialog\dialog))
    	EndIf  
    	WriteData(*buffer,255-Len(*Ptparam_dialog\dialog))

    Case #EVENT_BATTLE
     
    Case #EVENT_SHOP
     
    Case #EVENT_INN
     
    EndSelect
EndProcedure


[modifier] Chargement des événements

Il s'agit de l'opération inverse, InitLibEvent() s'occupe des allocations principales, cette fonction réalise l'allocation des paramètres et de leurs contenus.

Procedure loadEvent(*libevent.s_lib_event,*map.s_map,*libsprite.s_lib_sprite,filename.s)

    Protected *event.s_event,*Ptmap_event.s_event
    Protected n_event, x, y, i, spr
     
    InitLibEvent(*libevent,*map,*libsprite)
     
    If ReadFile(0,filename)=0
       ProcedureReturn
    EndIf 

    ;/ Global 
    n_event=0
    n_event=ReadLong()
     
    ReadEvent(*libevent\global_event)
     
    *event=*libevent\global_event
    For i=1 To n_event-1
        *event\Next=AllocateMemory(SizeOf(s_event))
        *event\Next=*event
        ReadEvent(*event)
    Next i
    
    *event\Next=#Null
     
    ;/ map    
    For x=0 To *map\width-1
        For y=0 To *map\height-1
            n_event=0
            n_event=ReadLong()
     		*event=*libevent\map_event + GET_MAPEVENT(x,y,*map,*libevent) * SizeOf(s_event)
            ReadEvent(*event)
            ;*event=*libevent\map_event + GET_MAPEVENT(x,y,*map,*libevent) * SizeOf(s_event)
     
    		For i=1 To n_event-1
    
        		*event\Next=AllocateMemory(SizeOf(s_event))
        		*event\Next=*event
       			ReadEvent(*event)
    		Next i
        	*event\Next=#Null
    	Next y
    Next x; 
    
    ;/ SPRITE 
    For spr=0 To *libsprite\n_sprite-1
    	n_event=0
        n_event=ReadLong()
     	*event=*libevent\sprite_event + spr * SizeOf(s_event);
    	ReadEvent(*event)
        ;*event=*libevent\sprite_event + spr * SizeOf(s_event);
    	For i=1 To n_event-1
        	*event\Next=AllocateMemory(SizeOf(s_event))
        	*event\Next=*event
    		ReadEvent(*event)
    	Next
		*event\Next=#Null
	Next spr
	
    CloseFile(0)
EndProcedure

Cette fonction lit un événement dans un fichier.

Procedure ReadEvent(*event.s_event)
    Protected *Ptparam_dialog.s_event_param_dialog,*Ptparam_telport.s_event_param_teleport
    
    InitEvent(*event)
     
    *event\type=ReadLong()
    If *event\type=#EVENT_NULL
        ProcedureReturn
    EndIf 
    *event\onaction=ReadLong()
    *event\proba=ReadByte()
    *event\player_anim=ReadLong()
    *event\is_unique=ReadByte()
    
    FichierSon.s=Space(256)
    ReadData(@FichierSon,63)
    *event\sound=LoadSound(#PB_Any,FichierSon)
     
    Select *event\type
     
    Case #EVENT_NULL
     
    Case #EVENT_TELEPORT
        
        *event\param=AllocateMemory(SizeOf(s_event_param_teleport))
        *Ptparam_telport=*event\param
        
        Fichier.s=Space(256)
        ReadData(@Fichier,63)
        
        If Len(Trim(Fichier))=0
            *Ptparam_telport\filename=#Null$
        Else
            *Ptparam_telport\filename=Fichier
        EndIf 
        
        *Ptparam_telport\startX=ReadLong()
        *Ptparam_telport\startY=ReadLong()
     
    Case #EVENT_DIALOG
        *event\param=AllocateMemory(SizeOf(s_event_param_dialog))
        *Ptparam_dialog=*event\param
        
        dialogue.s=Space(256)
        ReadData(@dialogue,255)
        
        *Ptparam_dialog\dialog=dialogue
     
    Case #EVENT_BATTLE
     
    Case #EVENT_SHOP
     
    Case #EVENT_INN
     
    EndSelect
EndProcedure


[modifier] Création de nouveaux événements

Pour créer un nouvel événément, il faut:

  • l'ajouter à la liste énumérée enum e_event_action
  • créer le type paramètre associé
  • modifier les Select des fonctions DoEvent(), HandleEvent() et Event()
  • faire gérer ce nouveau type en modifiant les Select de la fonction de sauvegarde et de chargement.

Ainsi, on peut relativement simplement ajouter une infinité de nouveaux événements qui pourront se déclencher dans diverses circonstances.

Le développement de ce système permettra d'arriver à des niveaux d'interactivité similaires à ceux de la plupart des RPG.

[modifier] Modifications de la fonction main()

Afin d'intéger le système d'événement, il faut intéger les fonctions que nous venons de concevoir:

Procedure Main()
    
    map.s_map
    libsprite.s_lib_sprite
    libevent.s_lib_event ; NOUVEAU 
    
    LoadMap(@map, "map\01.map")
    
    ;** Les sprites **
    ;InitSprites(@libsprite)
    ;SpriteStaticData(@libsprite)
    ;SaveSprites(@libsprite, "sprites\player.bmp", "map\01.sprite")
    LoadSprites(@libsprite,"map\01.sprite")
    
    ;** Les évènements **
    ;InitLibEvent(@libevent,@map,@libsprite)
    ;EventStaticData(@libevent,@map,@libsprite)
    ;SaveEvent(@libevent,@map,@libsprite,"son\tornade.wav","map\01.event")
	loadEvent(@libevent,@map,@libsprite,"map\01.event")
    
    Repeat
     
        FlipBuffers()
        ClearScreen(0,0,0)
        MapEvent(@map)
        quit=SpriteEvent(@map, @libsprite)
        Event(@libevent, @map, @libsprite); NOUVEAU 
        HandleMap(@map)
        HandleSprites(@libsprite, @map)
        HandleEvent(@libevent, @map, @libsprite) ; NOUVEAU 
    Until quit
    
    FreeLibEvent(@libevent, @map, @libsprite) ; NOUVEAU 
    FreeMap(@map)
    FreeSprites(@libsprite)
    
EndProcedure

Il est également nécessaire d'inclure le fichier event.pb dans le fichier main.pbi:

;** main.pbi
;**
;** Fichier principal.
;**
;** Rôle:
;** - 

XIncludeFile "map.pb"
XIncludeFile "sprite.pb"
XIncludeFile "event.pb"

Declare Init()
Declare Main()


[modifier] Démo 4: PNJ et événements

Démo 4: PNJ et événements

Dans cette démo, un PNJ qui se déplace a été ajouté ainsi que trois exemples d'événements:

  • un dialogue (n'oubliez pas que pour l'instant les dialogues s'affichent dans ScreenOutput() très rapidement ,l'affichage n'est pas maintenu , c'est juste pour tester)
  • une téléportation vers un autre endroit de la carte
  • une téléportation vers une autre carte ( une fois dans cette carte appuyez sur la touche [Echap] pour sortir )