[CPP] Genaue Kollision von Pixeln (Bewegung)

Skid

Erfahrenes Mitglied
Hallo,

ich bin wiedermal zu einen Punkt gekommen, wo ich einfach nicht weiterkomme.

Das Problem was ich derzeit habe ist eine genaue Kollision bspw. eines Balles.
Mir ist durchaus bewusst, dass das schon oft besprochen worden ist, jedoch fand ich bisher immer nur Kollisionsabfragen in Bezug auf Sprites oder ähnlichen, was sich in der Pixelumgebung (1Pixel bis 2x2Pixel) schwer realisieren lässt.

Mein Problem:
Ich habe einen Ball (1Pixel) der eine bestimmte Geschwindigkeit besitzt (bspw. ballSpeed = 4).

Die Geschwindigkeit bzw. die Bewegung rechne ich auf die jeweiligen Achsen um die abhängig vom derzeitigen Ballwinkel (ballAngle = 180) sind (XAchsenBewegung = ballSpeed * (cos (ballAngle * PI / 180)), YAchsenBewegung = ballSpeed * (sin (ballAngle * PI / 180)) ).

Code:
yCourse
^   
|       
| 
O --------> xCourse

Funktioniert soweit recht gut, auch wenn ich hier das Problem habe, dass das mir etwas zu grob ist, wenn ich es in int konvertiere, aber da lasse ich mir noch was genaues einfallen, dass soll auch nicht Thema dieses Threads werden.

Angenommen, der Ball kollidiert jetzt mit der Wand, dann kann ich das wie folgt abfragen:
Code:
if(bX >= windowWidth || bX <= 0)
	b.setAngle((bA == 0) ? 180 : (int)(180 - bA));


if(bY >= windowHeight || bY <= 0)
	b.setAngle((bA == 90) ? -90 : (int)(360 - bA));

Jedoch fängt es hier schon an ungenau zu werden, d.h. der Ball verschwindet teilweise kurz, weil er kurzzeitig in den Minusbereich kommen kann (bspw. -1 bei der Abfrage nach windowWidth).
Hier müsste ich die Differenz ausrechnen, anschließend den neuen Winkel (s.o.) und später die Differenz (von Abstand Wand zu Ball) einfach zur neuen Richtung hinzuaddieren.
Im nächsten Zyklus, würde die XAchsen- und YAchsenBewegung neuberechnet werden, okay, soweit so gut.

Problemetisch jedoch wird es für mich, wenn es um eine Kollision geht, bei einen Objekt, was der Spiele steuert und da hört es bei mir auf, weil die Algorithmen die mir einfallen einfach zu kompliziert sind und zu fehleranfällig.
Hier bräuchte ich jetzt Hilfe.

Wenn ich z.b. mein PingPong-Spiel nehme, dann müsste ich für jede XAchsenBewegung und YAchsenBewegung auf eine Kollision prüfen mit den Koordinaten des Spielers.
Da aber der Austrittswinkel abhängig von der Spielerbewegung ist und die XAchsenBewegung nicht 0 werden darf, komme ich irgendwie auf keinen wirklich guten Nenner.
Desweiteren kann der Ball das Spielerobjekt einfach überspringen, da er bspw. vom SpielerObjekt noch 2Pixel entfernt ist, sich jedoch 4Pixel schnell bewegt.

Entweder klappt die Kollision, der Ball wird allerdings kurzzeitig hinter dem Spielobjekt angezeigt, oder die Kollision wird erkannt und nichts geht mehr.

Vielleicht kann mit mir jemand das evtl. Schritt für Schritt durchgehen (Pseudocode), vielleicht ist die Lösung auch total simpel, da ich allgemein der Meinung bin, dass ich mich da irgendwie zu stark festgefressen habe.

Vielen Dank und liebe Grüße,
SkiD.
 
Ich würde das an deiner Stelle anders machen und den Ball immer um eine Pixel Bewegen ich weiß leider nicht was du benutzt um den Ball darzustellen. Die Geschwindigkeit würd dann durch einen Timer definiert (je nachdem wie schnelle oder oft dieser aufgerufen wird ist die Geschwiindigkeit des Balles). Somit hättest du das Problem gelöst das der Ball irgendetwas überspringen kann oder außerhalb des Feldes kommt. Und wieso wird der Ball manchmal erst kurz hinter dem Player angezeigt berechne doch einfach die Kollision bevor du neu zeichnest dann dürte es dazu nicht kommen. Du kannst doch einfach die Kollision Berechnen und falls ein vorliegt Berechnest du wie er weiterfliegen muss, also in welche richtung mit was für einem Winkel und Speed. Also die Player abhängigkeiten miteinbeziehen.
Um mehr zu sagen müsstest du schon Code zeigen.
 
