Coliziunea obiectelor

In special pentru pasionatii de jocuri, este important sa stiti cand doua obiecte se lovesc reciproc. In acest tutorial vom aborda forme simple ale obiectelor si vom invata sa testam daca exista o coliziune intre acestea.

 

Ca un prim exemplu vom lua doua obiecte dreptunghiulare (sau patrate). Pentru simulare vom folosi functii GDI+, in concluzie prima linie din script este:

#include <GDIPlus.au3>

Apoi vom crea o fereastra pentru simulare:

$hMain = GUICreate("Exemplu",200,200)
GUISetState(@SW_SHOW,$hMain)

Pentru aceasta simulare avem nevoie de trei functii:

Func CreateObject($X,$Y,$W,$H,$MinX,$MaxX,$MinY,$MaxY)
	Local $Object[8] = [$X,$Y,$W,$H,$MinX,$MaxX,$MinY,$MaxY]
	Return $Object
EndFunc

Functia CreateObject creaza un array cu caracteristicile unui obiect (coordonatele punctului de la care incepe obiectul, latimea, inaltimea si coorodonatele minime si maxime pentru X si Y pe care se poate misca obiectul).

Poate imaginea de mai jos este mai sugestiva:

Daca consideram patratul negru (fereastra 200×200), pentru MaxX = 160 si MaxY = 160, in coltul dreapta-jos ramane exact spatiul de desenare pentru un patrat cu latimea 40 si inaltimea 40. Deci MaxX = latimea ferestrei – latimea patratului si MaxY = inaltimea ferestrei – inaltimea patratului.

 

Func UpdateObject(ByRef $Obj,$X,$Y)
	If $X < $Obj[4] Then $X = $Obj[4]
   	If $X > $Obj[5] Then $X = $Obj[5]
	If $Y < $Obj[6] Then $Y = $Obj[6]
   	If $Y > $Obj[7] Then $Y = $Obj[7]
	$Obj[0] = $X
	$Obj[1] = $Y
EndFunc

Functia UpdateObject o vom folosi pentru a seta o noua pozitie obiectului. Primele 4 conditii testeaza noua pozitie sa nu fie in afara limitelor impuse iar ultimele doua seteaza pozitia.

 

Func ObjectsOverlap($Obj1,$Obj2)
    Local $SX = Abs($Obj2[0] - $Obj1[0]) + $Obj2[2]
    Local $SY = Abs($Obj2[1] - $Obj1[1]) + $Obj2[3]
    Return ($SX < $Obj1[2] + $Obj2[2] And $SY < $Obj1[3] + $Obj2[3])
EndFunc

Functia ObjectOverlap verifica daca cele doua obiecte au ajuns in contact. Imaginea de mai jos este sugestiva:

Pentru oricare doua obiecte, daca valoarea SX este mai mica decat suma latimiilor obiectelor atunci cele doua obiecte se suprapun pe verticala. Daca valoarea SY este mai mica decat suma inaltimiilor obiectelor atunci cele doua obiecte se suprapun pe orizontala. Pentru a  determina coliziunea dintre doua obiecte, trebuie indeplinite ambele conditii.

 

Cream suprafata de desenare, bufferul in memorie pentru Double Buffering si doua obiecte Brush cu care desenam patratele.

_GDIPlus_Startup()
$hGraphics = _GDIPlus_GraphicsCreateFromHWND($hMain)
$hBitmap = _GDIPlus_BitmapCreateFromGraphics(200,200,$hGraphics)
$hBackBuffer = _GDIPlus_ImageGetGraphicsContext($hBitmap)
$hBrush1 = _GDIPlus_BrushCreateSolid(0xFFFF0000)
$hBrush2 = _GDIPlus_BrushCreateSolid(0xFF00FF00)

Inainte de a incepe verificarea coliziunii intr-o bucla, trebuie sa cream cele doua obiecte:

$Step = 5
$Obj1 = CreateObject(0,0,40,40,0,160,0,160)
$Obj2 = CreateObject(160,160,40,40,0,160,0,160)

Variabila $Step reprezinta pasul cu care se vor misca obiectele in simulare.

In bucla principala trebuie facute trei lucruri importante: generarea unei noi pozitii pentru obiect (aleator in cazul nostru), reactualizarea pozitiei pentru obiect si desenarea acestuia pe suprafata de desenare. Bucla principala arata asa:

Do
	Switch Random(1,8,1)
		Case 1
			UpdateObject($Obj1,$Obj1[0]+$Step,$Obj1[1])
		Case 2
			UpdateObject($Obj1,$Obj1[0]-$Step,$Obj1[1])
		Case 3
			UpdateObject($Obj1,$Obj1[0],$Obj1[1]+$Step)
		Case 4
			UpdateObject($Obj1,$Obj1[0],$Obj1[1]-$Step)
		Case 5
			UpdateObject($Obj2,$Obj2[0]+$Step,$Obj2[1])
		Case 6
			UpdateObject($Obj2,$Obj2[0]-$Step,$Obj2[1])
		Case 7
			UpdateObject($Obj2,$Obj2[0],$Obj2[1]+$Step)
		Case 8
			UpdateObject($Obj2,$Obj2[0],$Obj2[1]-$Step)
	EndSwitch
	_GDIPlus_GraphicsClear($hBackBuffer,0xFF000000)
	_GDIPlus_GraphicsFillRect($hBackBuffer,$Obj1[0],$Obj1[1],$Obj1[2],$Obj1[3],$hBrush1)
	_GDIPlus_GraphicsFillRect($hBackBuffer,$Obj2[0],$Obj2[1],$Obj2[2],$Obj2[3],$hBrush2)
	_GDIPlus_GraphicsDrawImage($hGraphics,$hBitmap,0,0)
	Sleep($Step*10)
Until ObjectsOverlap($Obj1,$Obj2)

In bucla, primul lucru care se face este generarea unei noi pozitii pentru unul din obiecte. Pentru aceasta folosim o expresie conditionala cu un numar aleator intre 1-8. Primele 4 cazuri simuleaza miscarea (pozitiva si negativa) pe axa X, respectiv miscarea (pozitiva si negativa) pe axa Y, pentru primul obiect. Ultimele 4 cazuri simuleaza miscarea (pozitiva si negativa) pe axa X, respectiv miscarea (pozitiva si negativa) pe axa Y, pentru al doilea obiect.

Pentru oricare din aceste cazuri se efectueaza o operatie de reactualizare a pozitiei pentru un obiect.

Ultimele linii ale buclei curata bufferul, deseneaza obiectele in buffer si apoi copiaza imaginea din buffer pe ecran. Dupa cum stiti, pentru a nu suprasolicita CPU, in bucle se folosesc pauze scurte, in acest caz durata pauzei o sa fie proportionala cu pasul de miscare al obiectelor.

Bucla ruleaza in acest mod pana cand cele doua obiecte ajung in coliziune.

La iesirea din bucla se afiseaza mesajul de atentionare ca obiectele sunt in coliziune, timp de 5 secunde.

TrayTip("Coliziune","Obiectele au ajuns in contact.",1)
Sleep(5000)

Mai trebuie doar sa eliberam resursele GDI+ folosite:

_GDIPlus_BrushDispose($hBrush1)
_GDIPlus_BrushDispose($hBrush2)
_GDIPlus_GraphicsDispose($hBackBuffer)
_GDIPlus_BitmapDispose($hBitmap)
_GDIPlus_GraphicsDispose($hGraphics)
_GDIPlus_Shutdown()

 

Daca asamblam portiunile de cod sursa de mai sus obtinem urmatorul script (pentru vizibilitate am pus functiile la final):

#include <GDIPlus.au3>
 
$hMain = GUICreate("Exemplu",200,200)
GUISetState(@SW_SHOW,$hMain)
 
_GDIPlus_Startup()
$hGraphics = _GDIPlus_GraphicsCreateFromHWND($hMain)
$hBitmap = _GDIPlus_BitmapCreateFromGraphics(200,200,$hGraphics)
$hBackBuffer = _GDIPlus_ImageGetGraphicsContext($hBitmap)
$hBrush1 = _GDIPlus_BrushCreateSolid(0xFFFF0000)
$hBrush2 = _GDIPlus_BrushCreateSolid(0xFF00FF00)
 
$Step = 5
$Obj1 = CreateObject(0,0,40,40,0,160,0,160)
$Obj2 = CreateObject(160,160,40,40,0,160,0,160)
 
