Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

1

08.09.2012, 19:44

Problem mit dem Abprallverhalten eines Balles an einem Block

Hey Leute, ich bin gerade dabei ein 2D-Spiel mit SFML und C++ zu programmieren. Es soll nach dem alten Spielprinzip von BreakOut funzen, sprich: Ein Ball zerstört Blöcke und der Spieler kann eine Plattform im unteren Bereich des Bildschirms steuern um den Ball aufzufangen (ich denke ihr wisst, was gemeint ist :)).

Die eben angesprochene Plattform, der Ball, sowie die Blöcke haben eine eigene Klasse und besitzen intern Sprites mit einer gewissen Größe.

In einer Memberfunktion des Balls namens move werden die verschiedenen Abprallmöglichkeiten durchgegangen. Hier der Code:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void Ball::move(float speed, float frameTime, Spieler &s, Block &b)
{
    // Kollision mit den Rändern des Fensters
    if(sBall.GetPosition().x <= 10.5f || sBall.GetPosition().x >= 789.5f)
    {
        xAenderung = -xAenderung;
    }
    if(sBall.GetPosition().y <= 10.5f || sBall.GetPosition().y >= 589.5f)
    {
        yAenderung = -yAenderung;
    }

    // Kollision mit dem Spieler
    if((sBall.GetPosition().x + 10.5f >= s.getPositionX() - 100.5f) && (sBall.GetPosition().x - 10.5f <= s.getPositionX() + 100.5f) && (sBall.GetPosition().y + 10.5f >=   s.getPositionY() - 15.5f))
    {
        yAenderung = -yAenderung;
    }

    // Kollision mit dem Block
    // Hier ist das Problem! :(

    sBall.Move(xAenderung * speed * frameTime, yAenderung * speed * frameTime);
    setPosition(sBall.GetPosition().x, sBall.GetPosition().y);
}


Ich übergebe die Referenzen des Spielers und eines Blocks (zur Vereinfachung habe ich erst mal nur einen Block erstellt), sowie des Speed des Balls und die Zeit für einen Frame.
Die zwei Member xAenderung und yAenderung stellen zwei Teile eines Vektors dar, der die Richtung angibt, in die der Ball fliegt.

Das Abprallen von den Seitenwänden hat ja schon mal geklappt, jetzt aber meine große Frage: Wie kann ich das Abprallen von einem Block realisieren. Ich müsste ja theoretisch alle Seiten eines Blocks abfragen und dann die jeweiligen Vektorteile ändern. Aber das würde doch in ungeheure if-Monster ausufern. Ich hab auch mal versucht eine Kollisionbibliothek einzubinden, aber das Problem mit der Fallunterscheidung je Seite des Blocks bleibt ja.

Also: Hat jemand eine gute Idee, wie ich das Problem effizient lösen könnte? Oder wenigstens ein paar Hinweise? Vielleicht hatte jemand schon mal das selbe Problem und hat es gelöst? Ich freue mich auf Antworten.

Euer rabbinator

Nimelrian

Alter Hase

Beiträge: 1 216

Beruf: Softwareentwickler (aktuell Web/Node); Freiberuflicher Google Proxy

  • Private Nachricht senden

2

08.09.2012, 19:56

Sind doch nur 2 if-Abfragen. Rechts/Links (x-Richtung ändern) und Oben/Unten (y-Richtung ändern).
Ich bin kein UserSideGoogleProxy. Und nein, dieses Forum ist kein UserSideGoogleProxyAbstractFactorySingleton.

3

08.09.2012, 20:12

Das Problem ist, dass du gleichzeitig abchecken musst, ob der Ball nicht zu hoch und nicht zu tief ist. Dann wird das doch viel schreibarbeit.

eXpl0it3r

Treue Seele

Beiträge: 386

Wohnort: Schweiz

Beruf: Professional Software Engineer

  • Private Nachricht senden

4

08.09.2012, 23:15

Hmm ich bin kein Spezialist was Kollisions Erkennungen angeht, Google kennt jedoch so einige Beispiele zu diesem Thema (Stichwörter: breakout collision).

Die wohl einfachste Methode für die Kollision, ist eine Punkt-Rechteck Test zu implementieren und dann den Punkt durch einen Radius etwas zu vergrössern. Das Problem wird dann wohl sein, dass der Ball bei hoher Geschwindigkeit/niederiger FPS durch die Blöcke durch tunneliert, was die Kollisionserkennung etwas komplizierter macht.
Wenn man nun mal dieses Szenario ausschliesst, dann muss man eigentlich nur überprüfen ob man beim nächsten Schritt der Punkt im Rechteck des Blocks ist und falls ja, dann muss man den Geschwindikeits Vektor entsprechend der Lage anpassen (ja das braucht dann einen Test für alle vier Seiten) und dann setzt man den Ball an die Stelle, wo der Ball auf dem Block auftreten würde.

