Page 1 sur 1

Apprendre à gérer les collisions

Publié : ven. 11/janv./2008 13:44
par Octavius
Bonjour,

Voilà mon objectif est de réaliser un jeu de plate-forme simple (oui je sais c'est pas ce qu'il y a de plus facile pour un débutant!). Pour cela je dois apprendre à gérer les collisions, mais c'est pas facile...

J'ai essayé de faire un code, mais ça ne marche pas toujours très bien.

Mon problème est que de temps à autres certaines boîtes se pénètrent sans collision et je ne sais pas pourquoi ? Il m'arrive carrément de voir parfois des boîtes se téléporter en dehors de la cage après une collision avec la paroi ?!?

Est-ce vous pourriez aussi m'aider à mieux commenter et mieux organiser mon code ? Etant novice je ne sais pas vraiment comment m'y prendre avec ce problème...

Pour faire fonctionner mon code, vous avez besoin de créer dans le même dossier trois petits fichiers bitmap :
- Box4.bmp : une ligne horizontale épaisse grise 600*5
- Box6.bmp : une ligne verticale épaisse grise 5*600
- Box7.bmp : un carré plein orange de 30*30

J'ai essayé de représenter les molécules d'un gaz enfermé dans une boîte. Pour ajouter la pesanteur il suffit de donner une valeur positive à la variable ay pour chacune des boîtes.

Code : Tout sélectionner

Enumeration
#win  ;prévu pour un mode fenêtré
#box1 ;liste des objets
#box2
#box3
#box4
#box5
#box6
#box7
#box8
#box9
#box10
#box11
#box12
#box13
#box14
#box15
#box16
#box17
#box18
#box19
#box20
EndEnumeration

;Initialisations principales
If InitSprite()=0 Or InitKeyboard()=0 Or InitMouse()=0 Or OpenScreen(1280,800,32,"Collision!")=0
MessageRequester("Erreur","Impossible de démarrer le programme.")
End
EndIf

;Liste des paramètres
Structure Param
name$    ;nom de l'objet
type$    ;type d'objet
sprite.l ;sprite de l'objet
width.l  ;largeur
height.l ;hauteur
x.f      ;position x
y.f      ;position y
newx.f   ;nouvelle position x (utilité transitoire)
newy.f   ;nouvelle position y (utilité transitoire)
vx.f     ;vitesse x
vy.f     ;vitesse y
newvx.f  ;nouvelle vitesse x (utilité transitoire)
newvy.f  ;nouvelle vitesse y (utilité transitoire)
ax.f     ;accélération x
ay.f     ;accélération y (pesanteur)
mass.f   ;masse de l'objet
mob.b    ;objet mobile ou non
flex.b   ;flexibilité (contrainte sur les chocs inélastiques)
EndStructure

;Initialisation de la liste chaînée des objets
Global NewList Thing.Param()
Global time.l,dt.f,max.f

With Thing()

Procedure Loading()

