BreakOut - "Rekursive", fehlerfreie Kollisionen

Übersicht BlitzBasic Beginners-Corner

Neue Antwort erstellen

 

PacMani

Betreff: BreakOut - "Rekursive", fehlerfreie Kollisionen

BeitragSo, Nov 13, 2011 14:31
Antworten mit Zitat
Benutzer-Profile anzeigen
Hallo zusammen,

ich habe bereits nach Vorschlägen zu einer vernünftigen / fehlerfreien Breakout-Kollisionserkennung (2D) gesucht, aber keine fehlerfreien bzw. problemlosen Berechnungen gefunden.

Es geht ja wie in jedem Breakout darum, mit einem Ball Steine wegzuhauen, wenn der Ball einen Stein berührt, wird er von diesem natürlich wieder zurückgelenkt.
Allerdings kann es passieren, dass von einem Frame zum nächsten mehr als nur die Kollision mit _einem_ Stein stattfindet, besonders wenn der Ball schnell ist. So könnte er zum Beispiel genau in die Ecke von zwei anliegenden Steinen treffen und dadurch zwei Kollisionen haben. Oder aber er jagt gerade durch einen Tunnel unzerstörbarer Steine mit rasender Geschwindigkeit und kann so vermutlich über 8 Kollisionen haben - es muss also eine rekursive Lösung her, die so lange prüft, bis die Strecke des Balls abgerechnet wurde.

Ich dachte dabei theoretisch an folgendes:
- Alle Steine durchlaufen und die am nahesten liegende Kollision herausfinden
- Den Ball dahin verschieben und die bis dahin bereits abgelaufene Streckenlänge abziehen
- Erneute Ermittlung der nahesten Kollision mit der übriggebliebenen Strecke, die der Ball in diesem Frame noch zurücklegen muss
- Ball wieder dahin verschieben und Streckenlänge abziehen
- Wiederholen so oft, bis die noch zu berechnende Streckenlänge 0 ist.
Es klingt bestimmt schlau, da mit Vektoren zu arbeiten.

Nun liegt meine Schulzeit bezüglich Vektorenrechnung aber schon weit zurück und ich habe nur noch Grundgedanken davon im Kopf. Daher nun letztlich folgende Fragen:
- Was haltet ihr von obigen Lösungsansatz?
- Sollte man ihn mit Vektoren umsetzen oder geht dies auch leichter?
- Könnt ihr mich Stichpunkte bzgl. benötigter Vektoren-Formeln (Kreuzprodukt? Punktprodukt? Ein Dschungel für mich, leider) nennen?
- Umsetzung von Vektoren in Blitz? Mit Type?

Vielleicht bin ich hier auch ein wenig denkfaul, aber ich recherchiere nun schon seit einigen Stunden und fand keine passenden Lösungsansätze Sad

Besten Dank für die Denkhilfen im Voraus,

Gruß,
Pac-Mani
  • Zuletzt bearbeitet von PacMani am So, Nov 13, 2011 17:33, insgesamt einmal bearbeitet

ZEVS

BeitragSo, Nov 13, 2011 14:39
Antworten mit Zitat
Benutzer-Profile anzeigen
Ich weiß nicht, ob der Ball so schnell ist, dass er ganze Blöcke überspringt. Im Zweifelsfall mach einfach die FPS höher.
Die Kollision mit zwei Ecken brauchst du nicht miteinzubeziehen. Im nächsten Frame wird der Ball mit dem nächsten Block kollidieren (meinem empirischen Urteil nach finden sowieso nie zwei Kollisionen gleichzeitig statt. Versuch mal, einen Ball so gegen eine Wand zu werfen, dass er beide Wände gleichzeitig berührt).
Die Vektorrechnung streng nach Newton's dritten:
Zitat:
Kräfte treten immer paarweise auf. Übt ein Körper A auf einen anderen Körper B eine Kraft aus (actio), so wirkt eine gleich große, aber entgegen gerichtete Kraft von Körper B auf Körper A (reactio)

http://de.wikipedia.org/wiki/N...hes_Gesetz
Setze einfach für A den Ball und für B den Block ein und du hast es.

ZEVS
 

PacMani

BeitragSo, Nov 13, 2011 14:42
Antworten mit Zitat
Benutzer-Profile anzeigen
Aber was wenn der Ball in einem Frame zum nächsten Tatsächlich durch die Ablenkung zwei Kollisionen hat?
Ich klaue einfach mal dreist die Bilder einer anderen Seite hierzu:

Ablenkung, wie man sie sich vorstellt:
user posted image

Aber nun befindet sich noch eine Wand (oder ein zweiter Stein) auf dem abgelenkten Weg des Balls. Würde man nicht erneut prüfen, würde der Ball hinter der Wand platziert werden:
user posted image
Korrekt wäre es natürlich nur so:
user posted image

