Page 1 sur 2

[jeu] gérer des évènements

Publié : ven. 18/mai/2012 10:37
par blendman
salut

Voici une question d'ordre plutôt générale concernant la gestion/création d'évènement dans un jeu.

Petite explication sur un évènement :
- imaginons que vous arriviez sur une map, vous parlez à un pnj. Celui-ci vous dit de tuer tous les mobs d'une certaine zone (il y en a 4) et de lui ramener un objet : "une clef deuvhout".
- vous tuez tous les mobs de la map, une porte s'ouvre. Vous pouvez passez par là (avant c'était fermé et on ne pouvait pas y aller).
- vous arrivez dans une zone avec d'autres mobs (5), vous les tuez et un coffre apparait. Vous l'ouvrez et vous récupérez un objet "une clef deuvhout".
- vous revenez voir le pnj, vous lui donnez la clef, et un téléporteur apparait. Vous pouvez changer de map.

Voilà en gros un enchainement d'évènements. Je souhaiterai trouver un système de mise en place via mon éditeur de map/gameplay 2D, et qui me permettrait d'avoir un système de gestion d'event/action un peu automatique.

Voici en gros le schéma pour ces actions-là, mais cela pourrait être un autre schéma pour un autre cheminement :
Image

Je voulais donc avoir votre avis sur la ou les méthodes possibles pour ce genre de chose, ou des remarques et des idées :).

Par exemple, il faut que je puisse mettre en place tout ceci directement dans l'éditeur de map. J'ai déjà des objets à ajouter de type : pnj, mob, actions télé-porteur, coffre..). Et ces objets ont des numéros id faisant références à un fichier texte.

Par exemple, le pnj ayant l'id 17 dans l'éditeur va chercher ses informations (nom, type de pnj (quête, commerce..), etc..) dans le fichier texte des pnj, à la référence 17.

Re: [jeu] gérer des évènements

Publié : ven. 18/mai/2012 10:41
par dayvid
Que nous fait-tu la blendman :D
Un genre de RPG Maker en 3D :D

Re: [jeu] gérer des évènements

Publié : ven. 18/mai/2012 10:45
par blendman
non, c'est pour mon jeu Arkeos, je dois trouver un système qui me permet de concevoir facilement et rapidement le gameplay du jeu et ne pas avoir à tout coder directement.

C'est actuellement ce que je fais pour les :
- mob, pnj (quêtes simple, commerce achat, commerce vente), drop, actions (télé-porteurs), voix off, machine (craft)

J'essaie donc de trouver un système pour les évènements plus scénarisés.

Re: [jeu] gérer des évènements

Publié : ven. 18/mai/2012 11:17
par G-Rom
si tu galères , c'est que tu est limité par la conception de ton code à la base.
je vais te donner un exemple plus bas

pour les évènements , comme pour n'importe quel système à signaux, les éléments doivent pouvoir communiquer entre eux ,
s’interroger mutuellement. ce système , dans un premier temps et à définir par tes soins, pour un jeu avec de l'ia , ca peut être complexe,
il faut que tu te poses se genre de question : que peuvent faire les pnj ?

- se déplacer à un point X
- chercher player Y à un point X et le detruire
- ect...

pour chacune de ces actions il te faudra une fonction qui gère les cas de figure différents.
je ne vais pas te codé ton jeu , mais voici un exemple de ce que je ferais en gros :
j'utilise le principe de l'héritage (ou slicing) grâce à "extends"

- tout mes personnages (pnj & player) hérite de Actor
- ça m'évite d'écrire certaine fonction bateau pour chaque type de personnage , des fonctions du style getPosition() setPosition() peuvent être écrite dorénavant que pour Actor les autres en héritent de facto.
- mon moteur de rendu n'aura qu'une seule liste d'Actor qui contiendra tout mes personnage ( pnj , player , etc... )

Voila codé vite fait :

Code : Tout sélectionner


Action possible
Enumeration $FF
  #ACTION_MOVE_TO               ; Va à un point donner
  #ACTION_SEARCH_AND_DESTROY    ; Recherche à un point donner pour détruire
EndEnumeration



Structure Action
  
  actionType.i
  
  destination_x.f  
  destination_y.f  
  *actor_target.i
  
EndStructure



; Structure DE BASE qui gère TOUT les personnage du jeu
Structure Actor
  
  nom.s
  
  position_x.f
  position_y.f
  
  List actionToDo.Action()
  
  
  *func_render.i ; fonction de rendu de Actor

EndStructure


; PNJ dérive/hérite d'actor
Structure pnj Extends Actor
  ; Attribut spécifique à un PNJ
EndStructure

; Player dérive/hérite d'actor
Structure player Extends Actor
  ; Attribut spécifique à un joueur humain
EndStructure



; Procedure de rendu du joueur
Procedure renderPlayer(*this.player) :  
  Debug "Rendu du joueur"  
EndProcedure

; Procedure de rendu du pnj
Procedure renderPnj(*this.pnj) :  
  Debug "Rendu du pnj"  
  
  ForEach *this\actionToDo()
    Select *this\actionToDo()\actionType
      Case #ACTION_MOVE_TO
        Debug "Move to :" + Str( *this\actionToDo()\destination_x ) + ":"+ Str(*this\actionToDo()\destination_y)
      Case #ACTION_SEARCH_AND_DESTROY
        
        *target.actor = *this\actionToDo()\actor_target
        Debug *this\nom + " search and destroy the player " + *target\nom
    EndSelect
  Next   
  
EndProcedure




; liste de tout les acteurs du jeu
NewList *human.Actor()


; Création du joueur
*joueur.player      = AllocateMemory(SizeOf(player))
InitializeStructure(*joueur, player)
*joueur\nom         = "Arkeos"
*joueur\func_render = @renderPlayer()

