[TUTO Avancé] Poo héritage multiple

Informations pour bien débuter en PureBasic
Avatar de l’utilisateur
microdevweb
Messages : 1798
Inscription : mer. 29/juin/2011 14:11
Localisation : Belgique

[TUTO Avancé] Poo héritage multiple

Message par microdevweb »

Ceux qui n'aime la POO je le comprend et le respectes, mais cet article n'est pas pour vous :mrgreen: .

Pour les autres, voici une technique pour faire de beaux héritages multiples. Même si Pb n'est pas orienté objet et je ne vais pas remettre le couvert il y a moyen des faire des trucs vraiment sympa.

Voici en exemple super commenté d'une petite gestion de personnages pour un jeu 2d. Ce n'est que le modèle je ne crée pas les sprites etc...

Tout le code est ICI

Ce code reprend 2 modules et 4 classes.
  • MODULE GameObject
    • CLASS GameObject
  • MODULE People
    • CLASS People
    • CLASS Player
    • CLASS Enemy
Les héritages
  • People <- GameObject
  • Player <- People
  • Enemy <- People
Si par après on voulais faire des décors, pièges etc... ils hériteraient de GameObject

Les codes
Main.pbi Beaucoup d'entre vous dirons que cela fait pal mal de code, et que cela complique pour rien. Mais ce genre de système à l'avantage d'être facile à maintenir et très simple à faire évolué.

Maintenant le style Poo on aime ou on n'aime pas c'est une question de goût.
Dernière modification par microdevweb le jeu. 30/août/2018 15:18, modifié 3 fois.
Windows 10 64 bits PB: 5.70 ; 5.72 LST
Work at Centre Spatial de Liège
Avatar de l’utilisateur
microdevweb
Messages : 1798
Inscription : mer. 29/juin/2011 14:11
Localisation : Belgique

Re: [TUTO Avancé] Poo héritage multiple

Message par microdevweb »

Main

Code : Tout sélectionner

; Tutoriel Po avec héritage multiple et méthodes abstraites
; Auteur : MicrodevWeb
; Exemple : Un jeu 2d, avec gestion de personnages et decors

; Remarque : Les méthodes ne renverons qu'un debug, 
; je ne montre que le mécanique objet

IncludePath "Modules\"
; Nous créons un module qui contiendra une classe gameobject 
; qui servira pour tous les composants du jeu. 
; Les modules ne pouvant communiquer qu'avec d'autres modules 
; il est indispensable de placer cette classe abstraite dans un Module.
XIncludeFile "GameObject.pbi"
; Nous créons un module People qui contiendra les classes 
; People,Player,Enemy 
; People héritera de Gameobject 
; Player et Enemy hériterons de People. 
; Il est évident que l'on pourrais créer d'autres enemis 
; qui hériteraient de Enemy etc..
XIncludeFile "People.pbi"
; on vas le code ICI