ZEVS

BeitragSo, Nov 13, 2011 14:44
Antworten mit Zitat
Benutzer-Profile anzeigen
Wenn du die FPS nur hoch genug hast, wird der Ball sich in einem Frame nicht schneller bewegen, als ein Block groß ist.

ZEVS
 

PacMani

BeitragSo, Nov 13, 2011 14:46
Antworten mit Zitat
Benutzer-Profile anzeigen
Mein Spiel läuft bereits auf 60 FPS und kann sehr schnell werden. Genauer gesagt soll er theorethisch auch unbegrenzt schnell werden (natürlich nie unbegrenzt, aber eben schon nicht mehr verfolgbar).
Einfach die FPS hochzusetzen würde ich wie ein Rumoperieren am kranken Patienten empfinden.
Vor allem, wenn der PC kurz ruckelt gibt es sofort fehlerhafte Berechnungen. Gerade weil mein Spiel auch auf etwas älteren Rechnern laufen soll die nur 30 FPS schaffen (der Ball bewegt sich dann trotzdem gleich schnell).
Ein altes Breakout-Spiel von 1999 hat das auch geschafft, der mathematische Gedanke ist ja sicherlich auch nicht uninteressant Wink

ZEVS

BeitragSo, Nov 13, 2011 15:04
Antworten mit Zitat
Benutzer-Profile anzeigen
OK, du willst also die harte, mathematische, spannende Tour.
1. Kollisionsberechnung:
Du musst bei der Flugbahn den minimalen Abstand zu allen in Frage kommenden Blöcke berechnen, und schauen, ob dieser kleiner ist, als der Ball. Wenn ja, kommt es zu einer umfassenden Berechnung der Kollisionspunkte. Das Problem ist ja, dass der Bereich, in dem es zur Kollision kommt, ein Rechteck mit abgerundeten Ecken ist, also eine Form, die sich jeder ganzheitlichen mathematischen Berechnung entzieht.
Für die Seiten ist der naheste Punkt zwangsläufig an einem der Enden, wenn es keine Linienkollision gibt, das geht relativ einfach. Bei festgestellter Kollision kannst du mit Strahlensätzen arbeiten, wenn du die Blockkante und Flugbahn als Strahlen/Geraden interpretierst und so die exakte Kollision anhand des Radius' bestimmmen.
Für die Ecken musst du zur Kollisionsprüfung den Abstand Ecke-Flugbahn berechnen. Die Berechnung des exakten Kollisionspunktes wird wohl auf ein Gleichungssystem hinauslaufen, dass du allgemein löst und in das Programm eingibst.
2. Nach Berechnung der Kollisionspunkte getreu Newton's dritten den Ball ablenken.
3. Wenn dir das ganze zu langsam wird (besonders bei vielen Blöcken) kannst du überlegen, ob du evtl. einen räumlichen Index implementierst. Das ist eine Art Karte, wo zu jedem Bereich gespeichert ist, welche Blöcke drin sind. Damit kannst du alle Blöcke, die keinen Bereich mit dem Ball teilen, vorab als nicht kollidierend ignorieren.

Du kannst auch mehr Frames berechnen, als zu anzeigst. Benötigt weniger Zeit, hat aber die selben Prinzipprobleme.

ZEVS

BladeRunner

Moderator

BeitragSo, Nov 13, 2011 15:27
Antworten mit Zitat
Benutzer-Profile anzeigen
Du hast eine Geschwindigkeit v (Pixel pro frame) und den EinheitsVektor Z der in die aktuelle Richtung deines Balles zeigt.
Nun prüfst Du ob zwischen deinem aktuellen Punkt und v*Z ein Block liegt, beginnend beim Startpunkt. Ermittelst Du eine Kollision, wird v und die Distanz zwischen Startpunkt und Koillision gekürzt.
Nun berechnest Du Z neu (und gegebenenfalls die änderung von v) anhand der Kollisionswinkel. Dann wiederholst Du das Spiel bis v 0 erreicht. Dann wird für das nächste Frame V erneut auf die aktuelle Geschwindigkeit gesetzt.
Zu Diensten, Bürger.
Intel T2300, 2.5GB DDR 533, Mobility Radeon X1600 Win XP Home SP3
Intel T8400, 4GB DDR3, Nvidia GF9700M GTS Win 7/64
B3D BMax MaxGUI

Stolzer Gewinner des BAC#48, #52 & #92
 

PacMani

BeitragSo, Nov 13, 2011 17:14
Antworten mit Zitat
Benutzer-Profile anzeigen
Das klingt gut. Wie ermittle ich aber die Distanz zwischen Startpunkt und Kollision, um sie dann von v abzuziehen? Ist vermutlich eine banale Frage, sorry Sad mein Schulwissen liegt leider ziemlich weit zurück.