; on le rajoute à la liste globale
AddElement(*human())
*human() = *joueur



; Création du png
*mechant.pnj   = AllocateMemory(SizeOf(pnj))
InitializeStructure(*mechant, pnj)
*mechant\nom   = "Thor"
*mechant\func_render = @renderPnj()

; on lui ajoute une action
AddElement( *mechant\actionToDo() )

  *mechant\actionToDo()\actionType   = #ACTION_SEARCH_AND_DESTROY 
  *mechant\actionToDo()\actor_target = *joueur

; on le rajoute à la liste globale
AddElement(*human())
*human() = *mechant



; UNE SEULE LISTE CONTIENT DES JOUEURS , DES PNJ , ETC... , TOUT CE QUI DERIVE DE ACTOR !!!
ForEach *human()
  CallCFunctionFast( *human()\func_render,  *human()) 
  Debug ""
  Debug ""
Next 


Re: [jeu] gérer des évènements

Publié : ven. 18/mai/2012 19:32
par comtois
j'ai lu rapidement, donc il est possible que je passe à côté de l'essentiel :)
Mais je ne pense pas qu'il faille se soucier de gérer l'enchainement des événements. Il vaut mieux considérer les conditions à remplir pour qu'une action puisse se faire, par exemple :

La porte 1 ne peut s'ouvrir que si il n'y a plus de mob dans la zone, peu importe comment ils ont disparus.
Tu as donc à créer :
- un type Porte qui aura un compteur que tu pourras renseigner dans les propriétés de ton éditeur
- un type mob et une liste d'événements à générer à la mort du mob ( par exemple suppression du sprite dans la liste, ajouter des points au joueur, etc) et aussi un événement de type 'Décrémente compteur' --> Objet à décrémenter. Et dans l'éditeur tu indiques que l'objet sera la porte 1.

Ou tu peux imaginer que le comptage des mobs se fera au niveau de ta map, et que c'est elle qui génère un événement quand il n'y a plus de mob.

C'est juste une idée, mais ça va de soi qu'il faut traiter l'ensemble du jeu pour une bonne analyse !

[EDIT]
Voir aussi l'utilisation des scripts (et je viens de voir qu'il y a même un exemple pour une porte),c'est une très bonne approche pour ce que tu veux faire :
http://www.alpha-arts.net/blog/articles ... ironnement

http://www.alpha-arts.net/blog/articles/tag/6

un autre tuto
http://www.mti.epita.fr/blogs/2012/03/1 ... eux-video/

Re: [jeu] gérer des évènements

Publié : ven. 18/mai/2012 21:56
par case
oui je pencherais aussi pour un système de script. ça demande un peu de travail pour ecrire le langage de script mais après c'est très flexible et puisant

un petit exemple d'un truc que je suis en train de mettre au point justement c'est pas encore très optimisé... mais ça fonctionne pas trop mal^^




script a coller dans la fenêtre de script

Code : Tout sélectionner

if enter()=1
  let zombies,random(4,10)
  let zombiekilled=0
  let questname,"la pourriture grouillante"
  let questinfo,"vous devez tuer %zombies% zombies"
  let questreward,"%playername% a complété sa quete"
  let questupdate,"vous avez tué un zombie [%zombiekilled%/%zombies%]"
  let queststep,0
  let playername,"case"
  let npc,"gimli"
endif
print questname
print questinfo
if queststep=0
  do
    add zombiekilled,1
    print questupdate
    if zombiekilled=zombies
       break
    endif
  loop
  print questreward
  let questinfo,"retournez voir %npc%"
  add queststep,1
  print "quete mise a jour"
  print questname
  print questinfo
endif
if queststep=1
  do
    if random(1,10)=10
      break
    endif
    print " en route vers %npc%"
  loop
  print "%npc% : salut %playername% merci d'avoir tué ces %zombies% zombies"
  print "%npc% : voici ta recompense"
  print " vous avez gagné de l'experience"
  print " vous avez gagné de l'argent"
  endscript
endif



interpreteur

Code : Tout sélectionner