Zugegeben, eine ähnliche Idee hatte ich schonmal, allerdings gibt es da für mich noch zwei Probleme, wobei ich nicht weiss, ob das folgende Problem wirklich ein Problem ist:

Wenn ich den Ball immer um ein verschiebe, dann kann ich sogesehen doch nur maximal 4 Bewegungen in einen Quadranten realisieren, dass da wäre (xR = xRichtung, yR = yRichtung) xR = yR = 1; xR = 1, yR = 0; xR = yR = 0 (bringt mir ja nichts); xR = 0, yR = 1.
Vielleicht verstehe ich da nun auch etwas falsch, jedoch könnte ich genauere Winkel nicht realtisieren, bspw. 23°.
Allerdings könnte ich das evtl. über eine Schleife machen, für die XBewegung und yBewegung, vielleicht war dass deine Absicht ?

Das nächste Problem ist gerade, dass ich nicht weiss, was genau der Timer timen soll.
Ich habe es bisher so realisiert mit den FPS:
Code:
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance, 
			       LPSTR lpCmdLine, int nShowCmd				)
{
	//...

	//Main-Schleife, dort wo Messagesabgefangen werden 
	//Allgemein das ganze Leben des Spiels abspielt
	//(löschen vorherige Bewegung, neue Position, Tastenabfragen etc.)
	while(true)
	{
			//...

			//30 FPS maximum
			while((GetTickCount() - startTime) < 33);

			//...
	}

	//...
}

Was mir eine 30FPS Begrenzung gibt und nach jedem Zyklus die Schleife den Ball um die xCourse + yCourse Bewegung erhöht, sowie die Spielerbewegung realisiert.
Wie gesagt keine schöne Lösung und für mein befinden auch -wieder- zu umständlich, da ich so Überspringmechanismen hervorrufe die sich nur schwer beherrschen lassen.

Wenn ich jetzt bspw. einen Timer besitze, dann habe ich die Möglichkeit dieen logischerweise zu starten und zu stoppen, sowie die Differenz daraus zu erzeugen.
Was genau schwebte dir hierbei vor ?

Wie gesagt, deine Idee finde ich gut, nur wusste ich nicht, wie ich dass dann mit den jeweiligen XCourse und yCourse realisieren soll.

Ich würde das an deiner Stelle anders machen und den Ball immer um eine Pixel Bewegen ich weiß leider nicht was du benutzt um den Ball darzustellen.

Der Ball wird so dargestellt:
Code:
//Ball und Spieler Darstellung
void plotNextMove(playerClass &p1, playerClass &p2, ballClass &b)
{
	//Plot player1
	//...

	//Plot player2
	//...

	int* cA = b.getColor();
	for(int i = 0; i < b.getSize(); i++)
		for(int j = 0; j < b.getSize(); j++)
			SetPixel(hDC, b.getBallX() + i, b.getBallY() + j, 
					 RGB(cA[0], cA[1], cA[2]));
}

//Pixel löschen
void deletePrevMove(playerClass &p1, playerClass &p2, ballClass &b)
{
	//Player 1 delete pixel
	//...

	//Player 2 delete pixel
	//...

	//delete prev. ball pixel
	for(int i = 0; i < b.getSize(); i++)
		for(int j = 0; j < b.getSize(); j++)
			SetPixel(hDC, b.getBallX() + i, b.getBallY() + j, 0);
}

Die erste und zweite for-Schleife für die jeweilige Dicke des Balles, d.h. der Ball ist generell quadratisch, maximal jedoch 2x2 Pixel (bisher).
Mit SetPixel werden die Pixel jeweils farblich hervorgehoben, standardmässig ist der Ball weiss und 1 Pixel groß.

Das b Objekt enthält alle wichtigen Koordinaten, wie z.b. XPos, YPos, XCourse, YCourse, BallSize, BallSpeed etc.

Da ich sowieso der Meinung bin, dass meine bisherige Lösung etwas zu aufwändig ist, wäre ich für neue Vorschläge allgemein eher offen, da mir die Kollisionsabfrage pro Pixelmove, wie auch die maximale xBewegung = yBewegung = 1, eher zusagt.

