PureBasic:Realiser un RPG2D/Les événements et les PNJ
PureBasic:Realiser_un_RPG2D
<< Précédent | Sommaire | Suivant >>
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.
[modifier] Ajouter un PNJ
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!
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 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
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 )