;IncludeFile("autodeclare.pb")
Declare runscript(script$)
Declare parse2()
Declare execute()
Declare isglobal(var$)
Declare isnumeric(st$)
Declare comp(cmp1$,cmp$,cmp2$)
Declare.s function(fun$) ; retourne les resultats de fonctions
Declare.s getdata(value$)
; script engine
OpenWindow(#PB_Any,0,0,640,480,"script editor",#PB_Window_ScreenCentered|#PB_Window_SystemMenu)
edit=StringGadget(#PB_Any,0,0,640,460,"",#ES_MULTILINE|#ES_AUTOVSCROLL|#WS_VSCROLL|#WS_HSCROLL|#ESB_DISABLE_LEFT|#ESB_DISABLE_RIGHT)
rd=ReadFile(#PB_Any,"script.txt")
If rd
Repeat
  scr$+ReadString(rd)+#CRLF$  
Until Eof(rd)
CloseFile(rd)
SetGadgetText(edit,scr$)
EndIf
runscript=ButtonGadget(#PB_Any,0,460,640,20,"run script")
Structure flag
  name.s             ; nom du flag/variable
  value.s            ; valeur stockée  
EndStructure
Structure loop       ; boucle dans le script
  count.i            ; nombre d'execution avant sortie
  restart.i          ; debut de la boucle
EndStructure
Structure Listing    ; listes chainées dans un script
  name.s
  List value.s()
EndStructure  
Structure script
  newscript.b        ; le script viens d'etre ajouté si a 1 
  List script.s()    ; le script une ligne par entrée dans la liste
  position.i         ; position actuelle dans le script
  hunt_endif.i       ; execute ou non un code entre if/endif
  hunt_endloop.i     ; cherche la fin d'une loop et n'execute pas le code 
  List loops.loop()  ; compteurs des boucles et position de retour
  id.s               ; N°ID du script
  List local.flag()  ; variables locales
  List Global.s()    ; liste des variable globales
  List atoms.s()     ; list contenant les differentes parties de la ligne
EndStructure
Global NewList flags.flag() ; liste de variables globale
Global NewList loops.loop() ; liste de boucles globales
Global NewList scripts.script() ; liste des scripts actifs
Global function.s="enter(),random()" ; fonctions 
; boucle principale 
Repeat
  ev=WaitWindowEvent(600)
  Select ev
    Case #PB_Event_Gadget
      Select EventGadget()
        Case runscript
          runscript(GetGadgetText(edit))          
      EndSelect
  EndSelect
  ; script engine 
  ForEach scripts()
    If scripts()\position<=ListSize(scripts()\script())-1      ; si  la position dans le script est valide
      SelectElement(scripts()\script(),scripts()\position)     ; recuperation de la ligne du script
      parse2()                                                 ; decoupage de la ligne
      If scripts()\hunt_endif=0 And scripts()\hunt_endloop=0   ; si la derniere condition testee n'est pas invalide ou si on ne sort pas d'une boucle
        killed=execute()                                              ; execution du script
        If ListSize(scripts())=0                                
          Break
        EndIf
      Else
        If scripts()\atoms()="endif"
          scripts()\hunt_endif -1
          If scripts()\hunt_endif <0
            scripts()\hunt_endif =0
          EndIf 
        ElseIf scripts()\atoms()="loop"
          scripts()\hunt_endloop-1    
          If scripts()\hunt_endloop<0
            scripts()\hunt_endloop=0
          EndIf
        EndIf
      EndIf
      If killed=0 ; le script est toujours actif
      scripts()\position+1
      If scripts()\position>ListSize(scripts()\script())-1      
        scripts()\newscript=0
        scripts()\position=0
      EndIf      
      EndIf
    EndIf 
  Next  
ForEver
Procedure runscript(script$)
  If script$<>""
    AddElement(scripts())
    script$=ReplaceString(script$,#CRLF$,Chr(1))
    count=CountString(script$,Chr(1))+1  
    For a=1 To count
      AddElement(scripts()\script())
      scripts()\script()=Trim(StringField(script$,a,Chr(1)))
    Next
    scripts()\id=Str(Date())
    scripts()\newscript=1
  EndIf 
EndProcedure
Procedure parse2()
  ClearList(scripts()\atoms())
  pos=1
  s$=Trim(scripts()\script())
  atom$=""
  mode=0
  AddElement(scripts()\atoms())
  Repeat    
    Select mode
      Case 0 ; pas de mode
        Select Mid(s$,pos,1)
          Case Chr(34)
            mode=1
            If Len(scripts()\atoms())>0
              AddElement(scripts()\atoms())
            EndIf
            scripts()\atoms()+Mid(s$,pos,1) ; new string            
          Case "="
            If scripts()\atoms()<>"<" And scripts()\atoms()<>">"
              AddElement(scripts()\atoms())
              scripts()\atoms()+Mid(s$,pos,1)
              AddElement(scripts()\atoms())
            Else
              scripts()\atoms()+Mid(s$,pos,1)
              AddElement(scripts()\atoms())
            EndIf            
          Case ">"
            If scripts()\atoms()<>"<"
              AddElement(scripts()\atoms())
              scripts()\atoms()+Mid(s$,pos,1)
              AddElement(scripts()\atoms())                            
            Else
              scripts()\atoms()+Mid(s$,pos,1)
            AddElement(scripts()\atoms())
            EndIf                                              
          Case "+","-","<"
            ;mode=2 ; comparateur            
            AddElement(scripts()\atoms())
            scripts()\atoms()+Mid(s$,pos,1)   
            AddElement(scripts()\atoms())
          Case ";" 
            ; comments follows  
          Case ","
            AddElement(scripts()\atoms())
          Case " "
            AddElement(scripts()\atoms())  
          Case "("
            mode=2
            scripts()\atoms()+Mid(s$,pos,1)            
          Default
            scripts()\atoms()+Mid(s$,pos,1)
        EndSelect        
      Case 1 ; string
        Select Mid(s$,pos,1)
          Case Chr(34)
            mode=0
            scripts()\atoms()+Mid(s$,pos,1) ; new string            
          Default
            scripts()\atoms()+Mid(s$,pos,1)
        EndSelect   
      Case 2; function
        Select Mid(s$,pos,1)
          Case ")"
            scripts()\atoms()+Mid(s$,pos,1) 
            mode=0          
          Default                        
            scripts()\atoms()+Mid(s$,pos,1) 
        EndSelect
    EndSelect    
    pos+1
  Until pos>Len(s$)
EndProcedure
Procedure execute()  
  FirstElement(scripts()\atoms())
  Select scripts()\atoms()
    Case "endscript"                            ;tue le script
      DeleteElement(scripts())
      ProcedureReturn 1
    Case "print"                                ;affichage
      While NextElement(scripts()\atoms())      
        If scripts()\atoms()<>"+"    
          out$+getdata(scripts()\atoms())
        EndIf
      Wend
      Debug ">>"+out$
    Case"if"                                    ; comparaison
      NextElement(scripts()\atoms())
      cmp1$=getdata(scripts()\atoms())      
      NextElement(scripts()\atoms())
      cmp$=scripts()\atoms()
      NextElement(scripts()\atoms())
      cmp2$=getdata(scripts()\atoms())
      If comp(cmp1$,cmp$,cmp2$)=0
        scripts()\hunt_endif +1
      EndIf
    Case "let"                          ; assignation valeur a une variable
      NextElement(scripts()\atoms())      
      fnd=0
      ForEach scripts()\local()        
        If scripts()\local()\name=scripts()\atoms()
          fnd=1
          Break
        EndIf
      Next
      If fnd=0
        AddElement(scripts()\local())
      EndIf
      curent=ListIndex(scripts()\local())
      scripts()\local()\name=scripts()\atoms()
      NextElement(scripts()\atoms())
      NextElement(scripts()\atoms())            
      If isnumeric(scripts()\atoms())=3
        v$=getdata(scripts()\atoms())
      Else
        v$=scripts()\atoms()       
      EndIf      
      SelectElement(scripts()\local(),curent)
      scripts()\local()\value=v$
      While NextElement(scripts()\atoms())      
        If scripts()\atoms()<>"+"    
          out$+getdata(scripts()\atoms())
        EndIf
      Wend      
    Case "dec"                              ; decrementation variable
      NextElement(scripts()\atoms())      
      ForEach scripts()\local()
        If scripts()\local()\name=scripts()\atoms()
          NextElement(scripts()\atoms())      
          scripts()\local()\value=Str(Val(scripts()\local()\value)-Val(scripts()\atoms()))
          Break
        EndIf        
      Next
      ForEach flags()
        If flags()\name=scripts()\atoms()
          NextElement(scripts()\atoms())      
          flags()\value=Str(Val(flags()\value)-Val(scripts()\atoms()))
          Break
        EndIf        
      Next
    Case "add"                               ;incrementation variable
      NextElement(scripts()\atoms())      
      ForEach scripts()\local()
        If scripts()\local()\name=scripts()\atoms()
          NextElement(scripts()\atoms())      
          scripts()\local()\value=Str(Val(scripts()\local()\value)+Val(scripts()\atoms()))
          Break
        EndIf        
      Next
      ForEach flags()
        If flags()\name=scripts()\atoms()
          NextElement(scripts()\atoms())      
          flags()\value=Str(Val(flags()\value)+Val(scripts()\atoms()))
          Break
        EndIf        
      Next   
    Case "break"                             ; sortie d'une boucle
      LastElement(scripts()\loops())
      DeleteElement(scripts()\loops())
      If arg$<>""
        scripts()\hunt_endloop=Val(arg$)
      Else
        scripts()\hunt_endloop=1
      EndIf
    Case "do"                                ; debut d'une boucle
      AddElement(scripts()\loops())          
      If NextElement(scripts()\atoms())
        scripts()\loops()\count=Val(getdata(scripts()\atoms()))
      Else
        scripts()\loops()\count=-1
      EndIf
      scripts()\loops()\restart=ListIndex(scripts()\script())
    Case "loop"                              ; fin de la boucle
      If scripts()\loops()\count>0
        scripts()\loops()\count -1                   
      EndIf          
      If scripts()\loops()\count>0 Or scripts()\loops()\count<0
        scripts()\position=scripts()\loops()\restart
      Else
        DeleteElement(loops())
      EndIf
  EndSelect
EndProcedure
Procedure isglobal(var$)
  ForEach flags()
    If LCase(flags()\name)=LCase(var$)
      ProcedureReturn 1
    EndIf
  Next
  ProcedureReturn 0
EndProcedure
Procedure isnumeric(st$)  
  If Left(st$,1)=Chr(34) And Right(st$,1)=Chr(34)
    ProcedureReturn 00 ; string    
  Else
    num=0
    For a=1 To Len(st$)
      ch=Asc(Mid(st$,a,1))
      If ch>47 And ch<58
        num+1
      EndIf      
    Next
    If num=Len(st$)
      ProcedureReturn 1 ; numeric
    Else     
      f=FindString(st$,"(")
      If F
        v$=Left(st$,f)+")"
      EndIf      
      fnd=0
      For cnt=1 To CountString(function,",")+1
        If StringField(function,cnt,",")=v$
          fnd=1
          Break
        EndIf
      Next
      If fnd=1        
        ProcedureReturn 3 ; function
      Else
        ProcedureReturn 2 ; flag
      EndIf
    EndIf  
  EndIf 
EndProcedure

Procedure comp(cmp1$,cmp$,cmp2$)  
  Select cmp$
    Case">"
      If Val(cmp1$)>Val(cmp2$)
        ProcedureReturn 1
      EndIf
    Case"<"
      If Val(cmp1$)<Val(cmp2$)
        ProcedureReturn 1
      EndIf            
    Case"<>"
      If Val(cmp1$)<>Val(cmp2$)
        ProcedureReturn 1
      EndIf
      
    Case">="
      If Val(cmp1$)>=Val(cmp2$)
        ProcedureReturn 1
      EndIf
    Case"<="
      If Val(cmp1$)<=Val(cmp2$)
        ProcedureReturn 1
      EndIf
    Case"="
      If Val(cmp1$)=Val(cmp2$)
        ProcedureReturn 1
      EndIf
  EndSelect
EndProcedure
Procedure.s function(fun$) ; retourne les resultats de fonctions
      f=FindString(fun$,"(")
      If F
        v$=Left(fun$,f-1)
      EndIf
      f2=FindString(fun$,")",f+1)
      arg$=Mid(fun$,f+1,f2-(f+1))                  
      fnd=0
      For cnt=1 To CountString(function,",")+1
        If StringField(function,cnt,",")=v$
          fnd=1
          Break
        EndIf
      Next      
  Select v$
    Case "random"
      minrand=Val(StringField(arg$,1,","))
      maxrand=Val(StringField(arg$,2,","))      
      ProcedureReturn Str((Random(maxrand-minrand)+minrand)) 
    Case "enter"            
      ProcedureReturn Str(scripts()\newscript)
  EndSelect  
EndProcedure
Procedure.s getdata(value$)  
  Select isnumeric(value$)
    Case 3 ; function
      value$=function(value$)
      value$=getdata(value$)
    Case 2 ; flag
      ForEach flags()
        If flags()\name=value$
          value$=flags()\value
          value$=getdata(value$)     
          ProcedureReturn value$
        EndIf                  
      Next
      ForEach scripts()\local()
        If scripts()\local()\name=value$
          value$=scripts()\local()\value
          value$=getdata(value$)         
          ProcedureReturn value$          
        EndIf                  
      Next      
    Case 0 ; string      
      value$=Trim(value$,Chr(34))
      ReplaceString(value$,"\%",Chr(1)) ;placeholder pour les signes % a afficher 
      Repeat
        st=FindString(value$,"%",st)        
      If st        
        st2=FindString(value$,"%",st+1)        
        extracted$=Mid(value$,st+1,st2-(st+1))
        l$= Left(value$,st-1)
        r$= Right(value$,Len(value$)-st2)  
        value$=l$+getdata(extracted$)+r$                        
      EndIf
    Until st=0
    ReplaceString(value$,Chr(1),"%")
  Case 1 ; numeric
      value$=value$      
  EndSelect
  ProcedureReturn(value$)
EndProcedure


Re: [jeu] gérer des évènements

Publié : dim. 20/mai/2012 19:45
par stombretrooper
En terme de performance avec des scripts, on perd beaucoup ? Niveau développement je penses qu'on y gagne beaucoup, mais niveau vitesse est ce que la perte de performance est grosse à votre avis ?

Parce que je dois dire que je n'avais pas du tout envisagé cette piste pour gérer les évènements (et d'autres choses) sur le serveur 3Arks, et que je serais assez tenté pour faire un petit interpréteur de script pour gérer ça ! Mais sur un serveur, si 200 joueurs se mettent à exécuter chacun un script, ça risque forcement de ralentir "grandement" le serveur par rapport à du purebasic compilé ?

Au pire, je pensais peut être faire un langage de script pré-interprété (le script passe par un programme qui le convertie en série de byte => plus petit en mémoire, et permettant au programme qui l’exécute de comprendre directement ce qu'il doit faire. De façon à être plus rapide qu'un programme qui interprète les lignes une par une, en directe).

Re: [jeu] gérer des évènements

Publié : dim. 20/mai/2012 20:33
par case
oui rien n’empêche de convertir les instructions du script en byte-code pour gagner en rapidité.
mais pour l’écriture c'est quand même plus pratique d'avoir un langage lisible:)

pour ce qui est tu temps d’exécution je ne sais pas, perso je développe ce moteur de script pour un projet perso.
et je ne m'y suis mis qu'il y a quelques jours... mais je ne crois pas me tromper en disant que la plupart des jeux utilisent des scripts.

Re: [jeu] gérer des évènements

Publié : dim. 20/mai/2012 20:37
par G-Rom
Au pire, je pensais peut être faire un langage de script pré-interprété (le script passe par un programme qui le convertie en série de byte => plus petit en mémoire, et permettant au programme qui l’exécute de comprendre directement ce qu'il doit faire. De façon à être plus rapide qu'un programme qui interprète les lignes une par une, en directe).
Exact , c'est la solution la plus crédible , si c'est bien conçu , les perfs seront similaire à du java.
le principe est celui de la partie arrière d'un quelconque compilateur.

-> "Tokenisation"
-> Contrôle erreur de syntaxe
-> Génération du byte code


La tokenisation consiste à séparé tout les mots du code source ( le script )
et à leur attribuer une valeur en fonction du mot rencontré.
durant cette phase, on sauvegarde le mot lui même , sa valeur , la ligne ou il est écrit , et le fichier dans lequel il est écrit ( ca servira pour les erreurs de syntaxe )
par exemple le script :
A=23
B = A
deviens apres la tokenisation :
A
=
23
EOL
B
=
23
Chaque mot est sauvegardé en "brut" avec le numéro de ligne , et le nom du fichier

ex:
token()\word$ = "A"
token()\line = 1
token()\script$ = "MonScript.txt"
token()\value = ????
reste à déterminé la valeur du 'token' ( jeton ) , ( avec des expression régulière par exemple )

'A' est une suite de caractère , ca peut être une variable , un nom de structure , une fonction , un mot clé réservé à ton langage de script
'=' est un opérateur
'23' est une valeur immédiate

pour le 'A' , il faut que tu arrives à déterminer ce que c'est, si ton langage de script doit déclaré toute les variables avant d'être utilisé tu pourras vérifié dans une table si le nom est déjà présent. si le mot ne comporte pas de ( ) , il y a peu de chance que ce soit une fonction , en revanche pour un tableau , il faut vérifier que A() n'existe pas déjà, sinon erreur de sémantique.

pour la valeur de chaque token , c'est toi qui attribut selon ton envie :

Opérateur :

= valeur $10
+ valeur $11
- valeur $12
* valeur $13
/ valeur $14

Pour les variables , par exemple : $20 suivi d'un index (word par exemple) sur une table de variable ( $xx $xx )
pour les valeurs immédiate qui ont un type 32 bits : $50 suivi du nombre en bytecode aussi

ta serie de token est donc pour le code plus haut :

$20 [$xx $xx] : variable suivi de l'adresse ( l'index d'un tableau (word) )
$10 opérateur égal
$50 $00 $00 $00 $17 valeur immédiate , suivi de la valeur sur 4 octets
$FF fin de l'instruction ( très important ! )

$20 [$yy $yy] : variable 'B' suivi de l'adresse ( l'index d'un tableau (word) )
$10 opérateur égal
$20 [$xx $xx] : variable 'A' suivi de l'adresse ( l'index d'un tableau (word) )
$FF fin de l'instruction ( très important ! )

c'est la première phase , ensuite viens le contrôle de l'erreur de syntaxe, tu vas voir qu'avec un byte code , c'est bien plus simple de contrôler la syntaxe.
tu n'auras pas à le faire en runtime , donc gain de performance.

l'analyseur de syntaxe est une "machine à jeton" , quand elle démarre , elle est neutre , tu peut mettre n'importe quel jeton pour la démarrer, mais attention , une fois démarré , il faut pas lui donné n'importe quel jeton , sinon , erreur de syntaxe !!
dans notre cas, notre machine reçois en premier le jeton qui contient le mot 'A' , il lit la valeur : ( $20 )
a se moment là , la machine à jeton se met dans un état qui , pour le prochain jeton , attend une valeur particulière.
le prochain jeton est $10 ( un opérateur ) , la machine se met dans un autre état et attend le prochain jeton , etc...
si le second jeton était un autre $20 ou un $FF , il y aurais eu une erreur de syntaxe, ce n'était pas un jeton attendu, et comme tu as sauvegardé la ligne du script dans le jeton et le fichier , tu peu balancer une jolie erreur de syntaxe à l'utilisateur ;)
la machine reviens à l'état neutre dès lors quelle tombe sur une fin d'instruction.

récapitulatif de tout ca :

-> tokenisation du script
-> on vérifie les noms de variables , de tableau , si elles sont déclaré , etc...
-> on analyse la syntaxe avec la "machine à jeton"
-> on génère le byte code


Voilà, si je n'est pas été clair , dis le moi , la création de langage ne me fait pas peur :D

Re: [jeu] gérer des évènements

Publié : dim. 20/mai/2012 20:37
par G-Rom
Au pire, je pensais peut être faire un langage de script pré-interprété (le script passe par un programme qui le convertie en série de byte => plus petit en mémoire, et permettant au programme qui l’exécute de comprendre directement ce qu'il doit faire. De façon à être plus rapide qu'un programme qui interprète les lignes une par une, en directe).
Exact , c'est la solution la plus crédible , si c'est bien conçu , les perfs seront similaire à du java.
le principe est celui de la partie arrière d'un quelconque compilateur.

-> "Tokenisation"
-> Contrôle erreur de syntaxe
-> Génération du byte code


La tokenisation consiste à séparé tout les mots du code source ( le script )
et à leur attribuer une valeur en fonction du mot rencontré.
durant cette phase, on sauvegarde le mot lui même , sa valeur , la ligne ou il est écrit , et le fichier dans lequel il est écrit ( ca servira pour les erreurs de syntaxe )
par exemple le script :
A=23
B = A
deviens apres la tokenisation :
A
=
23
EOL
B
=
23
Chaque mot est sauvegardé en "brut" avec le numéro de ligne , et le nom du fichier

ex:
token()\word$ = "A"
token()\line = 1
token()\script$ = "MonScript.txt"
token()\value = ????
reste à déterminé la valeur du 'token' ( jeton ) , ( avec des expression régulière par exemple )

'A' est une suite de caractère , ca peut être une variable , un nom de structure , une fonction , un mot clé réservé à ton langage de script
'=' est un opérateur
'23' est une valeur immédiate

pour le 'A' , il faut que tu arrives à déterminer ce que c'est, si ton langage de script doit déclaré toute les variables avant d'être utilisé tu pourras vérifié dans une table si le nom est déjà présent. si le mot ne comporte pas de ( ) , il y a peu de chance que ce soit une fonction , en revanche pour un tableau , il faut vérifier que A() n'existe pas déjà, sinon erreur de sémantique.

pour la valeur de chaque token , c'est toi qui attribut selon ton envie :

Opérateur :

= valeur $10
+ valeur $11
- valeur $12
* valeur $13
/ valeur $14

Pour les variables , par exemple : $20 suivi d'un index (word par exemple) sur une table de variable ( $xx $xx )
pour les valeurs immédiate qui ont un type 32 bits : $50 suivi du nombre en bytecode aussi

ta serie de token est donc pour le code plus haut :

$20 [$xx $xx] : variable suivi de l'adresse ( l'index d'un tableau (word) )
$10 opérateur égal
$50 $00 $00 $00 $17 valeur immédiate , suivi de la valeur sur 4 octets
$FF fin de l'instruction ( très important ! )

$20 [$yy $yy] : variable 'B' suivi de l'adresse ( l'index d'un tableau (word) )
$10 opérateur égal
$20 [$xx $xx] : variable 'A' suivi de l'adresse ( l'index d'un tableau (word) )
$FF fin de l'instruction ( très important ! )

c'est la première phase , ensuite viens le contrôle de l'erreur de syntaxe, tu vas voir qu'avec un byte code , c'est bien plus simple de contrôler la syntaxe.
tu n'auras pas à le faire en runtime , donc gain de performance.

l'analyseur de syntaxe est une "machine à jeton" , quand elle démarre , elle est neutre , tu peut mettre n'importe quel jeton pour la démarrer, mais attention , une fois démarré , il faut pas lui donné n'importe quel jeton , sinon , erreur de syntaxe !!
dans notre cas, notre machine reçois en premier le jeton qui contient le mot 'A' , il lit la valeur : ( $20 )
a se moment là , la machine à jeton se met dans un état qui , pour le prochain jeton , attend une valeur particulière.
le prochain jeton est $10 ( un opérateur ) , la machine se met dans un autre état et attend le prochain jeton , etc...
si le second jeton était un autre $20 ou un $FF , il y aurais eu une erreur de syntaxe, ce n'était pas un jeton attendu, et comme tu as sauvegardé la ligne du script dans le jeton et le fichier , tu peu balancer une jolie erreur de syntaxe à l'utilisateur ;)
la machine reviens à l'état neutre dès lors quelle tombe sur une fin d'instruction.

récapitulatif de tout ca :

-> tokenisation du script
-> on vérifie les noms de variables , de tableau , si elles sont déclaré , etc...
-> on analyse la syntaxe avec la "machine à jeton"
-> on génère le byte code


Voilà, si je n'est pas été clair , dis le moi , la création de langage ne me fait pas peur :D

Re: [jeu] gérer des évènements

Publié : dim. 20/mai/2012 21:09
par Backup
GRom tu bégaie ? :lol:

Re: [jeu] gérer des évènements

Publié : dim. 20/mai/2012 21:30
par G-Rom
pourquoi ?

Re: [jeu] gérer des évènements

Publié : lun. 21/mai/2012 11:33
par blendman
G-rom : ton 1er exemple est intéressant. Ça m'a fait penser au C# et à l'OO :).