Liebe Grüße und danke für den Tipp,
SkiD. :)
 
das mit dem Timer meinte ich anders -.-
Code:
#define Timer_ID 8000
SetTimer(hWnd,Timer_ID,Speed,NULL);

// dann in der Nachrichtenbehandlung
case WM_TIMER:
{
       // Hier Bewegung hin
}

// Am Schluss
KillTimer(hWnd, Timer_ID);

Aber die FPS würde ich auf jedenfall abriegeln. Verhindert flackern und das Spiel ist überall gleichschnell.
 
Hallo,

ich werde das so einfach mal ausprobieren und dann berichte ich innerhalb der nächsten zwei Tage!

Liebe Grüße und erstmal danke für deine Mühe,
SkiD.
 
Hallo,

ich werde das so einfach mal ausprobieren und dann berichte ich innerhalb der nächsten zwei Tage!

Liebe Grüße und erstmal danke für deine Mühe,
SkiD.

Okay, also mit dem Timer funktioniert es einwandfrei, nur habe ich eine weitere Frage, was die weitere Bewegung angeht.

Ich hatte ja vorher den Ball immer um den xCourse bzw. yCourse bewegt.
Jetzt habe ich diese Möglichkeit nicht mehr, würde es aber so regeln dass ich dem Ball eine weitere Variable gebe, wie z.B. xCount und yCount, die bei jeder Pixelbewegung um eins hochgezählt werden und je nachdem ob xCount == xCourse bzw. yCount == yCourse dann den Ball eins in x- bzw. y-Richtung verschiebe.

Würdest du das Problem auch so lösen, oder hattest du dir das etwas anders gedacht ?

Liebe Grüße,
SkiD.
 
Ich habe gerade nicht mehr im Kopf wie du das gerade beschreibst. Du programmierst ja gerade ein Pong? Wie genau möchtest du denn nochmal die Bewegung des Balles beschreiben? Da gibt es ja mehrere Möglichkeiten und es kommt natürlich auch auf das Spiel selber an ob der Ball haben haben kann oder nicht?
 
Ich habe gerade nicht mehr im Kopf wie du das gerade beschreibst. Du programmierst ja gerade ein Pong? Wie genau möchtest du denn nochmal die Bewegung des Balles beschreiben? Da gibt es ja mehrere Möglichkeiten und es kommt natürlich auch auf das Spiel selber an ob der Ball haben haben kann oder nicht?

Ja wie gesagt, bin derzeit an einem Pong-Spiel, damit ich erstmal in die Spieleprogrammierung reinfinden kann.
Der Ball bewegt sich mittlerweile via Timer, immer nur einen Pixel pro Timerablauf.
Die Kollision -habe ich derzeit für das Spielfeld integriert- für den Spieler fehlt noch, kommt aber nach dem Winkelproblem.

Also ich habe mittlerweile soweit (im Detail):

Ich habe den Ball via Timer realisiert, habe aber den Timer (also sobald der Timer ausgeführt wird) den etwas beschleunigt, weil die Ballbewegung etwas zu langsam ist, deswegen habe ich eine For-Schleife drumgebastelt:

Code:
		case WM_TIMER:
			//Bewegung
			for(int i = 0; i < ball.getSpeed(); i++)
			{
				deletePrevBallMove();
				findNextBallMove();
				plotNextBallMove();
			}
			break;

So das nächste jetzt ist der Abschnitt, findNextBallMove().
Ich habe mir das folgendermaßen gedacht: Ich berechne den Winkel, teile das auf x-Richtung und y-Richtung und zähle zwei Variablen hoch, die ich immer mit der Richtung vergleiche, wenn sie gleich sind, wird ballX oder ballY +1 oder -1 gesetzt.
Hier die Funktion:

Code:
void findNextBallMove()
{
	//Collision Detection
	//true
	//	new xCourse
	//	new yCourse
	//false
	//	next Position
	//	xCount + 1
	//	yCount + 1

	//hole Attribute
	int bX	= ball.getBallX();
	int bY	= ball.getBallY();
	int xC	= ball.getXCount();
	int yC	= ball.getYCount();
	//int bXC	= ball.getBallXCourse();
	//int bYC	= ball.getBallYCourse();
	int bA	= ball.getAngle();
	int bS	= ball.getSpeed();

	//Kollision mit Spielfeld
	if( (bX - 1 <= 0) || (bX + 1 >= windowWidth) )
		bA = (bA == 0) ? 180 : 180 - bA;
	if( (bY - 1 <= 0) || (bY + 1 >= windowHeight) )
		bA = (bA == 90) ? -90 : 360 - bA;

	int bXC	= ballXCourse(bS, bA);
	int bYC = ballYCourse(bS, bA);

	//Nächste Position
	if(bXC < 0 && xC == (bXC * -1))
	{
		xC = 0;
		ball.setBallX(bX - 1); 
	}
	else if(bXC == 0)
		//bXCourse DARF NICHT NULL WERDEN - Kommentar für mich 
		ball.setBallX(bX);
	else if(bXC > 0 && xC == bXC)
	{
		xC = 0;
		ball.setBallX(bX + 1);
	}
	
	if(bYC < 0 && yC == (bYC * -1))
	{
		yC = 0;
		ball.setBallY(bY - 1);
	}
	else if(bYC == 0)
		ball.setBallY(bY);
	else if(bYC > 0 && yC == bYC)
	{
		yC = 0;
		ball.setBallY(bY + 1);
	}

	//Variable hochzählen
	xC += 1;
	yC += 1;

	//Attribute setzen
	ball.setAngle(bA);
	ball.setXCount(xC);
	ball.setYCount(yC);
	ball.setBallXCourse(bXC);
	ball.setBallYCourse(bYC);
}

Das Problem was ich jetzt habe ist, weil die Ballgeschwindigkeit immer 1 ist, bekomme ich manchmal nicht unbedingt die besten Werte bei xRichtung und yRichtung raus, desweiteren, wenn ich die BallGeschwindigkeit mit einen Modifikator belege (bspw. *3) bewegt sich der Ball bei 270° sehr langsam um bei 260° übel schnell ... ich weiss jetzt nicht ob das an meinem Code liegt, habe aber rausgefunden, dass das mit der XRichtungbewegung und YRichtungbewegung zusammenhängt:

Code:
int ballXCourse(int ballSpeed, int ballAngle)
{
	return (round_cast<int>(ballSpeed * (cos ((double)(ballAngle * PI / 180)))));
}

int ballYCourse(int ballSpeed, int ballAngle)
{
	return (round_cast<int>(ballSpeed * (sin ((double)(ballAngle * PI / 180)))));
}

Wenn ich bspw. die XBewegung und YBewegung nicht mit einen Zähler belege habe ich ja nur die Möglichkeit, denn Ball in 180, 0, 270, 90, 45, 135, 225 und 315 Grad bewegen zu lassen ...

//Edit:
Also mir geht es darum, dass zu jeden beliebigen Winkel, der Ball sich anders verhält.
Oben das funktioniert so nicht, weil ich einen Denkfehler hatte.
Wenn bspw. bei ballRichtungX 10 rauskommt und bei ballRichtungY 2, dann soll sich der Ball 10mal nach x bewegen, alle 5 Schritte jedoch einmal nach y.
Das habe ich übersehen und ist mir auch gerade klar geworden ...
 
Zuletzt bearbeitet:
weil die Ballbewegung etwas zu langsam ist, deswegen habe ich eine For-Schleife drumgebastelt:
Ich dachte das du falls sich der Ballspeed ändert den Timer neu eerstellst mit weniger mehr ms/s und wie denn zu langsam? min ist jede ms den Timer aufzurufen und das ist schon sehr sehr sehr schnell.
Den Rest schau ich mir am we mal an ich habe heut keine Zeit mehr.

MfG Nik
 
Ach und noch etwas ich weiß nit wie deine mathematischen Fahigkeiten aussehen aber ich hätte das mit einem Richtungsvektor gelöst. Du defnierst dir deinen Ursprung zB.
unten links in der Ecke und ordnest dem Ball objekt einen zweidmesionalen Vektor zu welcher die Richtung beschreibt und nach diesem würd dann immer bewegt. Dies kannst du dann auch machen wenn du 3d Spiele entwickelst und einem 3dimensionalen Vektor.
Ich kann dir ja mal Code liefern falls du dies möchtest.
und für die Spieler Position und Richtungsvektor und bei diesem kannst du z.b. die Länge als Speed des Spielers definieren. Dannn ist meiner meinung nach die Kollision und bewegen performanter und einfacher.
 
Zuletzt bearbeitet:
Zurück