le code ci-dessous est un Module à inclure dans un projet pour disposer d'un moteur de rebond tout fait !
Il faut juste respecter la chose suivante pour ne pas avoir de bug : en aucun cas une bille ne doit toucher le bords de l'écran, elle doit rebondir avant.
le code contient une partie exemple, qui montre comment s'en servir. Cette partie est automatiquement enlevée lors de l'inclusion dans un autre fichier.
Parmi les trucs intéressant du code, il y a notamment ce dont je parlais dans le post à falsam :
- la détection des collisions est gérée de sorte que jamais, quelque que soit la vitesse de la bille, elle ne puisse manquer un obstacle sur son chemin !
- le rebond est calculé en fonction de ce contre quoi la bille s'est cognée, avec la normale au contact. C'est donc assez réaliste.
à faire : ajouter la rotation de la bille au contact, et les effets que ça entraine lors d'une collision.
Avec ce module, j'ai fais un petit jeu sur windows : un casse-brique simple.
Ce jeu ajoute une notion un peu plus complexe : une callback lors de la collision avec certaine partie (ici les briques). C'est assez pratique.
A priori on pourrait programmer n'importe quoi avec une bille 2D dedans. Comme un flipper, ou autre (j'ai pas d'idée là).
Un des problèmes qui reste, est l'ajout d'éléments qui bougent dans la détection de la collision.
On peut remarquer que dans le casse brique, lorsque le curseur va sur la bille, le comportement est bizarre. Il faut que je corrige ce point.
Le Module :
Code : Tout sélectionner
;{ MODULE BILLE
DeclareModule BILLE
Structure V2D
x.d
y.d
EndStructure
Structure _bille_contact_
x.d
y.d
a.d ; angle
p.V2D
vec.V2D
C.b
EndStructure
Structure _bille_
; position et Mouvement du centre de gravité
x.d
y.d
a.d ; angle
o.V2D ; ancienne position de la bille
r.d ; rayon
v.d ; vitesse horizontale
u.d ; vitesse verticale
w.d ; vitesse de rotation
m.d ; masse
I.d ; moment d'inertie
nb_contact.l
Color.l
Array contact._bille_contact_(0)
List trace._bille_contact_()
max_trace.l
trace_step.b
Count.l
EndStructure
Structure _Limit_
w.l
h.l
Array Limit.i(0,0)
Array info.l(0,0)
EndStructure
Global _Gravite_.V2D
Declare.i Init_Limit(Output.l, Color.l) ; Color is where the ball can go
Declare.i Init_Bille(x, y, rayon, Color.l, Taille_Trace.l = 0)
Declare.i Add_ActionOnRebound(*Limit._Limit_, Output.l, *Function)
Declare Process_Bille(*Limit._Limit_, *Bille._bille_, dT.d)
Declare DrawBille(*Bille._bille_)
EndDeclareModule
Module BILLE
#Distance_Pt_detection_contact = 2
#MAX_trace = 10
Procedure.i Init_Limit(Output.l, Color.l) ; Color is where the ball can go
If StartDrawing(Output)
*limit._Limit_ = AllocateMemory(SizeOf(_Limit_))
InitializeStructure(*limit, _Limit_)
*limit\w = OutputWidth() - 1
*limit\h = OutputHeight() - 1
Dim *limit\Limit(*limit\w, *limit\h)
Dim *limit\info(*limit\w, *limit\h)
For x = 0 To *limit\w
For y = 0 To *limit\h
If Point(x, y) <> Color
*limit\Limit(x, y) = 1
EndIf
Next
Next
StopDrawing()
ProcedureReturn *limit
Else
ProcedureReturn #False
EndIf
EndProcedure
Procedure.i Add_ActionOnRebound(*Limit._Limit_, Output.l, *ptr_ptr_Function)
If *Limit And StartDrawing(Output)
For x = 0 To *limit\w
For y = 0 To *limit\h
color = Point(x, y)
If color
*limit\Limit(x, y) = *ptr_ptr_Function
*limit\info(x, y) = color
EndIf
Next
Next
StopDrawing()
ProcedureReturn *limit
Else
ProcedureReturn #False
EndIf
EndProcedure
Procedure.i Init_Bille(x, y, rayon, Color.l, Taille_Trace.l = 0)
*bille._bille_ = AllocateMemory(SizeOf(_bille_))
InitializeStructure(*bille, _bille_)
*bille\x = x
*bille\y = y
*bille\r = rayon
*bille\o\x = x
*bille\o\y = y
_tmp_.d = *bille\r * *bille\r
*bille\m = #PI * _tmp_
*bille\I = *bille\m * _tmp_ / 2
*bille\Color = Color
*bille\max_trace = Taille_Trace
*bille\nb_contact = Round(2 * #PI * *bille\r / #Distance_Pt_detection_contact, #PB_Round_Up)
Dim *bille\contact(*bille\nb_contact)
_tmp_.d = 2 * #PI / (*Bille\nb_contact + 1)
For i = 0 To *Bille\nb_contact
*Bille\contact(i)\a = i * _tmp_
*Bille\contact(i)\vec\x = Cos(*Bille\contact(i)\a)
*Bille\contact(i)\vec\y = Sin(*Bille\contact(i)\a)
*Bille\contact(i)\p\x = *Bille\r * *Bille\contact(i)\vec\x
*Bille\contact(i)\p\y = *Bille\r * *Bille\contact(i)\vec\y
Next
ProcedureReturn *bille
EndProcedure
Procedure Process_Bille(*Limit._Limit_, *Bille._bille_, dT.d)
*Bille\Count + 1
;{ physique
; _ax_.d = 0 ; sert à stocker les forces x
; _ay_.d = 0 ; sert à stocker les forces y
; _cz_.d = 0 ; sert à stocker le couple
; Forces
_ax_ = _Gravite_\x ;+ _ax_ / *Bille\m
_ay_ = _Gravite_\y ;+ _ay_ / *Bille\m
;_cz_ = _cz_ / *Bille\I
; ajout acc --> vitesse
*Bille\u + _ax_ * dT
*Bille\v + _ay_ * dT
;*Bille\w + _cz_ * dT
If KeyboardPushed(#PB_Key_B)
*Bille\u * 1.02
*Bille\v * 1.02
EndIf
If KeyboardPushed(#PB_Key_N)
*Bille\u / 1.02
*Bille\v / 1.02
EndIf
*Bille\o\x = *Bille\x
*Bille\o\y = *Bille\y
; Ajout vitesse --> position
*Bille\x + *Bille\u * dT
*Bille\y + *Bille\v * dT
;*Bille\a + *Bille\w * dT
;}
;{ détection de contact
direction.V2D\x = *Bille\x - *Bille\o\x
direction\y = *Bille\y - *Bille\o\y
Taille.d = Sqr(direction\x * direction\x + direction\y * direction\y) / 2
If Taille < 1 : Taille = 1 : EndIf
direction\x / Taille
direction\y / Taille
pos.V2D\x = *Bille\o\x
pos\y = *Bille\o\y
For j = 1 To Taille
pos\x + direction\x
pos\y + direction\y
_nb_contact = 0
For i = 0 To *Bille\nb_contact
*Bille\contact(i)\x = pos\x + *Bille\contact(i)\p\x
*Bille\contact(i)\y = pos\y + *Bille\contact(i)\p\y
x.l = *Bille\contact(i)\x
y.l = *Bille\contact(i)\y
*Bille\contact(i)\C = #False
If *Limit\Limit(x, y)
*Bille\contact(i)\C = #True
_nb_contact + 1
If *Limit\Limit(x, y) <> 1
CallFunctionFast(PeekI(*Limit\Limit(x, y)), *Limit\Limit(x, y), *Limit, *Bille, x, y)
EndIf
EndIf
Next
If _nb_contact
;{ contact
*Bille\x = pos\x - direction\x
*Bille\y = pos\y - direction\y
_u_.d = 0
_v_.d = 0
For i = 0 To *Bille\nb_contact
If *Bille\contact(i)\C = #True
_u_ - *Bille\contact(i)\vec\y
_v_ + *Bille\contact(i)\vec\x
EndIf
Next
_tmp_.d = Sqr(_u_ * _u_ + _v_ * _v_)
_u_ / _tmp_; / 1.05
_v_ / _tmp_; / 1.05
*Bille\x - _v_; * 2
*Bille\y + _u_; * 2
_tmp_c_x.d = *Bille\u * _u_ + *Bille\v * _v_
_tmp_c_y.d = *Bille\v * _u_ - *Bille\u * _v_
_tmp_d_x.d = _tmp_c_x * _u_ + _tmp_c_y * _v_
_tmp_d_y.d = _tmp_c_x * _v_ + _tmp_c_y * (-_u_)
*Bille\u = _tmp_d_x
*Bille\v = _tmp_d_y
;}
Break
EndIf
Next
;}
;{ trace
If *Bille\max_trace
*Bille\trace_step = 0
If *Bille\Count % 1 = 0 Or _REBOND_
LastElement(*Bille\trace())
AddElement(*Bille\trace())
*Bille\trace()\x = *Bille\x
*Bille\trace()\y = *Bille\y
*Bille\trace()\C = *Bille\max_trace
*Bille\trace_step = 1
If ListSize(*Bille\trace()) > *Bille\max_trace
FirstElement(*Bille\trace())
DeleteElement(*Bille\trace())
EndIf
EndIf
EndIf
;}
EndProcedure
Procedure DrawBille(*Bille._bille_)
If *Bille\max_trace
ForEach *Bille\trace()
_tmp_.d = *Bille\trace()\C / *Bille\max_trace
Circle(*Bille\trace()\x, *Bille\trace()\y, *Bille\r * _tmp_, $ff * _tmp_)
*Bille\trace()\C - *Bille\trace_step
Next
EndIf
Circle(*Bille\x, *Bille\y, *Bille\r, *Bille\Color)
;LineXY(*Bille\x, *Bille\y, *Bille\x + *Bille\r * Cos(*Bille\a), *Bille\y + *Bille\r * Sin(*Bille\a), 0)
EndProcedure
EndModule
;}
CompilerIf #PB_Compiler_IsMainFile
;{ Initialisation & fenetre
InitSprite()
InitKeyboard()
UsePNGImageDecoder()
OpenWindow(0, 0, 0, 800, 660, "Flipper", #PB_Window_ScreenCentered|#PB_Window_SystemMenu)
OpenWindowedScreen(WindowID(0), 0, 0, 800, 660)
KeyboardMode(1)
;LoadSprite(0, "flipper.png")
;{ decors
CreateSprite(0, 800, 600)
NewList *bille.BILLE::_bille_()
StartDrawing(SpriteOutput(0))
Box(0, 0, 800, 600, $FFFFFF)
For i = 0 To 250
rayon = Random(35)+20
x.d = rayon + Random(800 - 2 * rayon)
y.d = rayon + Random(600 - 2 * rayon)
If i%100=0
AddElement(*bille())
*bille() = BILLE::Init_Bille(x, y, Random(10)+5, $FF, 20)
*bille()\u = 200
EndIf
Circle(x, y, rayon - 10, 0)
Next
Line(0, 0, 800, 1, $FFFFFF)
Line(0, 599, 800, 1, $FFFFFF)
Line(0, 0, 1, 600, $FFFFFF)
Line(799, 0, 1, 600, $FFFFFF)
StopDrawing()
;}
*Limit = BILLE::Init_Limit(SpriteOutput(0), 0)
; *bille = BILLE::Init_Bille(100, 300, 10, $FF)
; *bille2 = BILLE::Init_Bille(400, 300, 5, $FF)
_time_ = ElapsedMilliseconds()-5 ; evite que _dt_ = 0 au début
;}
;{ boucle principale
Repeat
;{ Event
Repeat
_event_ = WindowEvent()
Until _event_ = 0 Or #PB_Event_CloseWindow
ExamineKeyboard()
If KeyboardPushed(#PB_Key_Escape)
_event_ = #PB_Event_CloseWindow
EndIf
BILLE::_Gravite_\x = 0
BILLE::_Gravite_\y = 0
If KeyboardPushed(#PB_Key_Left)
BILLE::_Gravite_\x = -500
EndIf
If KeyboardPushed(#PB_Key_Right)
BILLE::_Gravite_\x = 500
EndIf
If KeyboardPushed(#PB_Key_Up)
BILLE::_Gravite_\y = -500
EndIf
If KeyboardPushed(#PB_Key_Down)
BILLE::_Gravite_\y = 500
EndIf
;}
_dt_.d = (ElapsedMilliseconds() - _time_) / 1000
_time_ = ElapsedMilliseconds()
; BILLE::Process_Bille(*Limit, *bille, _dt_)
; BILLE::Process_Bille(*Limit, *bille2, _dt_)
ForEach *bille()
BILLE::Process_Bille(*Limit, *bille(), _dt_)
Next
;{ dessin
ClearScreen(0)
DisplaySprite(0, 0, 0)
StartDrawing(ScreenOutput())
DrawText(0, 0, "dT = " + _dt_, 0, $FFFFFF)
; BILLE::DrawBille(*bille)
; BILLE::DrawBille(*bille2)
ForEach *bille()
BILLE::DrawBille(*bille())
Next
DrawText(10, 610, "[B] / [N] : Accélérer / Ralentir toutes les billes", $FFFFFF, 0)
DrawText(10, 630, "[Flèches] : Applique une force dans la direction de la flèche", $FFFFFF, 0)
StopDrawing()
FlipBuffers()
;}
Until _event_ = #PB_Event_CloseWindow
;}
CompilerEndIf