LoadSprite(#box1,"Box4.bmp")
AddElement(Thing())
\name$="sol"
\type$="objet"
\sprite=#box1
\width=SpriteWidth(#box1)
\height=SpriteHeight(#box1)
\x=345
\y=705
\newx=0
\newy=0
\vx=0
\vy=0
\newvx=0
\newvy=0
\ax=0
\ay=0
\mass=1000
\mob=0
\flex=0

LoadSprite(#box2,"Box4.bmp")
AddElement(Thing())
\name$="sol"
\type$="objet"
\sprite=#box2
\width=SpriteWidth(#box2)
\height=SpriteHeight(#box2)
\x=345
\y=100
\newx=0
\newy=0
\vx=0
\vy=0
\newvx=0
\newvy=0
\ax=0
\ay=0
\mass=1000
\mob=0
\flex=0

LoadSprite(#box3,"Box6.bmp")
AddElement(Thing())
\name$="sol"
\type$="objet"
\sprite=#box3
\width=SpriteWidth(#box3)
\height=SpriteHeight(#box3)
\x=340
\y=105
\newx=0
\newy=0
\vx=0
\vy=0
\newvx=0
\newvy=0
\ax=0
\ay=0
\mass=1000
\mob=0
\flex=0

LoadSprite(#box4,"Box6.bmp")
AddElement(Thing())
\name$="sol"
\type$="objet"
\sprite=#box4
\width=SpriteWidth(#box4)
\height=SpriteHeight(#box4)
\x=945
\y=105
\newx=0
\newy=0
\vx=0
\vy=0
\newvx=0
\newvy=0
\ax=0
\ay=0
\mass=1000
\mob=0
\flex=0

For i=#box5 To #box12
LoadSprite(i,"Box7.bmp")
AddElement(Thing())
\name$="boîte n°"+Str(i-#box4)
\type$="objet"
\sprite=i
\width=SpriteWidth(i)
\height=SpriteHeight(i)
\x=360+(i-#box4)*60
\y=140+(i-#box4)*60
\newx=0
\newy=0
\vx=Random(500)-250
\vy=Random(500)-250
\newvx=0
\newvy=0
\ax=0
\ay=0
\mass=4/9
\mob=1
\flex=0
Next

For i=#box13 To #box20
LoadSprite(i,"Box7.bmp")
AddElement(Thing())
\name$="boîte n°"+Str(i-#box4)
\type$="objet"
\sprite=i
\width=SpriteWidth(i)
\height=SpriteHeight(i)
\x=910-(i-#box12)*60
\y=140+(i-#box12)*60
\newx=0
\newy=0
\vx=Random(500)-250
\vy=Random(500)-250
\newvx=0
\newvy=0
\ax=0
\ay=0
\mass=4/9
\mob=1
\flex=0
Next

EndProcedure

Procedure MoveOb()
Protected index1.l,index2.l,name1$,name2$,type1$,type2$,sprite1.l,sprite2.l
Protected width1.l,width2.l,height1.l,height2.l
Protected x1.f,x2.f,y1.f,y2.f,newx1.f,newx2.f,newy1.f,newy2.f,posx1.f,posx2.f,posy1.f,posy2.f
Protected vx1.f,vx2.f,newvx1.f,newvx2.f,vy1.f,vy2.f,newvy1.f,newvy2.f,Ux.f,Uy.f,ax1.f,ax2.f,ay1.f,ay2.f
Protected mass1.f,mass2.f,M.f,mob1.b,mob2.b,flex1.b,flex2.b,xcol.b,ycol.b,dis.f

ResetList(Thing())
Repeat
NextElement(Thing())
\newx=\x+\vx*dt+\ax*dt*dt/2
\newy=\y+\vy*dt+\ay*dt*dt/2
\newvx=\vx+\ax*dt
\newvy=\vy+\ay*dt
Until CountList(Thing())-1=ListIndex(Thing())

index1=0
Repeat
SelectElement(Thing(),index1)
name1$=\name$
type1$=\type$
sprite1=\sprite
width1=\width
height1=\height
x1=\x
y1=\y
newx1=\newx
newy1=\newy
vx1=\vx
vy1=\vy
newvx1=\newvx
newvy1=\newvy
ax1=\ax
ay1=\ay
mass1=\mass
mob1=\mob
flex1=\flex

index2=index1+1
Repeat
SelectElement(Thing(),index2)
name2$=\name$
type2$=\type$
sprite2=\sprite
width2=\width
height2=\height
x2=\x
y2=\y
newx2=\newx
newy2=\newy
vx2=\vx
vy2=\vy
newvx2=\newvx
newvy2=\newvy
ax2=\ax
ay2=\ay
mass2=\mass
mob2=\mob
flex2=\flex

M=mass1+mass2
Ux=Abs(vx1)+Abs(vx2)
Uy=Abs(vy1)+Abs(vy2)

xcol=0
ycol=0

If SpriteCollision(sprite1,newx1,y1,sprite2,newx2,y2)<>0
  If x1<x2
    dis=x2-x1-width1
    Else
    dis=x1-x2-width2
  EndIf
  posx1=Int(x1+dis*vx1/Ux)
  posx2=Int(x2+dis*vx2/Ux)
  If x1<x2
    dis=posx2-posx1-width1
    Else
    dis=posx1-posx2-width2
  EndIf
  If dis<>0
    xcol=0
    Else
    xcol=1
    newx1=posx1
    newx2=posx2
  EndIf
  ElseIf SpriteCollision(sprite1,x1,newy1,sprite2,x2,newy2)<>0
  If y1<y2
    dis=y2-y1-height1
    Else
    dis=y1-y2-height2
  EndIf
  posy1=Int(y1+dis*vy1/Uy)
  posy2=Int(y2+dis*vy2/Uy)
  If y1<y2
    dis=posy2-posy1-height1
    Else
    dis=posy1-posy2-height2
  EndIf
  If dis<>0
    ycol=0
    Else
    ycol=1
    newy1=posy1
    newy2=posy2
  EndIf
EndIf

If xcol=1
  If flex1=0 And flex2=0
    newvx1=((mass1-mass2)*vx1+2*mass2*vx2)/M
    newvx2=((mass2-mass1)*vx2+2*mass1*vx1)/M
    Else
    newvx1=(mass1*vx1+mass2*vx2)/M
    newvx2=newvx1
  EndIf
EndIf

If ycol=1
  If flex1=0 And flex2=0
    newvy1=((mass1-mass2)*vy1+2*mass2*vy2)/M
    newvy2=((mass2-mass1)*vy2+2*mass1*vy1)/M
    Else
    newvy1=(mass1*vy1+mass2*vy2)/M
    newvy2=newvy1
  EndIf
EndIf

SelectElement(Thing(),index2)
If mob2=1
\newx=newx2
\newy=newy2
\newvx=newvx2
\newvy=newvy2
EndIf

index2+1
Until index2=CountList(Thing())

SelectElement(Thing(),index1)
If mob1=1
\newx=newx1
\newy=newy1
\newvx=newvx1
\newvy=newvy1
EndIf

index1+1
Until index1=CountList(Thing())-1

ResetList(Thing())
Repeat
NextElement(Thing())
\x=\newx
\y=\newy
\vx=\newvx
\vy=\newvy
\newx=0
\newy=0
\newvx=0
\newvy=0
Until CountList(Thing())-1=ListIndex(Thing())

EndProcedure

Procedure DisplayBack()
EndProcedure

Procedure DisplayOb()
ResetList(Thing())

Repeat
NextElement(Thing())
;If \type$="objet" Or \type$="vivant"
DisplayTransparentSprite(\sprite,\x,\y)
;EndIf
Until CountList(Thing())-1=ListIndex(Thing())

EndProcedure

Procedure DisplayFore()
EndProcedure

;Chargement des caractéristiques de toutes les boîtes
Loading()

;Temps de départ
time=ElapsedMilliseconds()

;-Boucle principale
;Calcul du temps et gestion des procédures
Repeat
FlipBuffers()
ClearScreen(RGB(255,255,255))

;Temps écoulé depuis la dernière mesure
dt=(ElapsedMilliseconds()-time)/1000
;Nouvelle mesure du temps
time=ElapsedMilliseconds()
;Record de temps écoulé entre deux mesures
If max<dt
max=dt
EndIf

;Calcul du déplacement des boîtes
MoveOb()
;Affichage de l'arrière-plan (vide)
DisplayBack()
;Affichage des objets
DisplayOb()
;Affichage du premier plan (vide)
DisplayFore()

;Affichage du temps
If StartDrawing(ScreenOutput())<>#Null
DrawText(20,20,"time="+StrF(time/1000,3)+" sec ; dt="+StrF(dt,3)+" sec ; dt(max)="+StrF(max,3)+" sec")
StopDrawing()
EndIf

;Touche "échap" pour arrêter le programme
ExamineKeyboard()
Until KeyboardPushed(#PB_Key_Escape)

EndWith

Publié : ven. 11/janv./2008 14:52
par djes
Et bien dis donc, ce n'est pas mal pour un débutant! Ton code fonctionne plutôt bien. La prochaine fois, génére tes images ou balance-les, qu'on ne soit pas obligé de les créer (mieux, fais-nous un zip).

Pour ton problème de collision, la réponse est simple, et compliquée à la fois. Apparemment, tu es essayé après une collision de repositionner l'objet en cours à la surface de l'autre. C'est bien, mais il peut quand même arriver des cas particuliers, quand par exemple deux objets en rencontrent un troisième, ou lorsque ton repositionnement est impossible du fait d'un autre objet venu derrière entre-temps. Pour tordre le cou à ce problème, il faudrait faire un test itératif à chaque fois sur tous les objets, jusqu'à fin définitive des collisions.

Cependant, il faut se demander si la solution n'est pas trop complexe par rapport à l'enjeu. Surtout pour un jeu de plateformes? Une gestion des collisions aussi aboutie n'est pas vraiment indispensable dans ce genre de jeu...

Au fait, pour parcourir les listes, tu peux utiliser foreach(). C'est pratique et ça évite de faire un resetlist()

PS: apparemment il y a une boite qui ne fonctionne pas bien. il faudrait essayer avec une boite, puis deux, puis trois, etc. pour pouvoir choper le problème (en envoyant des valeurs au debugger)

Publié : ven. 11/janv./2008 15:58
par Octavius
djes a écrit :La prochaine fois, génére tes images ou balance-les, qu'on ne soit pas obligé de les créer (mieux, fais-nous un zip).
Je ne savais pas comment héberger mon rar. :?
djes a écrit :Pour tordre le cou à ce problème, il faudrait faire un test itératif à chaque fois sur tous les objets, jusqu'à fin définitive des collisions.
C'est la seule solution ? Il n'y aurait pas une stratégie pour faire ça "en un coup" ? J'ai d'ailleurs remarquer qu'il n'y avait pas que les cas particuliers qui posaient problème, en regardant bien il arrive qu'une collision simple entre deux boîtes aboutisse à une pénétration mais c'est plutôt rare...
djes a écrit :Cependant, il faut se demander si la solution n'est pas trop complexe par rapport à l'enjeu. Surtout pour un jeu de plateformes? Une gestion des collisions aussi aboutie n'est pas vraiment indispensable dans ce genre de jeu...
Ben quand même je voudrais éviter que mon personnage (alias une boîte pour le moment) ne se téléporte n'importe où ou ne s'incruste dans le décor dans temps à autre.
djes a écrit :PS: apparemment il y a une boite qui ne fonctionne pas bien. il faudrait essayer avec une boite, puis deux, puis trois, etc. pour pouvoir choper le problème (en envoyant des valeurs au debugger)
Tu veux dire toujours la même ? Bizarre, je n'ai pas remarqué...

Publié : ven. 11/janv./2008 16:22
par djes
Il y a d'autres cas particuliers. Par exemple, les coins (déplacement en diagonale).

Je dirais que c'est à toi de trouver une solution simple et élégante. Dans un jeu de plateformes, on peut travailler avec des blocs qui se trouvent à des positions multiples de 16, 32, etc, et avec des tableaux.

On peut aussi travailler au pixel (comme tu le fais), avec ou sans des masques (imagine les objets avec une seule couleur, blanc c'est solide, noir c'est vide). Dans ce cas, surtout pour des personnages, on peut utiliser plusieurs points de collisions bien répartis afin de savoir comment réagir et se repositionner (important!).

Ce qui simplifie le problème, c'est que ton perso ne rebondira pas, et qu'il sera seul. Pas de multiples interractions à prévoir pour lui, et c'est quand même le principal!

Publié : ven. 11/janv./2008 22:51
par comtois
Tu auras autant d'objets susceptibles d'entrer en collision dans ton jeu de plateforme ? avec des mouvements aussi complexes ?

Je comprends que c'est un exercice, j'avais tenté le même avec des sphères en faisant du calcul a priori !
voir ce lien

Je n'avais pas fait de recherche sur internet, je m'aperçois en lisant cette page, que j'aurais dû , j'aurais abandonné plus tôt , ou je me serais orienté vers une solution moins fiable mais plus simple, par exemple la solution a posteriori !!

voir aussi cet article, sur la gestion du temps avec les collisions.

Publié : ven. 11/janv./2008 23:47
par Octavius
Merci pour les liens c'est très intéressant! :D

J'utilise donc une méthode a posteriori. Je suis sur la bonne voie. Je vais essayer de changer mon code et je vous donnerai des nouvelles!

faut commencer par faire simple

Publié : sam. 12/janv./2008 13:27
par beauregard
Octavius a écrit :Merci pour les liens c'est très intéressant! :D

J'utilise donc une méthode a posteriori. Je suis sur la bonne voie. Je vais essayer de changer mon code et je vous donnerai des nouvelles!
bonjour,
Sur cette page tu trouvera un code:
http://www.purebasic.fr/french/viewtopi ... c&start=15

Publié : sam. 12/janv./2008 13:54
par Anonyme
ici aussi , c'est du flash , mais facilement convertible dans n'importe quel langage http://www.tonypa.pri.ee/tbw/start.html