J'utilise déjà "l'héritage" avec extends et pas mal de structure : objet d'action (porte, télélporteur, coffre, objet bloquant..) et pnj (player, pnj, ennemi..).

J'ai une question par contre : comment puis-je connaitre un pnj précisément dans la liste via les pointeurs ?
Car là, ton exemple est simple, amis en réalité, dans mon jeu, j'ai par exemple :
- les objets de type 7 (mobs) ayant une id = 6 qui, si leur condtion N°0 est remplie (ils sont mort) incrémentent la condition N°0 de l'objet type N°5 ( un téléporteur) et d'id = 2 (donc le téléporteur N°2)

Donc, en gros :
lorsqu'un mob7 meurt, le compteur N°1 du téléporteur2 gagne 1 pt.

Mais comment puis-je savoir si le mob que j'ai tué est bien d'id N°7, et ensuite incrémenter le compteur du bon objet (avec son type et son id donc).

Moi, j'ai une structure avec un id comme paramètre, dois-je parcourir la liste des objets et vérifier si l'id correspond (c'est ce que je fais actuellement)

Comtois : j'étais parti sur ce système là, c'est à dire avoir les propriétés de type condition/action à définir sur les objets.
Et ça fonctionne plutôt bien, je pense :).

J'ai aussi pensé aux scripts. Cependant, je pense que mon système mis en place fonctionne correctement, je vais tester un peu les scripts pour voir si je gagne quelque chose.
Etant donné que je fais tout dans mon éditeur de map (ajout des conditions, et des actions), je ne suis pas certain d'avoir vraiment besoin d'un système de script complet. Mais je vais y réfléchir quand même car je l'avais prévu au besoin :).

