Ceux qui n'aime la POO je le comprend et le respectes, mais cet article n'est pas pour vous .
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...
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.
; 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())
; ****************************************************
; 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
; ****************************************************
; 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
; ****************************************************
; 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
; ****************************************************
; 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
; ****************************************************
; 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