2016-11-12 3 views
3

Ich poste hier nach einem Versuch auf einem französischen Forum namens OpenClassrooms, aber ohne Antworten, poste ich hier auch.std :: vector Sprite Segmentierung Fehler

Also warne ich, ich bin neu in C++ und SFML, also gibt es wahrscheinlich zehntausend Fehler, und es scheint, dass das Buch, das ich gelesen habe, ein wirklich schlechtes Buch war, also versuche ich das mit Bjarne Stroustrups Buch zu korrigieren.

Mein Problem ist folgendes:

I Projektilen erstellen, wenn ich die Eingabetaste drücken oder Raum (es gibt zwei Spieler auf der gleichen Tastatur). Und jedes Mal, wenn ich es drücke, erstelle ich eine Kopie des Sprites des Projektils für das neue Projektil, und wir legen es in eine std::vector<sf::Sprite>. Das Problem ist, wenn ich das Spiel starte, wenn die beiden Spieler ihre Shoot-Taste (Enter und Space) gleichzeitig drücken (ich meine, solange das erste Projektil sichtbar ist), stürzt das Spiel ab und zeigt Segmentation Fault (core dumped). Um dies zu beheben, habe ich zwei Sprites (eines für jeden Spieler) erstellt und sie für ihre Projektile beeinflusst. Das Problem ist, wenn ihre Angriffsgeschwindigkeit riesig ist, können sie ihr zweites Projektil schießen, bevor das erste verschwindet, so dass die Kollisionen ein Problem haben werden, weil es das gleiche Sprite zweimal gibt ... und das erste wird nicht funktionieren. Um dieses Problem zu lösen, wollte ich einen std :: vector verwenden. Übrigens, ich versuche nicht, dies nur für zwei Spieler zu lösen, ich habe vor, etwas mehr hinzuzufügen, also brauche ich etwas, das zum Beispiel mit 1000 Spielern funktionieren würde (natürlich werde ich das nicht mit 1000 Spielern tun, aber wenn es mit dieser Menge funktioniert, wird es auch für 5 Spieler funktionieren).

Um mein Projektil zu erstellen, verwende ich einen Verweis auf das Objekt Sprite, das ich später dank einer Methode in meiner Klasse Game zeige. Diese Referenz ist der Verweis auf ein Sprite im std :: vector. Ich erkannte auch, dass wenn wir ein erstes Projektil schießen, warten, bis es verschwindet und dann die beiden Spieler schießen lassen, es funktioniert richtig (manchmal stürzt es manchmal auch) ... Ich verstehe nicht warum, aber es ist meistens, wenn ich starte das Spiel, dass es abstürzt.

Hier ist mein Code:

std::vector<sf::Sprite> sprites; 