ZEVS

BeitragSo, Nov 13, 2011 17:15
Antworten mit Zitat
Benutzer-Profile anzeigen
Code: [AUSKLAPPEN]
Sqr((startX-KollisionX)^2+(startY-KollisionY)^2)


ZEVS
 

PacMani

BeitragSo, Nov 13, 2011 17:16
Antworten mit Zitat
Benutzer-Profile anzeigen
Es lag mir auf der Zunge... hatte ich doch sogar schonmal benutzt in einem anderen Spiel.
Tut mir Leid Smile
Aber danke!
 

PacMani

BeitragSo, Nov 13, 2011 19:10
Antworten mit Zitat
Benutzer-Profile anzeigen
Ich habe eben versucht, es umzusetzen.
Allerdings fiel mir dabei ein, dass ich beim Durchlaufen der einzelnen Steine auf jeden Fall die am nahesten liegende Kollision berechnen muss.
Meine Funktion läuft die Steine von oben links nach unten rechts durch.
Bewegt sich der Ball z.B. nach oben links und zwar schneller pro Frame als ein Stein groß ist, so würde er mit Steinen oben links kollidieren, obwohl unten rechts vor diesen Steinen auf dem Kollisionsweg noch weitere Steine liegen, mit dem der Ball eigentlich kollidieren müsste.

Ich dachte mir also, ich ermittle die kürzeste Distanz zu jedem kollidierten Stein.
Aber die Funktion ist grässlich aufwendig, groß und unübersichtlich geworden.

Da fiel mir folgendes ein, was ich auch mal kurz illustrieren kann: Ich durchlaufe das Steinlevelarray einfach von der Position des Balls ausgehend (gelbe Pfeile) in die Richtung in die der Stein fliegt (grün). Dann sollte ich doch definitiv den am nahesten kollidierenden Stein finden, müsste nicht alle Steine durchlaufen (sondern nur einen Teil) und könnte bei der ersten ermittelten Kollision die For-Schleifen verlassen. Sehe ich das richtig oder fällt euch sofort etwas dagegensprechendes ein?
user posted image

ZEVS

BeitragSo, Nov 13, 2011 19:37
Antworten mit Zitat
Benutzer-Profile anzeigen
Nein, wenn du die Wand-Kollision nicht aus den Augen verlierst.
Vielleicht solltest du dir auch alle Blöcke, die überhaupt erreicht werden können (also nicht eingebaut sind) extra speichern. Das sollte weitere Performancesteigerungen bringen.

ZEVS
 

PacMani

BeitragSo, Nov 13, 2011 19:39
Antworten mit Zitat
Benutzer-Profile anzeigen
Du meinst, Positionen, an denen sich keine Steine befinden, auslassen? Daran dachte ich auch schon, ist aufgrund der momentanen Implementierung aber leider zu aufwändig (leere Positionen haben einfach ID = 0).
So eine ähnliche Umstellung habe ich letztendlich auch bei einem Bomberman-Klon gemacht Wink

Die Wände berechne ich separat als erstes.

ZEVS

BeitragSo, Nov 13, 2011 19:46
Antworten mit Zitat
Benutzer-Profile anzeigen
Nein, nicht so ganz.
Du speicherst zu jedem Block, ob er irgendeine freie Fläche hat, mit der der Ball ihn erreichen könnte. Die Berechnung braucht zwar evtl. ihre Zeit, da aber bezogen auf alle Frames nur relativ selten ein Block zerstört wird, kannst du hier Performance sparen. Wenn du nämlich weißt, dass der Ball überhaupt nicht an den Block kommt, sparst du dir die Kollision.


Des weiteren kannst du, wenn du die Flugbahn als lineare Gleichung betrachtest, viele Blöcke daran ausschließen, dass sie zu weit neben der Gerade liegen.

ZEVS
 

PacMani

BeitragSo, Nov 13, 2011 19:50
Antworten mit Zitat
Benutzer-Profile anzeigen
Ok, das wäre natürlich aufwendiger.
Aber die Idee mit nicht erreichenden Blöcken hat mich zu folgende Idee gebracht:
Nur so weit wie die Flugbahn in Höhe und Breite reicht, die Steine zurückverfolgen:
user posted image
Klar, wenn der Ball sehr schnell wird, werden auch wieder (fast exponentiell ansteigend) mehr Blöcke berechnet werden müssen Wink
 

PacMani

BeitragSo, Nov 13, 2011 22:20
Antworten mit Zitat
Benutzer-Profile anzeigen
So, nun habe ich zumindest den Durchlauf der Blöcke und die erste Kollisionsabfrage fertig. Jetzt muss ich es nur noch so umbasteln, dass die Kollisionsprüfung rekursiv mit der verbleibenden Distanz abgefragt wird (man sieht im Screenshot einen fälschlicherweise dezimierten Block, der eigentlich garnicht erreichbar war).