Wenn du das nun alles mit Klassen und deren Eigenschaften anstatt mit statischen Werten umsetzt, muss du den Test auch nur einmal schreiben.

Überigens die Logik eines Spieles besteht sehr oft aus vielen if's, das ist normal, es kann jedoch durch geschicktes einsetzten (z.B. Generalisieren) minimiert werden. ;)
Blog: https://dev.my-gate.net/
—————————————————————————
SFML: https://www.sfml-dev.org/
Thor: http://www.bromeon.ch/libraries/thor/
SFGUI: https://github.com/TankOs/SFGUI/

Beiträge: 26

Wohnort: Dort wo mein PC steht

  • Private Nachricht senden

5

08.09.2012, 23:50

Ich habs in meinem Breakout einmal so gemacht (Projekt aus Zeitmangel beendet).
Hat aber nur Behelfsmäßig funktioniert.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
int Brick::Collision(int x, int y, PMASK* mask)
{
    if(isAlive)
    {
        if(check_pmask_collision(this->mask,mask,this->x,this->y,x,y))
        {
            if(this->y>y+(mask->h+ballradius)||this->y<(y-ballradius))
            {
                return 1;
            }
            if(this->x>x+mask->w-ballradius||this->x<x+ballradius)
            {
                return 2;
            }
        }
    }
    return 0;
}

PMASK ist übrigens eine Kollisionsbibliothek(Pixel-Perfekt für SDL und Allegro)
So hab ich geschaut ober der Ball,
unter||über dem Brick ist -> vector.y *= -1
oder links||rechts -> vector.x *= -1
(Das hier ist halt nur die Kollisionserkennung, der Rest steht woanders im code)

Mfg
Caesiumhydroxid
Wer Rechtschreibfehler findet, darf sie behalten ;)

6

09.09.2012, 17:10

Hallo, danke für die hilfreichen Antworten. Das Problem ist leider immer noch nicht gelöst. Es kann nämlich passieren, dass das Programm nicht genau erkennt, auf welcher Seite des Blocks er sich befindet. Ich werd mal eine groß angelegte Google-Befragung durchführen :) Wenn ich was hilfreiches finde, poste ich es natürlich gleich hier.

Grüße, rabbinator

CBenni::O

1x Contest-Sieger

Beiträge: 1 145

Wohnort: Stuttgart

  • Private Nachricht senden

7

09.09.2012, 20:23

Man kann auch die einzelnen Linien testen. Du nimmst den Mittelpunkt des Balles und prüfst, ob der minimale Abstand zu den Linien jeweils kleiner oder gleich dem Radius ist.
Um den Abstand Punkt-Linie zu berechnen (Als Vektor), sollte der folgende (Pseudo-)Code helfen:

Quellcode

1
2
3
4
5
6
7
8
9
Vector2 PointLineDist(Vector2 point, Vector2 linestart, Vector2 lineend)
{
      Vector2 a = lineend - linestart;
      Vector2 b = point - linestart;
      double t = dot(a, b)/(length(a)²);
      if (t < 0) t = 0;
      if (t > 1) t = 1;
      return linestart + a * t;
}

Funktioniert auch bei Linien, die nicht entlang einer Koordinatenachse sind. Sie gibt den Punkt auf der Linie zurück, der dem Punkt am nächsten liegt.

Damit kannst du bestimmen, ob und wenn ja, wo der Ball auf eine Kante des Rechtecks trifft. Um den Ball korrekt abprallen zu lassen, lässt du ihn entlang dem verbindungsvektor zwischen dem mittelpunkt und der funktionwert nach dem einfallswinkel=ausfallswinkel-Gesetz abprallen. Dabei solltest du beachten, dass du dafür immer denjenigen Verbindungsvektor verwendest, der die minimale Länge hat. Damit kollidiert der Ball auch korrekt, wenn er auf eine Ecke zufliegt ;)

Ich hoffe, das hilft dir ein wenig, evtl werde ich noch etwas dazu posten, wie das abprallen am besten funktioniert.

EDIT: war einfacher, als ich dachte. Man teilt den geschwindigkeitsvektor auf, in einen vektor entlang der normalen, einen entlang der orthogonalen dazu, und zieht den ersten teil vom zweiten ab. Funktioniert wunderbar (erklärung dazu siehe Lineare Algebra):

Quellcode

1
2
3
4
5
6
7
// minvec = Kürzester Verbindungsvektor zum rechteck
// speed = geschwindigkeit des balles
Vector2 norm = normalize(minvec); // Normale
Vector2 orth = Vector2(norm.y,-norm.x); // Orthogonale
Vector2 snorm = dot(norm,speed) * norm; // Geschwindigkeitskomponente in normalen-richtung
Vector2 sorth = dot(orth,speed) * orth; // Geschwindigkeitskomponente in orthogonalen-richtung
speed = sorth - snorm; // neue Geschwindigkeit