int main() 
{ 
    Game game; 

    sf::ContextSettings settings; 
    settings.antialiasingLevel = 8; 
    sf::RenderWindow window(sf::VideoMode(1600, 900), "Bombardes", sf::Style::Default, settings); 
    sf::Texture text; 
    if (!text.loadFromFile("resources/projectile.png")) { 
     logg.error("Could not create texture for projectile. Aborting."); 
    } 
    Bombard bomb(50, 150, &game, &pSprite, &movement2); // player class 
    Bombard bomb2(1550, 850, &game, &pSprite2, &movement); 
    std::vector<std::shared_ptr<Projectile>> p; 
    while (window.isOpen()) 
    { 
     sf::Event event; 
     while (window.pollEvent(event)) 
     { 
      switch (event.type) { 
       case sf::Event::Closed: 
        window.close(); 
        break; 
       case sf::Event::KeyPressed: 
        if (event.key.code == sf::Keyboard::Return) { // Player 2 
         auto current2 = std::chrono::steady_clock::now(); 
         auto elapsed2 = std::chrono::duration_cast<std::chrono::milliseconds>(current2 - last2); 
         if (elapsed2.count() >= 1000/bomb2.getAttackSpeed()) { // Time the frequency of shots 
          last2 = std::chrono::steady_clock::now(); 
          if (bomb2.getAmmo() > 0) { // Check if there's still ammo 
           sprites.push_back(sf::Sprite(text)); // New sprite in vector 
           p.push_back(std::make_shared<Projectile>(bomb2.getPos().getX(), bomb2.getPos().getY(), &sprites[sprites.size()-1], &game, bomb2.getProjectileMovement(), bomb2.getPenetration(), 
           bomb2.getSpeed())); // Create the projectile 
           bomb2.fire(); // Remove an ammo 
          } 
         } 
        } 
        else if (event.key.code == sf::Keyboard::Space) { // Player 1 
         auto current = std::chrono::steady_clock::now(); 
         auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(current - last); 
         if (elapsed.count() >= 1000/bomb.getAttackSpeed()) { 
          last = std::chrono::steady_clock::now(); 
          if (bomb.getAmmo() > 0) { 
           sprites.push_back(sf::Sprite(text)); 
           p.push_back(std::make_shared<Projectile>(bomb.getPos().getX(), bomb.getPos().getY(), &sprites[sprites.size()-1], &game, bomb.getProjectileMovement(), bomb.getPenetration(), bomb.getSpeed())); 
           bomb.fire(); 
          } 
         } 
        } 
        break; 
      } 
     } 
    } 
    return 0; 
} 

Natürlich nahm ich einige Teile, würde ich meinen ganzen Code nicht kopieren und einfügen, gibt es nutzlose Dinge. Wenn Sie das Gefühl haben, dass Sie meine Klassen brauchen (Projektil, Spiel, Entität, Bombardierung), werde ich sie veröffentlichen.

Ich denke, es kann Ihnen helfen, das Projektil Konstruktor, um zu sehen:

Projectile::Projectile(int posX, int posY, sf::Sprite *sprite, Game *game, sf::Vector2f direction, double penetration, double speed) { 
    /** @brief Full constructor. 
    @param int posX : The X position on the map. 
    @param int posY : The Y position on the map. 
    @param sf::Sprite *sprite : A pointer to the sprite. 
    @param Game *game : A pointer to the game. 
    @param sf::Vector2f direction : The direction in which the projectile will move. */ } 

Vielen Dank für Ihre Hilfe!

Antwort

2

Da ist schon ziemlich viel los, also hätte ich vielleicht ein paar Dinge verpasst.

Eine Sache scheint seltsam: beide Spieler verwenden den gleichen Zeiger Projectile, wenn sie schießen!

Spieler 1 schießt: ein erstes Projektil ist Heap allokieren und Sie behalten ihre Adresse in . So weit, ist es gut.

Dann Spieler 2 schießt. Sie erstellen ein neues Projektil (mit der richtigen Position und Sprite und so weiter ...) ABER, Sie speichern auch seine Adresse in .

Wenn Sie die Adresse des ersten Projektils nicht irgendwo in Ihrem Code gespeichert haben, wie können Sie dann darauf zugreifen? Wie kann man wissen, ob es sein Ziel erreicht hat (und dann sollte Spieler 1 punkten) oder ob es außerhalb des Bildschirms gegangen ist (und dann können Sie es löschen, um Speicher zu löschen)?

Ich vermute, da ist etwas um ihn herum.Vielleicht sollten Sie versuchen, alle Ihre Projektile in einem std::vector<Projectile*> oder noch besser, einem std::vector<std::unique_ptr<Projectile>> zu speichern. Auf diese Weise (wenn ich den Code richtig verstanden habe) könnte der Spieler mehr als ein Projektil schießen.

(wenn Sie sich fragen über diese unique_ptr Teil, nicht zu fragen nichts dagegen)

Informieren Sie uns informiert, was Sie versucht haben, werden Sie?

+3