Do
	Switch Random(1,8,1)
		Case 1
			UpdateObject($Obj1,$Obj1[0]+$Step,$Obj1[1])
		Case 2
			UpdateObject($Obj1,$Obj1[0]-$Step,$Obj1[1])
		Case 3
			UpdateObject($Obj1,$Obj1[0],$Obj1[1]+$Step)
		Case 4
			UpdateObject($Obj1,$Obj1[0],$Obj1[1]-$Step)
		Case 5
			UpdateObject($Obj2,$Obj2[0]+$Step,$Obj2[1])
		Case 6
			UpdateObject($Obj2,$Obj2[0]-$Step,$Obj2[1])
		Case 7
			UpdateObject($Obj2,$Obj2[0],$Obj2[1]+$Step)
		Case 8
			UpdateObject($Obj2,$Obj2[0],$Obj2[1]-$Step)
	EndSwitch
	_GDIPlus_GraphicsClear($hBackBuffer,0xFF000000)
	_GDIPlus_GraphicsFillRect($hBackBuffer,$Obj1[0],$Obj1[1],$Obj1[2],$Obj1[3],$hBrush1)
	_GDIPlus_GraphicsFillRect($hBackBuffer,$Obj2[0],$Obj2[1],$Obj2[2],$Obj2[3],$hBrush2)
	_GDIPlus_GraphicsDrawImage($hGraphics,$hBitmap,0,0)
	Sleep($Step*10)
Until ObjectsOverlap($Obj1,$Obj2)
 
TrayTip("Coliziune","Obiectele au ajuns in contact.",1)
Sleep(5000)
 
_GDIPlus_BrushDispose($hBrush1)
_GDIPlus_BrushDispose($hBrush2)
_GDIPlus_GraphicsDispose($hBackBuffer)
_GDIPlus_BitmapDispose($hBitmap)
_GDIPlus_GraphicsDispose($hGraphics)
_GDIPlus_Shutdown()
 
Func CreateObject($X,$Y,$W,$H,$MinX,$MaxX,$MinY,$MaxY)
	Local $Object[8] = [$X,$Y,$W,$H,$MinX,$MaxX,$MinY,$MaxY]
	Return $Object
EndFunc
 
Func UpdateObject(ByRef $Obj,$X,$Y)
	If $X < $Obj[4] Then $X = $Obj[4]
   	If $X > $Obj[5] Then $X = $Obj[5]
	If $Y < $Obj[6] Then $Y = $Obj[6]
   	If $Y > $Obj[7] Then $Y = $Obj[7]
	$Obj[0] = $X
	$Obj[1] = $Y
EndFunc
 
Func ObjectsOverlap($Obj1,$Obj2)
    Local $SX = Abs($Obj2[0] - $Obj1[0]) + $Obj2[2]
    Local $SY = Abs($Obj2[1] - $Obj1[1]) + $Obj2[3]
    Return ($SX < $Obj1[2] + $Obj2[2] And $SY < $Obj1[3] + $Obj2[3])
EndFunc

 

 
Asemanator este si pentru alte tipuri de obiecte. Spre exemplu pentru cercuri, functia CreateObject poate arata in felul urmator:

Func CreateObject($X,$Y,$R,$MinX,$MaxX,$MinY,$MaxY)
	Local $Object[7] = [$X,$Y,$R,$MinX,$MaxX,$MinY,$MaxY]
	Return $Object
EndFunc

Pentru un cerc vor fi importante coordonatele centrului cercului si raza acestuia. Restul parametrilor raman neschimbati.
 
Functia UpdateObject ramane neschimbata, X si Y reprezentand centrul obiectului(cercului).
 
Din ecuatia cercului se poate obtine si formula pentru a verifica cand doua cercuri sunt in contact, deci vom avea functia:

Func ObjectsOverlap($Obj1,$Obj2)
	Return ((($Obj1[2]+$Obj2[2])^2) > ((($Obj2[0]-$Obj1[0])^2)+((($Obj2[1]-$Obj1[1])^2))))
EndFunc

Altfel spus cand (R1+R2)^2 > ((X2-X1)^2 + (Y2-Y1)^2) cele doua cercuri sunt in contact, unde X si Y sunt coordonatele pentru centrul cercului si R este raza cercului (indexul 1 sau 2 indica daca este vorba despre primul sau al doilea obiect).
 
Singurul lucru care poate fi derutant este la desenarea cercurilor pe ecran. In AutoIt, cercul este o elipsa in care latimea este egala cu inaltimea si pentru care X si Y nu reprezinta centrul cercului ci coordonatele punctului de la care incepe desenarea elipsei. Acest lucru poate fi scris la desenare in felul urmator:

_GDIPlus_GraphicsFillEllipse($hBackBuffer,$Obj1[0]-$Obj1[2],$Obj1[1]-$Obj1[2],$Obj1[2]*2,$Obj1[2]*2,$hBrush1)
_GDIPlus_GraphicsFillEllipse($hBackBuffer,$Obj2[0]-$Obj2[2],$Obj2[1]-$Obj2[2],$Obj2[2]*2,$Obj2[2]*2,$hBrush2)

 
Altfel spus coordonatele elipsei sunt: X = coordonata X a centrului cercului – raza, Y = coordonata Y a centrul cercului – raza, W = raza cercului * 2 si H = raza cercului * 2.
 
Un script care verifica daca doua cercuri ajung in contact poate arata asa:

#include <GDIPlus.au3>
 
$hMain = GUICreate("Exemplu",200,200)
GUISetState(@SW_SHOW,$hMain)
 
_GDIPlus_Startup()
$hGraphics = _GDIPlus_GraphicsCreateFromHWND($hMain)
$hBitmap = _GDIPlus_BitmapCreateFromGraphics(200,200,$hGraphics)
$hBackBuffer = _GDIPlus_ImageGetGraphicsContext($hBitmap)
$hBrush1 = _GDIPlus_BrushCreateSolid(0xFFFF0000)
$hBrush2 = _GDIPlus_BrushCreateSolid(0xFF00FF00)
 
$Step = 5
$Obj1 = CreateObject(20,20,20,20,180,20,180)
$Obj2 = CreateObject(180,180,20,20,180,20,180)
 
Do
	Switch Random(1,8,1)
		Case 1
			UpdateObject($Obj1,$Obj1[0]+$Step,$Obj1[1])
		Case 2
			UpdateObject($Obj1,$Obj1[0]-$Step,$Obj1[1])
		Case 3
			UpdateObject($Obj1,$Obj1[0],$Obj1[1]+$Step)
		Case 4
			UpdateObject($Obj1,$Obj1[0],$Obj1[1]-$Step)
		Case 5
			UpdateObject($Obj2,$Obj2[0]+$Step,$Obj2[1])
		Case 6
			UpdateObject($Obj2,$Obj2[0]-$Step,$Obj2[1])
		Case 7
			UpdateObject($Obj2,$Obj2[0],$Obj2[1]+$Step)
		Case 8
			UpdateObject($Obj2,$Obj2[0],$Obj2[1]-$Step)
	EndSwitch
	_GDIPlus_GraphicsClear($hBackBuffer,0xFF000000)
	_GDIPlus_GraphicsFillEllipse($hBackBuffer,$Obj1[0]-$Obj1[2],$Obj1[1]-$Obj1[2],$Obj1[2]*2,$Obj1[2]*2,$hBrush1)
	_GDIPlus_GraphicsFillEllipse($hBackBuffer,$Obj2[0]-$Obj2[2],$Obj2[1]-$Obj2[2],$Obj2[2]*2,$Obj2[2]*2,$hBrush2)
	_GDIPlus_GraphicsDrawImage($hGraphics,$hBitmap,0,0)
	Sleep($Step*5)
Until ObjectsOverlap($Obj1,$Obj2)
 
TrayTip("Coliziune","Obiectele au ajuns in contact.",1)
Sleep(5000)
 
_GDIPlus_BrushDispose($hBrush1)
_GDIPlus_BrushDispose($hBrush2)
_GDIPlus_GraphicsDispose($hBackBuffer)
_GDIPlus_BitmapDispose($hBitmap)
_GDIPlus_GraphicsDispose($hGraphics)
_GDIPlus_Shutdown()
 
Func CreateObject($X,$Y,$R,$MinX,$MaxX,$MinY,$MaxY)
	Local $Object[7] = [$X,$Y,$R,$MinX,$MaxX,$MinY,$MaxY]
	Return $Object
EndFunc
 
Func UpdateObject(ByRef $Obj,$X,$Y)
	If $X < $Obj[3] Then $X = $Obj[3]
	If $X > $Obj[4] Then $X = $Obj[4]
	If $Y < $Obj[5] Then $Y = $Obj[5]
	If $Y > $Obj[6] Then $Y = $Obj[6]
	$Obj[0] = $X
	$Obj[1] = $Y
EndFunc
 
Func ObjectsOverlap($Obj1,$Obj2)
	Return ((($Obj1[2]+$Obj2[2])^2) > ((($Obj2[0]-$Obj1[0])^2)+((($Obj2[1]-$Obj1[1])^2))))
EndFunc

* Pentru orice intrebari sau nelamuriri legate de curs sau limbajul AutoIt accesati sectiunea AutoIt a forumului SkullBox sau platforma de suport tehnic NetHelp.