Case : merci pour ton exemple, c'est très intéressant. Si je décide de mettre en place un système de script, je pense que je m'inspirerai de ton exemple :).


Voici un petit test que j'ai réalisé pour valider mon module de mise en place d'event par objet.
Bon c'est du vite-fait pour tester l'idée.
Je ne sais pas ce que ça vaut, ni si c'est la solution la plus rapide ou intéressante.

N'hésitez pas à corriger si vous pensez qu'on peut l'améliorer ;).

Code : Tout sélectionner

;{ enumeration
Enumeration ; condition 
  ; condtion pour les objets
  #Cond_IsDead
  #Cond_VariableEgal
  #Cond_VariableNotEgal
  #Cond_Compteur1Egal
  #Cond_Compteur2Egal
  #Cond_Compteur3Egal    
EndEnumeration 
Enumeration ; les Actions à lancer
  #ACTION_Nothing ; par défaut c'est  = 0, alors je ne mets aucune actions au cas où
  #ACTION_MOVE_TO  ; Va à un point donner
  #ACTION_SEARCH_AND_DESTROY  ; Recherche à un point donner pour détruire
  #ACTION_ChangeCompteur1 ; changer la valeur du compteur 1 d'un objet
  #ACTION_ChangeCompteur2
  #ACTION_ChangeCompteur3
EndEnumeration
;}

