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

Anna

Frischling

  • »Anna« ist der Autor dieses Themas
  • Private Nachricht senden

1

08.09.2013, 21:01

Kapitel 12 Asteroiden hitbox

Hallo liebe Forumbesucher,

Erstmal möchte ich mich entschuldigen, dass ich ein Thema anspreche, zu dem es bereits Forenbeiträge gibt. Allerdings taucht dieses konkrete Problem meist erst auf der 2. Seite irgendwo versteckt auf und wurde nicht gelöst, weshalb ich diesen Beitrag noch einmal getrennt eröffnen möchte.
Als ich versucht habe die Kollision von Spieler und Asteroiden einzubauen bin ich in eine kleine Falle gelaufen. Ich habe die Breite des Raumschiffs versucht aus dem zugehörigen Sprite auszulesen, habe dabei aber die Breite des ganzen Bildes (alle Teile der Animation in einer langen Reihe) ausgelesen. Das erhält man mittels m_pSpritePlayer->GetRect().w, für den richtigen Wert muss man sich erst eine eigene Funktion bauen, die die Breite des Rects m_FrameRect ausliest, ich habe die Breite des Raumschiffs einfach konstant auf 64 gesetzt, nicht schön, aber funktioniert.

So weit alles schön und gut. Die große Frage, die sich mir dann aber gestellt hat war "Wie wurde das eigentlich bei den Asteroiden gemacht?", denn da sollte ja das gleiche Problem auftreten. Bei den Asteroiden passiert aber etwas ganz witziges. Die Breitenzuordnung sieht folgendermaßen aus:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
    void CAsteroid::Init (CSprite *pSpriteAsteroid, float fXPos, float fYPos)
{
.
.
.
m_Rect.w = pSpriteAsteroid->GetRect().w;
m_Rect.h = pSpriteAsteroid->GetRect().h;
.
.
.
} // Init

GetRect wiederum ist eine einfache Funktion des Sprites

C-/C++-Quelltext

1
        SDL_Rect GetRect () {return m_Rect;}

und in der Variable m_Rect sollten eigentlich die Maße des gesamten Bildes geladen werden, also in diesem Fall sollte die Breite des Kometen auf 640 (Gesamtbreite der 10 Bilder hintereinander) und nicht 64 (Breite eines Ausschnitts des Kometenbildes) gesetzt werden.
Lustigerweise passiert das auch, aber nur beim allerersten Kometen.
Das haben sowohl Debugger als auch eine kleine Testausgabe bestätigt:

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void CGame::CheckCollisions ()
{
.
.
.
  while (ItAsteroid != m_AsteroidList.end () )
    {
        // Rect des Asteroiden holen
        RectAsteroid = ItAsteroid->GetRect ();
        cout << RectAsteroid.w;
.
.
.
} // CheckCollision


Wenn man diese kleine Zeile einfügt hat man nach Beenden des Spiels in der Konsole etwas in der Art:
640640640640640640640640640640640640640640640640640640640646406464064640646406464064....
stehen. Das ist jetzt nicht sehr hübsch, aber wer genau hinschaut sieht, dass der erste Komet Breite 640 hat und der 2. (und wenn man es etwas weiter laufen lässt sieht man, dass das auch für alle weiteren gilt) hat Breite 64.
Das lässt sich auch nochmal etwas besser mit dem Debugger überprüfen.

Die große Frage ist jetzt: Wie kommt das Zustande?
1. Was unterscheidet den 1. Kometen von allen weiteren
2. Wo kommt die 64 der späteren Kometen her? Nach meiner Ansicht müsste diese Breite immer bei 640 liegen.

Ich weiß, dass diese Frage nicht zu beantworten ist ohne den kompletten Code des Spiels in Kapitel 12 zu kennen. Da ich aber wirklich keine Ahnung habe wo der Fehler liegt, kann ich auch nicht wirklich mehr Code posten um das Problem deutlicher zu erklären. Unter http://downloads.hanser.de/getDoc.asp?doc_id=21351514131-27 kann man alle Codebeispiele runterladen und im Ordner Quellcode/Visual Studio/Kapitel 12/SDL_Game findet man den Code des gesamten Projekts. Allerdings hoffe ich im Moment eher darauf, dass mir jemand die Frage beantworten kann, der sich bereits diesem Code beschäftigt hat. Sonst ist der Aufwand wahrscheinlich zu groß.

Ich habe mir schon eine ganze Weile den Kopf darüber zerbrochen, aber da dies offenbar ein bug ist, der auch in der 4. Auflage des Buches noch vorhanden ist (oder einfach ignoriert wurde?) ist das Problem vielleicht auch eher subtil. Ich hoffe einfach, dass sich hier ein paar sehr kluge Leute herumtreiben, die mir helfen können :)

Und nochmal um das klar zu machen: Dieses Problem entsteht wenn man den heruntergeladenen Originalcode aus dem Buch ungeändert (abgesehen von der Überprüfungsausgabe) laufen lässt, es ist also kein Fehler, den ich persönlich gemacht habe. Man sieht auch ein Symptom dieses Fehlers, wenn man mal versucht den ersten Kometen abzuschießen. Das klappt nämlich auch, wenn man ein gutes Stück weit weg ist.

Viele liebe Grüße
Anna

2

09.09.2013, 21:03

Überprüfe mal die Variable m_rect aus CSprite vor und nach dem _allerersten_ blitten des Asteroids. Kann sein, dass SDL_BlitSurface() das Rechteck verändert. Nur so kann ich mir erklären, dass das überhaupt funktioniert. Wie ich das Überblicke, müsste die Klasse CSprite dafür eigentlich eine Methode zur Rückgabe vom FrameRect bereitstellen.

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »dr.hallo« (09.09.2013, 21:09)