mfg CBenni::O
Ein Mitglied der VEGeiCoUndGraSonMaWiGeS Bewegung.
42!
Aufräumen kann jeder, nur das Genie überblickt das Chaos!
Metal will never die!
1. Sppro Gamecontest - mein Beitrag

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »CBenni::O« (09.09.2012, 21:18)


Schorsch

Supermoderator

Beiträge: 5 145

Wohnort: Wickede

Beruf: Softwareentwickler

  • Private Nachricht senden

8

10.09.2012, 01:43

Ich habe das mal so gelöst, dass ich eine Abstandsabfrage gemacht habe. Dabei habe ich einfach die Distanz zwischen Ball und Blockmittelpunkt berechnet. Wenn der Block nah genug ist um zu kollidieren, habe ich eine genaue Kollisionserkennung ausgeführt. Dafür habe ich einfach die 4 Seiten betrachtet und die Lage des Balls zu ihr. Dafür guckst du dir einfach den Abstand zwischen Ballmittelpunkt und Seite an.
„Es ist doch so. Zwei und zwei macht irgendwas, und vier und vier macht irgendwas. Leider nicht dasselbe, dann wär's leicht.
Das ist aber auch schon höhere Mathematik.“

CBenni::O

1x Contest-Sieger

Beiträge: 1 145

Wohnort: Stuttgart

  • Private Nachricht senden

9

10.09.2012, 10:23

Das kannst du natürlich machen, ein bounding-sphere test ist allerdings sehr ungenau ;)
Und wie man den Abstand zwischen Ballmittelpunkt und Seite berechnet, hab ich oben beschrieben.

Ich habe es probiert, zu diesem code muss nur die bestimmung des kürzesten abstands hinzugefügt werden, und schon hat man korrektes verhalten für die kollision.
Für ein breakout sollte man allerdings noch eine kleine sache verändern: wenn der ball auf das Paddle trifft, dann sollte er nicht ganz korrekt abgelenkt werden, sondern ein wenig stärker, wenn er näher am Rand auftrifft. Das ist nicht allzuschwer zu erreichen, man kann z.B. den "Auftreffpunkt" ein wenig zur mitte des paddles skalieren, dadurch wird die normale stärker geneigt.

mfg CBenni::O
Ein Mitglied der VEGeiCoUndGraSonMaWiGeS Bewegung.
42!
Aufräumen kann jeder, nur das Genie überblickt das Chaos!
Metal will never die!
1. Sppro Gamecontest - mein Beitrag

10

10.09.2012, 23:58

Hi, danke für die ganzen inspirierenden Antworten; gibt offensichtlich tausend verschiedene Möglichkeiten, an die Sache ranzugehen. Ich habe letztendlich folgende Lösung aus einem Tutorial benutzt (nachzulesen hier: http://aaroncox.net/tutorials/arcade/breakout.pdf): Ihr sucht die Mittelpunkte der "Ballränder", stellt euch also quasi den Ball in einer Art Bounding-Box vor, speichert diese und checkt dann mittels eines einfachen PointInRect-Tests, ob und wenn ja welcher Mittelpunkt im Block "steckt". Hier mal ein bisschen Code:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// Kollision mit den Blöcken
    // Speichere alle Mittelpunkte der Ballränder
    float left_x = posX;
    float left_y = posY + 10.5f;
    float right_x = posX + 20.f;
    float right_y = posY + 10.5f;
    float top_x = posX + 10.5f;
    float top_y = posY;
    float bottom_x = posX + 10.5f;
    float bottom_y = posY + 20.f;

        bool left = false;
    bool right = false;
    bool top = false;
    bool bottom = false;

    if(checkPointInBlock(left_x, left_y, Brick.posX, Brick.posY))
        left = true;
    if(checkPointInBlock(right_x, right_y, Brick.posX, Brick.posY))
        right = true;
    if(checkPointInBlock(top_x, top_y, Brick.posX, Brick.posY))
        top = true;
    if(checkPointInBlock(bottom_x, bottom_y, Brick.posX, Brick.posY))
        bottom = true;

        if(left)
    {
        xAenderung = -xAenderung;
        posX += 5.f;
        setPosition(posX, posY);
    }
    if(right)
    {
        xAenderung = -xAenderung;
        posX -= 5.f;
        setPosition(posX, posY);
    }
    if(top)
    {
        yAenderung = -yAenderung;
        posY += 5.f;
        setPosition(posX, posY);
    }
    if(bottom)
    {
        yAenderung = -yAenderung;
        posY -= 5.f;
        setPosition(posX, posY);
    }


Den pointInRect-test kann man dann z.b. so gestalten:

C-/C++-Quelltext

1
2
3
4
bool checkPointInBlock(float x, float y, float blockX, float blockY)
{
    return ((x > blockX) && (x < blockX + 100.f) && (y > blockY) && (y < blockY + 50.f));
}


Die Werte sind an meine Blöcke angepasst.

Grüße

Werbeanzeige