;{ structure
Structure StCondition
  conditionType.a[2] ; le type de condition, par ex : #Cond_IsDead ou  #Cond_Compteur1Egal
  conditionValueCurrent.a[2]  ; valeur actuelle lié à cette condition
  conditionValueMax.a[2] ; valeur max
EndStructure

Structure StAction 
  actionType.i ; le type d'action à entreprendre (attaquer, augmenter un compteur..°
  actionValue.a ; la valeur si besoin (augmenter un compteur)
  targetObjet.a; le type d'objet cible pour l'action à faire (mob, pnj, player, téléporteur..)
  targetId.w ; la cible en question (connu par son id)
EndStructure

; les types d'objets (éditeur et jeu)
Structure StMob
  Vie.w
  Visible.a
  Id.w; id unique, utile pour connaitre le mob
  EventId.a; le numéro identifiant de l'évent auquel il est lié, (=targetid de l'objet qui le déclenche)
  ; Action & condition
  Cond.StCondition
  Action.StAction[2]  ; les actions qu'il peut faire (2 max pour l'instant, mais je passerai sans doute par une liste)
  List actionToDo.StAction(); les actions qu'il fait ou qu'il a en court, en focntion de ce qu'il peut faire
EndStructure
Global Dim mob.StMob(0)


Structure StTeleporter
  Id.w
  EventId.a; le numéro identifiant de l'évent auquel il est lié
  Visible.a
  ; Action & condition
  Cond.StCondition
  Action.StAction; je peux passer par un tableau ou une liste si besoin
  List actionToDo.StAction()