UseModule People
Global.Player myPlayer = newPlayer(10,10)
Global.Enemy solder = newEnemy(200,10),blackMage = newEnemy(500,10)
; *************************************
; TESTE DU PLAYER
; *************************************
; On teste les position initial
Debug "CURRENT POS OF PEOPLE"
Debug "PLAYER : X : "+Str(myPlayer\getPosX())+" Y : "+Str(myPlayer\getPosY())
Debug "SOLDER : X : "+Str(solder\getPosX())+" Y : "+Str(solder\getPosY())
Debug "BLACK MAGE : X : "+Str(blackMage\getPosX())+" Y : "+Str(blackMage\getPosY())
; on fait marcher le player vers la droite 
myPlayer\walk()
Debug "AFTER WALK TOO RIGHT:"
Debug "PLAYER : X : "+Str(myPlayer\getPosX())+" Y : "+Str(myPlayer\getPosY())
; on fait marcher le player vers la gauche 
myPlayer\setDirection(People::#LOOK_TO_LEFT)
myPlayer\walk()
Debug "AFTER WALK TOO LEFT:"
Debug "PLAYER : X : "+Str(myPlayer\getPosX())+" Y : "+Str(myPlayer\getPosY())
; on fait courrir le player vers la droite
myPlayer\setDirection(People::#LOOK_TO_RIGHT)
myPlayer\run()
Debug "AFTER RUN TOO RIGHT:"
Debug "PLAYER : X : "+Str(myPlayer\getPosX())+" Y : "+Str(myPlayer\getPosY())
; *************************************
; TESTE DU SOLDAT
; *************************************
; on fait marcher le SOLDAT vers la droite 
solder\walk()
Debug "AFTER WALK TOO RIGHT:"
Debug "SOLDAT : X : "+Str(solder\getPosX())+" Y : "+Str(solder\getPosY())
; on fait marcher le SOLDAT vers la gauche 
solder\setDirection(People::#LOOK_TO_LEFT)
solder\walk()
Debug "AFTER WALK TOO LEFT:"
Debug "SOLDAT : X : "+Str(solder\getPosX())+" Y : "+Str(solder\getPosY())
; on fait courrir le SOLDAT vers la droite
solder\setDirection(People::#LOOK_TO_RIGHT)
solder\run()
Debug "AFTER RUN TOO RIGHT:"
Debug "SOLDAT : X : "+Str(solder\getPosX())+" Y : "+Str(solder\getPosY())
; *************************************
; TESTE DU MAGE NOIR
; *************************************
; on fait marcher le MAGE NOIR vers la droite 
blackMage\walk()
Debug "AFTER WALK TOO RIGHT:"
Debug "MAGE NOIR : X : "+Str(blackMage\getPosX())+" Y : "+Str(blackMage\getPosY())
; on fait marcher le MAGE NOIR vers la gauche 
blackMage\setDirection(People::#LOOK_TO_LEFT)
blackMage\walk()
Debug "AFTER WALK TOO LEFT:"
Debug "MAGE NOIR : X : "+Str(blackMage\getPosX())+" Y : "+Str(blackMage\getPosY())
; on fait courrir le MAGE NOIR vers la droite
blackMage\setDirection(People::#LOOK_TO_RIGHT)
blackMage\run()
Debug "AFTER RUN TOO RIGHT:"
Debug "MAGE NOIR : X : "+Str(blackMage\getPosX())+" Y : "+Str(blackMage\getPosY())

Windows 10 64 bits PB: 5.70 ; 5.72 LST
Work at Centre Spatial de Liège
Avatar de l’utilisateur
microdevweb
Messages : 1798
Inscription : mer. 29/juin/2011 14:11
Localisation : Belgique

Re: [TUTO Avancé] Poo héritage multiple

Message par microdevweb »

Code : Tout sélectionner

; ****************************************************
; AUTHOR  : MicrodevWeb
; MODULE  : GameObject
; CLASS   : GameObject
; ****************************************************

DeclareModule GameObject
  ; Pour pouvoir hériter de cette classe, la Structure doit être publique 
  ; cependant par convention nous ferons précéder tous les éléments 
  ; qui ne doivent pas être directement utilisé par 
  ; l'utilisateur final d'un "_"
  Structure _pos
    posX.l
    posY.l
    width.l
    height.l
  EndStructure
  Structure _velocity
    velocityX.l
    velocityY.l
  EndStructure
  ; Nous allons créer un prototype de fonction 
  ; qui sera en quelque sorte une (méthode abstraite)
  ; ce qui veut dire que chaque classe fille décrira sa propre méthode
  Prototype _gameObject_build(*this)
  Structure _gameObject
    *methods   ; Ici seront stockées toutes les méthodes publiques
    myPos._pos ; Chaque gameobjet aura une position x,y,w,h
    sprite.l   ; Chacun game objet aura un sprite
    myVelocity._velocity ; Chacun game objet aura un velocité
                         ; c'est ce qui permettra de faire
                         ; bouger l'objet
    build._gameObject_build ; on stockera ici l'adresse de la procédure
                            ; qui sera utilisé
  EndStructure
  
  Interface GameObject
    getPosX()
    setPosX(x)
    getPosY()
    setPosY(y)
    getwidth()
    setWidth(width)
    getHeigth()
    setHeight(height)
    getVelocityX()
    setVelocityX(velocityX)
    getVelocityY()
    setVelocityY(velocityY)
    getSprite()
    setSprite(sprite)
  EndInterface
  Declare _gameObject_super(*this._gameObject,Array *daugtherMth(1),Array daugtherMethLenght(1))
EndDeclareModule
Module GameObject
  EnableExplicit
  ; ici nous allons créer tous les getters et setters
  ; il est préférable de ne pas accéder directement aux attributs
  ; Car imaginons que la valeur doit par exemple subir 
  ; un teste ou un calcul; nous ferons cela ici 
  ; et cela affectera tout le code qui utilise cette classe
  ; Remarque : ces méthodes serons accesiblent depuis n'importe quel
  ; classe filles
  ;-* GETTERS & SETTERS
  Procedure _gameObject_getX(*this._gameObject)
    With *this\myPos
      ProcedureReturn \posX
    EndWith
  EndProcedure
  
  Procedure _gameObject_setX(*this._gameObject,x.l)
    With *this\myPos
      \posX = x
    EndWith
  EndProcedure
  
  Procedure _gameObject_getY(*this._gameObject)
    With *this\myPos
      ProcedureReturn \posY
    EndWith
  EndProcedure
  
  Procedure _gameObject_setY(*this._gameObject,Y.l)
    With *this\myPos
      \posY = y
    EndWith
  EndProcedure
  
  Procedure _gameObject_getwidth(*this._gameObject)
    With *this\myPos
      ProcedureReturn \width
    EndWith
  EndProcedure
  
  Procedure _gameObject_setWidth(*this._gameObject,width.l)
    With *this\myPos
      \width = width
    EndWith
  EndProcedure
  
  Procedure _gameObject_getHeigth(*this._gameObject)
    With *this\myPos
      ProcedureReturn \height
    EndWith
  EndProcedure
  
  Procedure _gameObject_setHeight(*this._gameObject,heigth.l)
    With *this\myPos
      \height = heigth
    EndWith
  EndProcedure
  
  Procedure _gameObject_getVelocityX(*this._gameObject)
    With *this\myVelocity
      ProcedureReturn \velocityX
    EndWith
  EndProcedure
  
  Procedure _gameObject_setVelocityX(*this._gameObject,velocityX.l)
    With *this\myVelocity
      \velocityX = velocityX
    EndWith
  EndProcedure
  
  Procedure _gameObject_getVelocityY(*this._gameObject)
    With *this\myVelocity
      ProcedureReturn \velocityY
    EndWith
  EndProcedure
  
  Procedure _gameObject_setVelocityY(*this._gameObject,velocityY.l)
    With *this\myVelocity
      \velocityY = velocityY
    EndWith
  EndProcedure
  
  Procedure _gameObject_getSprite(*this._gameObject)
    With *this
      ProcedureReturn \sprite
    EndWith
  EndProcedure
  
  Procedure _gameObject_setSprite(*this._gameObject,sprite.l)
    With *this
      \sprite = sprite
    EndWith
  EndProcedure
  ;}
  ; Le super constructeur, 
  ; il va servir à empiler les adresses de méthodes dans la même variable (*méthode) 
  ; nous utiliserons pour faire des tableaux
  Procedure _gameObject_super(*this._gameObject,Array *daugtherMth(1),Array daugtherMethLenght(1))
    With *this
      ; En premier nous prenons la taille des procédures de (Game Object)
      Protected l = ?E_gameObject - ?S_gameObject
      ; nous lui ajoutons les taille des procédures filles
      ; avec une boucle
      Protected n
      For n = 0 To ArraySize(daugtherMethLenght())-1
        l + daugtherMethLenght(n)
      Next
      ; nous allons l'espace nécessaire
      \methods = AllocateMemory(l)
      ; Maintenant nous allons empiler les adresses
      ; en premier les adresses de GameObject
      MoveMemory(?S_gameObject,\methods,?E_gameObject - ?S_gameObject)
      ; ensuite les adresses des filles
      ; --> on mémorise la taille des adresses de GameObject pour bien positionner le pointeur
      l = ?E_gameObject - ?S_gameObject
      For n = 0 To ArraySize(daugtherMethLenght())-1
        If n > 0 ; héritage multiple
                 ; on ajoute la taille des adresses de la fille précédente
          l + daugtherMethLenght(n -1)
        EndIf
        ; on empile les adresses des filles
        MoveMemory(*daugtherMth(n),\methods + l,daugtherMethLenght(n))
      Next
    EndWith
  EndProcedure
  
  ; Ici nous allons copier les adresses des procédures publiques. 
  ; Il est impératif de respecter le même ordre pour l'interface
  DataSection
    S_gameObject:
    Data.i @_gameObject_getX()
    Data.i @_gameObject_setX()
    Data.i @_gameObject_getY()
    Data.i @_gameObject_setY()
    Data.i @_gameObject_getwidth()
    Data.i @_gameObject_setWidth()
    Data.i @_gameObject_getHeigth()
    Data.i @_gameObject_setHeight()
    Data.i @_gameObject_getVelocityX()
    Data.i @_gameObject_setVelocityX()
    Data.i @_gameObject_getVelocityY()
    Data.i @_gameObject_setVelocityY()
    Data.i @_gameObject_getSprite()
    Data.i @_gameObject_setSprite()
    E_gameObject:
  EndDataSection
EndModule
Windows 10 64 bits PB: 5.70 ; 5.72 LST
Work at Centre Spatial de Liège
Avatar de l’utilisateur
microdevweb
Messages : 1798
Inscription : mer. 29/juin/2011 14:11
Localisation : Belgique

Re: [TUTO Avancé] Poo héritage multiple

Message par microdevweb »

Code : Tout sélectionner

; ****************************************************
; AUTHOR  : MicrodevWeb
; MODULE  : People
; ****************************************************
DeclareModule People
  ; sera utilisé par People
  Enumeration 
    #LOOK_TO_RIGHT
    #LOOK_TO_LEFT
  EndEnumeration
  
  Interface __people Extends GameObject::GameObject
    getWalkSpeed()
    setWalkSpeed(speed)
    getRunSpeed()
    setRunSpeed(speed)
    getDirection()
    setDirection(direction)
    walk()
    run()
    stop()
  EndInterface
  Interface Player Extends __people
    ; vide dans le cadre de ce tuto
    ; même vide il vaut mieu créer l'interface
    ; car si on veux ajouté des fonctions 
    ; c'est assez simple une fois
    ; la base posée
  EndInterface
  Interface Enemy Extends __people
    ; vide dans le cadre de ce tuto
    ; même vide il vaut mieu créer l'interface
    ; car si on veux ajouté des fonctions 
    ; c'est assez simple une fois
    ; la base posée
  EndInterface
  Declare newEnemy(x,y)
  Declare newPlayer(x,y)
EndDeclareModule

Module People
  EnableExplicit
  ; Nous allons ici importer toutes nos classes
  IncludePath "PeopleClasses\"
  ; Classes abstraite People
  XIncludeFile "People.pbi"
  ; Classes Player hérite de People
  XIncludeFile "Player.pbi"
  ; Classes Enemy hérite de People
  XIncludeFile "Enemy.pbi"
  
EndModule
Windows 10 64 bits PB: 5.70 ; 5.72 LST
Work at Centre Spatial de Liège
Avatar de l’utilisateur
microdevweb
Messages : 1798
Inscription : mer. 29/juin/2011 14:11
Localisation : Belgique

Re: [TUTO Avancé] Poo héritage multiple

Message par microdevweb »

Code : Tout sélectionner

; ****************************************************
; AUTHOR  : MicrodevWeb
; MODULE  : People
; CLASS   : People
; ****************************************************
; chaque personnage peut marcher,courrir
; normalement suater aussi, mais dans le cadre de ce tuto
; je ne l'implémenterais pas
; on pourra appelé une péthode protégée de la classe fille
Prototype _people_protected(*this)

Structure _people Extends GameObject::_gameObject
  walkSpeed.l             ; vitesse de marche
  runSpeed.l              ; vitesse de course
  direction.l             ; la direction du personnage
  walk._people_protected  ; méthode protégée ou vide
  run._people_protected   ; méthode protégée ou vide
  stop._people_protected  ; méthode protégée ou vide
EndStructure
; on créer ici les méthodes
;-* GETTERS & SETTER
Procedure _people_getWalkSpeed(*this._people)
  With *this
    ProcedureReturn \walkSpeed
  EndWith
EndProcedure

Procedure _people_setWalkSpeed(*this._people,walkSpeed)
  With *this
    \walkSpeed = walkSpeed
  EndWith
EndProcedure

Procedure _people_getRunSpeed(*this._people)
  With *this
    ProcedureReturn \runSpeed
  EndWith
EndProcedure

Procedure _people_setRunSpeed(*this._people,runSpeed)
  With *this
    \runSpeed = runSpeed
  EndWith
EndProcedure

Procedure _people_getDirection(*this._people)
  With *this
    ProcedureReturn \direction
  EndWith
EndProcedure

Procedure _people_setDirection(*this._people,direction)
  With *this
    \direction = direction
  EndWith
EndProcedure

;}
;-* PUBLIC METHOD
Procedure _people_walk(*this._people)
  With *this
    ; pour pouvoir utiliser les setters je vais créer une 
    ; variable temporaire
    Protected GO.GameObject::GameObject = *this
    Select \direction
      Case People::#LOOK_TO_RIGHT
        GO\setVelocityX(\walkSpeed)
      Case People::#LOOK_TO_LEFT
        GO\setVelocityX(-\walkSpeed)
    EndSelect
    ; pour le teste je change la position ici
    ; en situation réel on la chagerais dans la boucle de jeux
    GO\setPosX(GO\getPosX() + GO\getVelocityX())
    ; j'appelles la méthodes protégée si elle à été définie
    If \walk
      \walk(*this)
    EndIf
  EndWith
EndProcedure

Procedure _people_run(*this._people)
  With *this
    ; pour pouvoir utiliser les setters je vais créer une 
    ; variable temporaire
    Protected GO.GameObject::GameObject = *this
    Select \direction
      Case People::#LOOK_TO_RIGHT
        GO\setVelocityX(\runSpeed)
      Case People::#LOOK_TO_LEFT
        GO\setVelocityX(-\runSpeed)
    EndSelect
    ; pour le teste je change la position ici
    ; en situation réel on la chagerais dans la boucle de jeux
    GO\setPosX(GO\getPosX() + GO\getVelocityX())
    ; j'appelles la méthodes protégée si elle à été définie
    If \run
      \run(*this)
    EndIf
  EndWith
EndProcedure

Procedure _people_stop(*this._people)
  With *this
    ; pour pouvoir utiliser les setters je vais créer une 
    ; variable temporaire
    Protected setter.GameObject::GameObject = *this
    setter\setVelocityX(0)
    ; j'appelles la méthodes protégée si elle à été définie
    If \stop
      \stop(*this)
    EndIf
  EndWith
EndProcedure
;}
;-* SUPER CONSTRUCTOR
Procedure _people_super(*this._people,*daugtherMth,daugtherMehLength)
  With *this
    ; on crée les tableaux
    Protected Dim *mth(2),Dim mthLength(2)
    ; on place en premier les adresses et taille de people
    *mth(0) = ?S_people
    mthLength(0) = ?E_people - ?S_people
    ; ensuite les adresses et taille de la classe fille
    *mth(1) = *daugtherMth
    mthLength(1) = daugtherMehLength
    ; enfin on appelle les super constructeur de GameObject
    GameObject::_gameObject_super(*this,*mth(),mthLength())
  EndWith
EndProcedure

; on place toutes les adresses en data
DataSection
  S_people:
  Data.i @_people_getWalkSpeed()
  Data.i @_people_setWalkSpeed()
  Data.i @_people_getRunSpeed()
  Data.i @_people_setRunSpeed()
  Data.i @_people_getDirection()
  Data.i @_people_setDirection()
  Data.i @_people_walk()
  Data.i @_people_run()
  Data.i @_people_stop()
  E_people:
EndDataSection

Windows 10 64 bits PB: 5.70 ; 5.72 LST
Work at Centre Spatial de Liège
Avatar de l’utilisateur
microdevweb
Messages : 1798
Inscription : mer. 29/juin/2011 14:11
Localisation : Belgique

Re: [TUTO Avancé] Poo héritage multiple

Message par microdevweb »

Code : Tout sélectionner

; ****************************************************
; AUTHOR  : MicrodevWeb
; MODULE  : People
; CLASS   : Player extends of People
; ****************************************************
Structure _player Extends _people
  ; dans le cadre du tuto se sera vide
  ; mais ajoute ce que l'on veux
EndStructure
;-* PROTECTED METHOD
Procedure _player_walk(*this._player)
  With *this
    Debug "START WALK ANIMATION OF PLAYER "+Str(*this)
  EndWith
EndProcedure

Procedure _player_run(*this._player)
  With *this
    Debug "START RUN ANIMATION OF PLAYER "+Str(*this)
  EndWith
EndProcedure

Procedure _player_stop(*this._player)
  With *this
    Debug "START STOP ANIMATION OF PLAYER "+Str(*this)
  EndWith
EndProcedure

;}
;-* CONSTRUCTOR
Procedure newPlayer(x,y)
  ; on va allouer la structure
  Protected *this._player = AllocateStructure(_player)
  With *this
    ; j'appelle le super contructeur de People
    _people_super(*this,?S_player,?E_player - ?S_player)
    ; variable temporaire pour utiliser les setter de GameObject
    ; Note : on pourrait passer le valeur directement mais
    ; pour garder le principe  d'encapsulation c'est mieux de procéder
    ; ainsi
    Protected GO.GameObject::GameObject = *this
    GO\setPosX(x)
    GO\setPosY(y)
    GO\setwidth(64) ; j'aurais peu demander la valeru dans la procédure
    GO\setHeight(64)
    ; idem pour People
    Protected PE.People::__people = *this
    PE\setWalkSpeed(2)
    PE\setRunSpeed(4)
    ; je renseigne mes methodes protégées (ce n'est pas obligatoire
    ; is on ne veux pas de méthode protégée pour une action définie)
    \walk = @_player_walk()
    \run = @_player_run()
    \stop = @_player_stop()
    
    ProcedureReturn *this
  EndWith
EndProcedure
;}

DataSection
  S_player:
  ; vide dans le cadre de ce tuto
  E_player:
EndDataSection
Windows 10 64 bits PB: 5.70 ; 5.72 LST
Work at Centre Spatial de Liège
Avatar de l’utilisateur
microdevweb
Messages : 1798
Inscription : mer. 29/juin/2011 14:11
Localisation : Belgique

Re: [TUTO Avancé] Poo héritage multiple

Message par microdevweb »

Code : Tout sélectionner

; ****************************************************
; AUTHOR  : MicrodevWeb
; MODULE  : People
; CLASS   : Enemy extends of People
; ****************************************************
Structure _enemy Extends _people
  ; dans le cadre du tuto se sera vide
  ; mais ajoute ce que l'on veux
EndStructure
;-* PROTECTED METHOD
Procedure _enemy_walk(*this._enemy)
  With *this
    Debug "START WALK ANIMATION OF ENEMY "+Str(*this)
  EndWith
EndProcedure

Procedure _enemy_run(*this._enemy)
  With *this
    Debug "START RUN ANIMATION OF ENEMY "+Str(*this)
  EndWith
EndProcedure

Procedure _enemy_stop(*this._enemy)
  With *this
    Debug "START STOP ANIMATION OF ENEMY "+Str(*this)
  EndWith
EndProcedure

;}
;-* CONSTRUCTOR
Procedure newEnemy(x,y)
  ; on va allouer la structure
  Protected *this._enemy = AllocateStructure(_enemy)
  With *this
    ; j'appelle le super contructeur de People
    _people_super(*this,?S_enemy,?E_enemy - ?S_enemy)
    ; variable temporaire pour utiliser les setter de GameObject
    ; Note : on pourrait passer le valeur directement mais
    ; pour garder le principe  d'encapsulation c'est mieux de procéder
    ; ainsi
    Protected GO.GameObject::GameObject = *this
    GO\setPosX(x)
    GO\setPosY(y)
    GO\setwidth(64) ; j'aurais peu demander la valeru dans la procédure
    GO\setHeight(64)
    ; idem pour People
    Protected PE.People::__people = *this
    PE\setWalkSpeed(1)
    PE\setRunSpeed(3)
    ; je renseigne mes methodes protégées (ce n'est pas obligatoire
    ; is on ne veux pas de méthode protégée pour une action définie)
    \walk = @_enemy_walk()
    \run = @_enemy_run()
    \stop = @_enemy_stop()
    ProcedureReturn *this
  EndWith
EndProcedure
;}

DataSection
  S_enemy:
  ; vide dans le cadre de ce tuto
  E_enemy:
EndDataSection
Windows 10 64 bits PB: 5.70 ; 5.72 LST
Work at Centre Spatial de Liège
Répondre