Hier der bisherige noch grauenhafte und nicht optimierte Code:
BlitzBasic: [AUSKLAPPEN]
Function UpdateBall(Instance.Ball)
If Instance\IsSticked Then
GameBallSticked = True ;Für Anzeige der Magneten
Instance\X = PaddleX + Instance\PaddlePosition
Else
;In Richtung der Ballbewegung ausgehend von Ballposition Steine durchlaufen
Local DistanceLeft# = Vector2Length(Vector2MultiplySkalar(Instance\Direction, Instance\Speed))

;Schleifenvariablen berechnen
Local BrickLoopStartX = Floor((Instance\X - SizWalls) / SizBrickFrmWidth)
Local BrickLoopStartY = Floor(Instance\Y / SizBrickFrmHeight)
Local BrickLoopEndX = IIf(Instance\Direction\X > 0, LevelWidth, -1) ;!Könnte noch optimiert werden:
Local BrickLoopEndY = IIf(Instance\Direction\Y > 0, LevelHeight, -1) ;!Nur Steine im Bereich der Bewegung berechnen
Local BrickLoopStepX = Instance\Direction\X / Abs(Instance\Direction\X)
Local BrickLoopStepY = Instance\Direction\Y / Abs(Instance\Direction\Y)

;Blöcke durchlaufen und Kollisionspunkt herausfinden
Local CollisionDetected = False

Local x = BrickLoopStartX
While x <> BrickLoopEndX And CollisionDetected = False

Local y = BrickLoopStartY
While y <> BrickLoopEndY And CollisionDetected = False

If Level(x, y) > KndBrickNone Then
;Steinkoordinaten berechnen
Local BrickXLeft = SizWalls + x * SizBrickFrmWidth
Local BrickXRight = SizWalls + (x + 1) * SizBrickFrmWidth - 1
Local BrickYTop = y * SizBrickFrmHeight
Local BrickYBottom = (y + 1) * SizBrickFrmHeight - 1

;Endpunkte der Bewegungslinie des Balls berechnen
Local BallXEnd = Instance\X + (Instance\Direction\X * Instance\Speed)
Local BallYEnd = Instance\Y + (Instance\Direction\Y * Instance\Speed)

;Vertikale Seiten
If Instance\Direction\X > 0 Then
;Linke Steinseite
If CrossingPoint(Instance\X, Instance\Y, BallXEnd, BallYEnd, BrickXLeft, BrickYTop, BrickXLeft, BrickYBottom) Then
Instance\X = CrossingPointX - SizBallRadius
Instance\Y = CrossingPointY
Instance\Direction\X = Instance\Direction\X * -1
CollisionDetected = True
HitBrick(x, y)
End If
Else
;Rechte Steinseite
If CrossingPoint(Instance\X, Instance\Y, BallXEnd, BallYEnd, BrickXRight, BrickYTop, BrickXRight, BrickYBottom) Then
Instance\X = CrossingPointX + SizBallRadius
Instance\Y = CrossingPointY
Instance\Direction\X = Instance\Direction\X * -1
CollisionDetected = True
HitBrick(x, y)
End If
End If
;Horizontale Seiten
If Instance\Direction\Y > 0 Then
;Obere Steinseite
If CrossingPoint(Instance\X, Instance\Y, BallXEnd, BallYEnd, BrickXLeft, BrickYTop, BrickXRight, BrickYTop) Then
Instance\X = CrossingPointX
Instance\Y = CrossingPointY - SizBallRadius
Instance\Direction\Y = Instance\Direction\Y * -1
CollisionDetected = True
HitBrick(x, y)
End If
Else
;Untere Steinseite
If CrossingPoint(Instance\X, Instance\Y, BallXEnd, BallYEnd, BrickXLeft, BrickYBottom, BrickXRight, BrickYBottom) Then
Instance\X = CrossingPointX
Instance\Y = CrossingPointY + SizBallRadius
Instance\Direction\Y = Instance\Direction\Y * -1
CollisionDetected = True
HitBrick(x, y)
End If
End If
End If

y = y + BrickLoopStepY
Wend
x = x + BrickLoopStepX
Wend

;Ball bewegen
Instance\X = Instance\X + Instance\Direction\X * Instance\Speed * SpeedFactor
Instance\Y = Instance\Y + Instance\Direction\Y * Instance\Speed * SpeedFactor
End If

user posted image

Neue Antwort erstellen


Übersicht BlitzBasic Beginners-Corner

Gehe zu:

Powered by phpBB © 2001 - 2006, phpBB Group