EndStructure
Global Dim teleporter.StTeleporter(0); on crée 2 teleporter
;}

;{ procedures

Procedure AddMob(i=0,visible=0,eventid= 1,cond=#Cond_IsDead,target=4,tid=1,actVal=1,act=#ACTION_ChangeCompteur1)
  With mob(i)
    \vie = 80
    \Id = i
    \EventId = eventid
    \visible = visible
    \Cond\conditionType[0] = cond 
    \Cond\conditionValueMax[0] = 1
    \Cond\conditionValueMax[1] = 1
    \Action[0]\actionType = act
    \Action[0]\actionValue = actVal
    \Action[0]\targetObjet = target
    \Action[0]\targetId = tid
  EndWith
EndProcedure

Procedure AddTeleporter(i=0,visible=0,eventid= 1,cond=#Cond_Compteur1Egal,condval = 4,target=7,tid=1,actVal=1,act=#ACTION_Nothing)
  With teleporter(i)
    \Id = i
    \EventId = eventid
    \Cond\conditionType[0] = cond
    \cond\conditionValueMax[0] = condval
    \Action\targetId = tid
  EndWith  
EndProcedure

Procedure DoActions()
  Protected ok.a
  ok = 1; juste pour sortir d ela boucle :)
  ;{ mob
  For i =0 To 8
    With mob(i)
      If \visible = 1
        If \vie > 0
          \vie -Random(20)
          Debug "Vie Du Mob N° "+Str(\id) +" : "+Str(\vie) 
        Else
          \Cond\conditionValueCurrent[0] = \Cond\conditionValueMax[0]
        EndIf
        For a = 0 To 1
          If \Cond\conditionValueCurrent[a] = \Cond\conditionValueMax[a]
            Debug "le mob "+Str(\Id)+" a une condition remplie, la "+Str(a)
            Select \Cond\conditionType[a]
                
              Case #Cond_IsDead 
                Debug "condition : le mob est mort"
                ClearList(\actionToDo())
                Select \Action[a]\actionType
                    
                  Case #ACTION_ChangeCompteur1
                    Debug "action : on change le compteur1"
                    For u = 0 To 1                                            
                      If teleporter(u)\EventId = \Action[a]\targetId                          
                        teleporter(u)\Cond\conditionValueCurrent[0]+1
                        Break
                      EndIf                        
                    Next 
                    
                  Case #ACTION_ChangeCompteur2
                                        
                  Case #ACTION_ChangeCompteur3
                    
                  Default
                    
                EndSelect
                \Visible= 0
                Break
                
              Case #Cond_VariableEgal
                
            EndSelect
          EndIf
        Next a
        
        ForEach \actionToDo()    
          Select \actionToDo()\actionType  
              
            Case #ACTION_MOVE_TO
              
            Case #ACTION_SEARCH_AND_DESTROY 
              
            Default
              ;nothing
              
          EndSelect
        Next 
      EndIf
    EndWith
  Next i
  ;}
  ;{ teleporter
  For i = 0 To 1
    With teleporter(i)
      If teleporter(i)\Cond\conditionValueCurrent[0] >=  teleporter(i)\Cond\conditionValueMax[0] 
        Debug "Condition 1teleporter "+Str(i)+" est remplie. On le rend visible !"
        teleporter(i)\Visible = 1
        teleporter(i)\Cond\conditionValueMax[0] +1         
      EndIf   
      If \visible = 1 
        If \Action\actionValue = 0
          Debug "VALEUR : " +Str(\Action\actionValue)
          Debug "Le teleporter "+Str(\id) + " est Visible."          
          For a = 0 To 8             
            If mob(a)\EventId = \Action\targetId
              mob(a)\Visible = 1
              Debug "On rend le mod visible"
            EndIf             
          Next a
          \Action\actionValue +1
        EndIf        
      ElseIf \visible = 0
        ok = 0
      EndIf
    EndWith    
  Next
  If ok = 1
    quit = 1
    Debug "les 2 téléporteurs sont visibles, le level est terminé ! "
    ProcedureReturn quit
  EndIf  
  ;}  
EndProcedure
;}

;{ ajout d'objet (mob et teleporter) pour tester
ReDim mob(8)
For i=0 To 4
  AddMob(i,1)
Next i
n=5
For i=0 To 3
  AddMob(i+n,0,2,#Cond_IsDead,4,2)
Next i
; teleporters
AddTeleporter(0,0,1,#Cond_Compteur1Egal,5,7,2)
ReDim teleporter(1)
AddTeleporter(1,0,2,#Cond_Compteur1Egal,4,7,0)
;}

Repeat
  Delay(20)   
  quit = DoActions()
Until quit = 1

Re: [jeu] gérer des évènements

Publié : lun. 21/mai/2012 12:11
par G-Rom
Le parcour de liste pour trouver un element est a eviter. A proscrire meme. Tu auras des perfs mediocre.
Je vois plus les choses comme ca :
Structure evenement

*from.i
*to.i

Condition.i
Action.i

Endstructure
Tu peut completer les champs. Avec une liste globale & une map tu pourras retrouver toutes les infos que tu veut sans un seul bloc foreach/next.

Re: [jeu] gérer des évènements

Publié : lun. 21/mai/2012 12:36
par blendman
G-Rom a écrit :Le parcour de liste pour trouver un element est a eviter. A proscrire meme. Tu auras des perfs mediocre.
je suis bien d'accord, c'est pour ça que je cherche une technique pour l'éviter ^^.
Je vois plus les choses comme ca :
Structure evenement
*from.i
*to.i
Condition.i
Action.i
Endstructure
Tu peut completer les champs. Avec une liste globale & une map tu pourras retrouver toutes les infos que tu veut sans un seul bloc foreach/next.
heu, je n'ai pas du tout compris ce que je dois faire en fait :).
Tu aurais un petit exemple fonctionnel ?