Anna

Frischling

  • »Anna« ist der Autor dieses Themas
  • Private Nachricht senden

3

10.09.2013, 20:45

Vielen Dank für den guten und schnellen Hinweis dr. hallo!

Es stimmt tatsächlich, dass sich in dem Schritt

C-/C++-Quelltext

1
SDL_BlitSurface (m_pImage, &m_FrameRect, m_pScreen, &m_Rect);

Die Breite von m_Rect von 640 auf 64 ändert.

Dann werde ich demnächst mal versuchen anhand der SDL Dokumentation zu entschlüsseln, was da passiert (wenn das jemand schon weiß und mir Arbeit ersparen will, freue ich mich natürlich :)).
Akut werde ich aber denke ich erstmal eine Funktion einbauen, die FrameRect zurückgibt (brauche ich ja eh für mein Raumschiff) und das Ganze reparieren, denn es tut ja trotz Zauberei nicht genau das, was wir gern hätten.

edit: Das Nachlesen war einfacher als ich dachte. Ich schreibe meine Erkenntnisse mal hier rein, falls es wen interessiert.

http://wiki.libsdl.org/SDL_BlitSurface
Dort wird genau beschrieben was passiert. Das letzte Rectangle in der Übergabe soll das Rechteck auf dem Ziel-surface beschreiben, in das gemalt wird. Das wird jetzt etwas zu groß angegeben (sollte ja eigentlich nur 64 breit sein weil unser Bild auch so groß ist), das macht uns aber nichts, weil die Funktion eh nur x und y ausliest und somit läuft das Malen problemlos.
Spannend ist dagegen diese Zeile in der Erklärung:
"The final blit rectangle is saved in dstrect after all clipping is performed"
Also im Wesentlichen speichert er das Rechteck, in das tatsächlich gemalt wurde, in dstrect, was unserem m_Rect entspricht. Somit ist nach dem Aufruf die Breite von m_Rect natürlich wie gewollt.

Ich hoffe das habe ich jetzt richtig verstanden und erklärt. Falls ihr meine Erklärung nicht versteht, einfach den Link anklicken, da steht alles :).

Sollte man das Problem irgendwo als Fehler im Buch melden? Wenn ja wo? :)

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Anna« (10.09.2013, 21:08)


4

11.09.2013, 14:16

Evtl. kannst du auch dem m_rect gleich beim Laden die korrekten Werte für Breite und Höhe mitgeben.

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void CSprite::Load (const string sFilename, int NumFrames,
                    int FrameWidth, int FrameHeight)
{
    // Bitmap laden
    Load (sFilename);

    // Rect für Animationsphase initialisieren
    m_NumFrames   = NumFrames;
    m_rect.w = m_FrameWidth  = FrameWidth;//<-HIER
    m_rect.h = m_FrameHeight = FrameHeight;//<-UND DA
    m_FrameRect.w = FrameWidth;
    m_FrameRect.h = FrameHeight;
    m_NumFramesX  = m_pImage->w / m_FrameWidth;
} // Load


Dann brauchst du keine extra Funktion und Variable.

Anna

Frischling

  • »Anna« ist der Autor dieses Themas
  • Private Nachricht senden

5

11.09.2013, 18:33

Das stimmt, gerade weil der Wert ja ohnehin bald überschrieben wird.
Dann beschreibt das Rechteck auch konsequent die Fläche, in die wir das Bild malen wollen. Das ist also nicht nur ein Trick sondern wirklich logisch. Gefällt mir, danke :).
Jetzt habe ich fast das Bedürfnis das Ding m_TargetRect und das Rect andere m_SoureRect oder so zu nennen und die nicht-animierte Version entsprechend anzupassen...

6

21.09.2013, 22:29

nur nochmal nebenbei ...

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
void CSprite::Load (const string sFilename, int NumFrames,
                    int FrameWidth, int FrameHeight)
{
    // Bitmap laden
    Load (sFilename);

    // Rect für Animationsphase initialisieren
    m_NumFrames   = NumFrames;
    m_rect.w = m_FrameWidth  = FrameWidth;//<-HIER
    m_rect.h = m_FrameHeight = FrameHeight;//<-UND DA
    m_FrameRect.w = FrameWidth;
    m_FrameRect.h = FrameHeight;
    m_NumFramesX  = m_pImage->w / m_FrameWidth;
} // Load

dieser funktion müssen die einzelnen animationsphasen übergeben werden sprich, wenn der Asteroid 10 verschiedene Animationen in einer länge hat die 64x64 breit sind, so hast du die länge 640.

Es ist kein fehler im Buch, du musst, wenn du die Funktion benutzt so vorgehen:

C-/C++-Quelltext

1
2
Load ("Asteroid.bmp, 10,
                    64, 64)



Die 10 steht für 10 Animationsbilder, schau dir mal genau das Bild Asteroid.bmp an ;). Dort wirst du sehen, das das Bild ingsgesamt 640 Breite und 128 Hoehe besitzt im ganzen.
Nun musst du ja der funktion mitteilen, das es ein animiertes Bild ist, sonst wird die andere funktion Load aufgerufen.
Somit wird in der m_rect variable 64 gespeichert und nicht 640 was dein Ergebnis ja bei der abfrage ist


Gruß Leri :)

7

23.09.2013, 23:04

Ließ nochmal genau den Thread.

Werbeanzeige