* * 'std :: vector ', 'verwenden std :: vector >' (oder möglicherweise 'std :: vector >' - auf diese Weise wird es sein, Sofort geklärt –

+1

@MartinBonner: Ich habe gezögert, es zu erwähnen, da ich nicht zu viel STL-Zeug in die Antwort setzen wollte (er sollte auch 'make_unique' anstelle von neu verwenden) ... Aber du hast Recht, Ich bearbeite die Antwort, um es zu erwähnen: Gute Praktiken werden besser früh gelernt. –

+0

Wie ich schon sagte, habe ich mit einem schlechten Buch gelernt, also ja, es ist besser, gute Dinge zu wissen, wie ich std :: vector <*Something> überall>. < Ich werde versuchen, was Sie gerade sagen, und antworten nach ^^ Und sicher, ich würde gerne mehr über unique_ptr wissen, ich sah es nie: D – FeelZoR

0

Okay, also, nach einem Tag der Arbeit, dank der Hilfe von giant_teapot, denke ich, dass ich das Problem endlich gelöst habe. Aber ich bin mir nicht sicher. Ich experimentiere dieses Thema nicht mehr, aber ich verstehe nicht warum.

Anstatt rohe Zeiger zu verwenden, entschied ich mich für intelligente Zeiger. Es scheint, dass es keinen Bug mehr gibt. Aber warum ? Ich habe strikt nichts geändert. Da ist etwas, was ich nicht verstehe. Aber hier ist der geänderte Code:

std::vector<sf::Sprite> sprites; 

int main() 
{ 
    Game game; 

    sf::ContextSettings settings; 
    settings.antialiasingLevel = 8; 
    sf::RenderWindow window(sf::VideoMode(1600, 900), "Bombardes", sf::Style::Default, settings); 
    sf::Texture text; 
    if (!text.loadFromFile("resources/projectile.png")) { 
     logg.error("Could not create texture for projectile. Aborting."); 
    } 
    Bombard bomb(50, 150, &game, std::make_shared<sf::Sprite>(pSprite), &movement2); 
    Bombard bomb2(1550, 850, &game, std::make_shared<sf::Sprite>(pSprite2), &movement); 
    std::vector<std::shared_ptr<Projectile>> p; 
    while (window.isOpen()) 
    { 
     sf::Event event; 
     while (window.pollEvent(event)) 
     { 
      switch (event.type) { 
       case sf::Event::Closed: 
        window.close(); 
        break; 
       case sf::Event::KeyPressed: 
        if (event.key.code == sf::Keyboard::Return) { // Player 2 
         auto current2 = std::chrono::steady_clock::now(); 
         auto elapsed2 = std::chrono::duration_cast<std::chrono::milliseconds>(current2 - last2); 
         if (elapsed2.count() >= 1000/bomb2.getAttackSpeed()) { // Time the frequency of shots 
          last2 = std::chrono::steady_clock::now(); 
          if (bomb2.getAmmo() > 0) { // Check if there's still ammo 
           sprites.push_back(sf::Sprite(text)); // New sprite in vector 
           p.push_back(std::make_shared<Projectile>(bomb2.getPos().getX(), bomb2.getPos().getY(), std::make_shared<sf::Sprite>(sprites[sprites.size()-1]), &game, bomb2.getProjectileMovement(), bomb2.getPenetration(), bomb2.getSpeed())); // Create the projectile 
           bomb2.fire(); // Remove an ammo 
          } 
         } 
        } 
        else if (event.key.code == sf::Keyboard::Space) { // Player 1 
         auto current = std::chrono::steady_clock::now(); 
         auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(current - last); 
         if (elapsed.count() >= 1000/bomb.getAttackSpeed()) { 
          last = std::chrono::steady_clock::now(); 
          if (bomb.getAmmo() > 0) { 
           sprites.push_back(sf::Sprite(text)); 
           p.push_back(std::make_shared<Projectile>(bomb.getPos().getX(), bomb.getPos().getY(), std::make_shared<sf::Sprite>(sprites[sprites.size()-1]), &game, bomb.getProjectileMovement(), bomb.getPenetration(), bomb.getSpeed())); 
           bomb.fire(); 
          } 
         } 
        } 
        break; 
      } 
     } 
    } 
    return 0; 
} 

Sie werden verstehen, dass ich auch meine Klassen geändert habe. Aber es erklärt immer noch nicht, warum rohe Zeiger das Ganze zum Absturz bringen, während intelligente Zeiger richtig funktionieren.

Nochmals ein riesiges Dankeschön riant_teapot, das mir viele Stunden geholfen hat und mir diese wunderbaren Hinweise gezeigt hat. Danke auch Martin Bonner, der ihm gesagt hat, dass er mir das zeigen soll.

EDIT: Nachdem einige in meinem Code geändert, erkannte ich, warum make_shared funktioniert, während rohe Zeiger nicht funktionieren. Ich habe nicht wirklich verstanden, wie man Smart Pointer benutzt, um ehrlich zu sein, es war so etwas wie "zufälliges Zeug hier geschrieben, weil es funktioniert". Aber schließlich ist das Problem nicht wirklich gelöst. Wir tun nicht wirklich Zugriff auf den Vektor. Dieser Code erstellt eine Kopie meines Sprites und legt seine Adresse im shared_pointer ab. ABER wenn wir in der Projektilklasse das Sprite modifizieren, modifizieren wir nicht das Sprite im Vektor, sondern die Kopie. SO löst es das Problem, indem es ein anderes Problem einführt: Was ist der Gebrauch des Vektors, wenn seine Mitglieder nutzlos sind, weil kopiert?

Für diejenigen, die sich fragen, wie man das Vektorproblem löst, habe ich eigentlich keine Ahnung, die Probleme scheinen ein Vektorproblem zu sein, nicht SFML. Ich muss sie wahrscheinlich mehr studieren. Also mein Code funktioniert, aber ist etwas wirklich schlimmes. Zumindest hat es mir geholfen, die Verwendung von Smart Pointer und std :: make_shared zu lernen.

EDIT 2: Nach dem Gehen im Internet versucht zu suchen, wie man auf die Adresse eines Vektormitglieds zugreifen kann, findet es einen Beitrag auf SO, und merkte, dass ich nicht schlecht dachte. Ich könnte Iterator verwenden, aber es war nicht das, was ich gesucht habe. Und jetzt verstehe ich völlig, warum mein Programm nicht funktioniert hat. Je nachdem, wie das Spiel lief, konnte oder konnte es nicht zum Absturz kommen, aber dieses undefinierte Verhalten war problematisch. Um ehrlich zu sein, hatte ich keine Ahnung, wie man Vektoren benutzt, es war wirklich neu für mich, ich experimentierte mit Dingen, die ich nicht beherrschte. Indem ich den Beitrag über SO gelesen habe, habe ich gelernt, dass der Zugriff auf Vektoradressen gefährlich ist. Wenn sich die Vektorgröße ändert und größer wird als die zugewiesene Größe, können sich Elemente bewegen, wodurch der Zeiger ungültig wird. Das passiert wahrscheinlich, wenn ich Space + Enter drücke und zwei Projektile erschaffe. Es war nur Glück. Also muss ich etwas finden, um dieses Verhalten zu umgehen. Eine Kopie zu machen ist eine Lösung, aber es gibt viele andere, ich werde das Thema ausgraben. Nicht

+0

Wenn Sie zusätzliche Informationen für Ihre Frage haben, fügen Sie sie bitte dem ursprünglichen Beitrag hinzu. Wenn Sie eine andere Frage haben, schreiben Sie sie bitte als neue Frage (Verknüpfung mit dieser, wenn sie den benötigten Kontext hinzufügt). – EJoshuaS