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

26.11.2012, 11:28

Objekte für Kollisionsabfrage sinnvoll anlegen

Hallo zusammen,

ich lese euer Forum schon einige Zeit als Gast und möchte erst mal vielen Dank für die vielen nützlichen Tipps sagen!

Ich selbst versuche mich immer mal wieder an einer "Spieleprogrammierung", je nachdem wieviel Zeit ich erübrigen kann. Momentan ist es also wieder so weit und ich versuche mich mit den verschiedenen Möglichkeiten, Anforderungen und Techniken in C++ und SFML vertraut zu machen.

Mein aktuelles "Projekt": ein Top-Down-View 2D Spiel. Dazu möchte ich eine grobe Spielwelt skizzieren (inkl. Hindernissen wie Steine, Bäume, Mauern, etc.) und meine Spielfigur inkl. Kollisionsabfrage hindurch bewegen. Vom Aufwand her also sehr überschaubar (wie gesagt, es geht mir darum, nach und nach mit den verschiedenen Anforderungen für ein Spiel vertraut zu werden und das Projekt somit wachsen zu lassen).

Prinzipiell funktioniert das auch alles (Hintergrund und Sprites darstellen, Spieler erstellen und bewegen, Objekte anlegen (und IntRects erstellen) und die Kollisionsabfrage funktioniert auch, allerdings sehr umständlich aus der main.cpp heraus. Eine Spielerklasse habe ich allerdings bereits angelegt und auch die Bewegung, etc. dorthin ausgelagert.


Viel Text bisher, ich hoffe ihr lest noch weiter… ;)


Mein Plan ist jetzt, eine Objektklasse anzulegen, in der ich einmalig alle Hindernisse (also die IntRect) der Welt (bzw. des aktuellen levels) anlege und positioniere. Dafür suche ich eine sinnvolle Methode, wenn ich 100+ Objekte habe. Diese brauchen NICHT dynamisch sein, also während des Spiels werden keine erstellt, keine verschoben und auch keine entfernt (lediglich die außerhalb der aktuellen View inaktiv geschaltet).

Für die Kollisionsabfrage will ich dann bei jeder Bewegung (aus der Spielerklasse heraus) den IntRect des Spielers mit allen aktuell aktiven IntRects der Spielwelt vergleichen.


Macht es also Sinn, eine Klasse für die Objekte anzulegen? Oder wäre eine verkettete Liste? Oder eine einfache Struktur? Oder oder oder…


Meiner Überlegung nach müsste jedes Objekt ein „IntRect", eine „bool active" und evtl. einen „pointer Nachfolger" haben.


Ich will hier keine Lösung, aber ich habe schon fast alle o. g. Möglichkeiten ausprobiert und doch wieder verworfen, weil es teilweise sehr ineffektiv gewirkt haben. Daher frage ich jetzt hier nach einer Richtung, in die ich am besten weiterdenken soll :D


Allerdings wäre natürlich ein Beispiel Source-Code von einem kleinen Projekt mit ähnlichen Anforderungen hilfreich ;)

Ich habe auch das Problem, dass ich nicht genau weiß, in welche Klasse ich dann die Kollisionsabfrage legen soll (Spielerklasse, Objektklasse, eigene Klasse?).



Danke schon mal

Rudel


Schrompf

Alter Hase

Beiträge: 1 470

Wohnort: Dresden

Beruf: Softwareentwickler

  • Private Nachricht senden

2

26.11.2012, 11:42

Wenn es nur um ein paar hundert Objekte geht, würde ich das schlicht halten. Gib jedem Deiner Objekte ein Kollisionsrechteck mit und prüfe nur bei Bewegung gegen die Rechtecke aller Objekte. Die Objekte würde ich in einem banalen std::vector ablegen, eine Liste (auch eine selbstgebaute) ist in praktisch allen Fällen eine schlechte Idee.
Häuptling von Dreamworlds. Baut aktuell an nichts konkretem, weil das Vollzeitangestelltenverhältnis ihn fest im Griff hat. Baut daneben nur noch sehr selten an der Open Asset Import Library mit.

3

26.11.2012, 12:08

Danke für die schnelle Antwort.

Natürlich habe vor, dass später weiter auszubauen. Also Objekte, mit denen interagiert werden kann (Schränke, Kisten, Türen, etc.).
D.h. ich werde dann (eigentlich jetzt schon) auch unterschiedliche Datentypen pro Objekt benötigen, da hilft mir doch ein std::vector nicht wirklich weiter?!

Schrompf

Alter Hase

Beiträge: 1 470

Wohnort: Dresden

Beruf: Softwareentwickler

  • Private Nachricht senden

4

26.11.2012, 12:37

Für sowas ist dann eigentlich Vererbung gedacht. Du erfindest eine Basisklasse, die nur die Methoden und Attribute (in Deinen Worten: Datentypen) hat, die wirklich alle Objekte in Deiner Spielwelt gemeinsam haben. Das sind meist nicht wirklich viele. Ich würde folgendes empfehlen:

Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class BasisObjekt {
protected:
  /// Konstruktor mit Übergabe der Spielwelt (*)
  BasisObjekt( Welt* welt);
  
public:
  /// Destruktor
  virtual ~BasisObjekt();

  /// Lässt das Objekt seine Arbeit tun
  virtual void Arbeiten(); 

  /// Zeichnet das Objekt
  virtual void Zeichnen( ZeichenProzess& prozess) const;

protected:
  /// Die Welt, in wir uns befinden
  Welt* mWelt;

  /// wo wir uns befinden
  Vektor2D mPosition;
};


Von dieser Klasse könntest Du alle Objekte in Deiner Spielwelt ableiten: Monster, Spieler, Türen, Bäume, wasweißichnoch. Irgendwann kommt der Punkt, an dem man sich mit Ableitungen mehr Aufwand macht als nötig, dann empfehle ich zusätzlich einen komponenten-basierenden Ansatz, aber das ist noch lange hin. Und zurück zum Thema "Kollision": in die Basisklasse würde ich auch die Kollisionsform packen, vorausgesetzt, die allermeisten Deiner Objekte kollidieren auch. Wenn nur ein paar Deiner Objekte kollidieren, dann hat es den Nachteil, dass alle Deine Objekte etwas mit sich herumschleppen, dass die meisten nicht brauchen. In dem Fall bietet sich dann eine Ableitung von einer Schnittstelle "KollidierendesObjekt" an oder das Hinzufügen einer Komponente, die Kollisionsform und Verhalten bei Kollision regelt.

Hier kommen wir aber schon in den Bereich der Software-Architektur, bei dem man sich hervorragend streiten kann. Du wirst also von anderen Leuten evtl. andere Empfehlungen bekommen.
Häuptling von Dreamworlds. Baut aktuell an nichts konkretem, weil das Vollzeitangestelltenverhältnis ihn fest im Griff hat. Baut daneben nur noch sehr selten an der Open Asset Import Library mit.

5

26.11.2012, 13:46

Ich tue mich mit den Begriffen leider etwas schwer. Ich hatte zwar zwei Semester C/C++ während meines E-Technik-Studiums, aber das ist auch schon ne Weile her… Aber mit „Datentypen" für ein Objekt meinte ich wirklich die Typen IntRect (für die Kollisionsgeometrie) und eine bool’sche Variable, die anzeigt, ob das Objekt aktiv ist und beachtet werden muss.



Also eine Basis-Klasse für Objekte und je eine abgeleitete Klasse für den Spieler (welcher ja auch ein Objekt ist) und für meinetwegen „unbewegliche Gegenstände aller Art (uGaA)" hört sich schon mal sinnvoll an.

Dann würde ich so vorgehen, dass ich in der uGaA-Klasse eine Init()-Methode erstelle, die einmalig ausgeführt wird und alle uGaA anlegt, die in dem Level vorkommen.


Wenn ich das dann aber so mache:


C-/C++-Quelltext

1
2
3
4
5
6
7
8
uGaA::uGaA(int x0, int y0, int w, int h, bool active) 
{ … } 

void uGaA::Init() 
{ 
   uGaA g1(x0, y0, w, h, true); 
   uGaA g2… 
}



Und die Kollisionsabfrage aus der Spieler-Klasse aufrufe, nämlich nachdem ich das Event für links, rechts, hoch, runter erkannt habe; WIE komme ich dann an die Adressen der erstellten Objekte? An der Stelle stehe ich voll auf dem Schlauch :(


Daher war ja meine Idee, dass ich mir einen Container erstelle, in dem alle Objekte aufgelistet sind und ich meine Spielerposition über einen Iterator mit jeder einzelnen aktiven Objektposition vergleiche.


Also (im Pseudo-Code):


C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Spielerklasse::Bewegung() 
{  
    if(Taste.Links) 
    {
       if(Spieler.Kollision()==false) ^
       {
           Spieler.move(links);
… }}}
 
bool ???::Kollision()
{
   bool koll;
 
   for(int i = 0; i++; i<=Anzahl) 
         koll = Spieler.intersects(g[i]);
 
   return koll;
}



Ich muss leider im Pseudo-Code schreiben, da ich nicht daheim am PC bin ich die Syntax nicht komplett auswendig kann :( Außerdem passt vieles Typen-mäßig nicht, ich weiß, aber auf die schnelle im Editor schreiben bekomme ich auch nicht hin. Es vermittelt wohl aber, was ich vorhabe und dass das so nicht klappt ?(

Schrompf

Alter Hase

Beiträge: 1 470

Wohnort: Dresden

Beruf: Softwareentwickler

  • Private Nachricht senden

6

26.11.2012, 14:13

Das stimmt, das klappt so nicht. Die Aufgabe, alle uGaAs anzulegen, hat nicht die uGaA-Klasse. Eine Spielwelt besteht aus Spieler, Monstern, Hindernissen. Wer also sollte die uGaAs anlegen? Richtig: die Spielwelt. In C++ ist es sehr wichtig, sich bei langlebigen Objekten klarzumachen, wem sie gehören. In diesem Fall ganz einfach: der aktuelle Spielablauf hat eine Spielwelt, die Spielwelt hat Spieler, Monster, Hindernisse.

Und das Stichwort "langlebig" besagt auch, was noch an Deiner Überlegung falsch ist. Wie lange leben denn die Hindernisse in Deiner Spielwelt? Meistens so lange wie die Spielwelt selbst. Wenn Du am Anfang eine Spielwelt erstellst, also von Platte lädst oder live mit Hindernissen und dem Spieler bevölkerst, dann werden die BasisObjekte erzeugt. Und am Ende, wenn der Spielablauf fertig ist, wird die Spielwelt gelöscht. Und die Spielwelt räumt dann auch alle Objekte ab. Vielleicht kommen zwischendurch noch neue Objekte hinzu, oder manche Objekte werden vorzeitig gelöscht, aber das ist nebensächlich. Die längste mögliche Lebensdauer ist hier von Interesse. Demzufolge mein Vorschlag:

Quellcode

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
class Spielwelt
{
  /// Konstruktor
  Spielwelt() 
  { 
    // hier kannst Du für den Anfang erstmal die Hindernisse und den Spieler erzeugen 
  }
  /// Destruktor
  ~Spielwelt() 
  { 
    // alle Objekte abräumen
    for( BasisObjekt* obj : mObjekte )
      delete obj;
  }

  /// Fügt ein neues Objekt hinzu
  void AddObjekt( BasisObjekt* obj ) { mObjekte.push_back( obj); }
  
  /// Lässt die Spielwelt arbeiten. Aktuell: alle Objekte kommen einmal dran
  void Arbeiten() 
  {
    for( BasisObjekt* obj : mObjekte )
      obj->Arbeiten();
  }

  /// Zeichnet die Spielwelt
  void Zeichnen( ZeichenProzess& prozess ) const
  {
    for( BasisObjekt* obj : mObjekte )
      obj->Zeichnen( prozess);
  }

protected:
  // alle Objekte in der Spielwelt
  std::vector<BasisObjekt*> mObjekte;
};


In Deiner Lösung oben legst Du die Objekte lokal an, sie werden automatisch abgeräumt, wenn der aktuelle Gültigkeitsbereich (also die Funktion Init()) zu Ende ist. Das sind sehr kurzlebige Objekte. Wir brauchen aber langlebige Objekte. Die Spielwelt und die Objekte darin müssen ja viele Frames hintereinander aktiv sein und gezeichnet werden. Also legen wir sie per new an:

Quellcode

1
2
3
4
5
void BevoelkereDieWelt( Spielwelt& welt )
{
  welt.AddObjekt( new SpielerObjekt( position, ...));
  welt.AddObjekt( new UgaaObjekt( position, typ, ...));
}

Das ist ein Beispiel, also bitte nicht über-interpretieren. In jeder Zeile wird ein neues Objekt des benannten Typs erzeugt, es wird mit den gegebenen Parametern konstruiert (also der Konstruktor aufgerufen) und dann wird der Zeiger des Objekts an die Welt übergeben. An dieser Stelle geht das Objekt in den Besitz der Spielwelt über. Das steht dort nirgends, das kann C++ nicht leisten. Es ist eine Vereinbarung, die Du mit Dir selbst triffst: sobald ich ein Objekt an Spielwelt::AddObjekt() übergeben habe, gehört es nicht mehr mir, sondern der Spielwelt. Und die kümmert sich am Ende auch um die saubere Aufräumung.

Diese Lösung hat übrigens auch den Vorteil, dass der Spieler in seiner Arbeiten()-Funktion die Welt fragen kann, ob denn da ein Hindernis in seiner Wunsch-Bewegung ist. Jedes einzelne UGAA kann nur von sich selbst prüfen, ob eine Kollision mit einem gebenenen Rechteck erfolgt. Nur die Spielwelt kennt alle Objekte und kann demzufolge allgemeine Aussagen darüber treffen, ob eine Bewegung möglich ist.

Bleib dran! Du wirst mit der Zeit - also im Laufe der nächsten Jahre, ein paar Wochen reichen da nicht - ein Gefühl dafür bekommen, wie man die jeweilige Aufgabe in Klassen, Funktionen, Strukturen usw. aufteilt. Man neigt am Anfang eh dazu, nur wenige Klassen zu basteln und die dafür mit Funktionalität vollzustopfen. Ich kenne aber keine Methode, Dich davon abzuhalten... ich glaube, Du wirst es einfach ausprobieren müssen und nach und nach lernen, welche Strategien besser funktionieren als andere.
Häuptling von Dreamworlds. Baut aktuell an nichts konkretem, weil das Vollzeitangestelltenverhältnis ihn fest im Griff hat. Baut daneben nur noch sehr selten an der Open Asset Import Library mit.

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Schrompf« (26.11.2012, 14:18)


7

26.11.2012, 15:12

ahh ja da wird so einiges klar... 8o

Zitat

Bleib dran! Du wirst mit der Zeit - also im Laufe der nächsten Jahre, ein paar Wochen reichen da nicht - ein Gefühl dafür bekommen, wie man die jeweilige Aufgabe in Klassen, Funktionen, Strukturen usw. aufteilt. Man neigt am Anfang eh dazu, nur wenige Klassen zu basteln und die dafür mit Funktionalität vollzustopfen. Ich kenne aber keine Methode, Dich davon abzuhalten... ich glaube, Du wirst es einfach ausprobieren müssen und nach und nach lernen, welche Strategien besser funktionieren als andere.

ja das ist ein langwieriger Prozess, aber es macht ja auch viel Spaß, wenn man sich kleine Ziele setzt und dann auch Erfolge hat. Das anlegen neuer Klassen ist immer eine Hemmschwelle, da es ja die Komplexität im ersten Moment deutlich erhöht, ist aber auf lange Sicht natürlich sinnvoll, um die Übersicht zu behalten... Wenn man dann den ganzen Syntax im Schlaf beherrscht, wird es hoffentlich angenehmer ;)

vielen Dank, ich werde heute/morgen mal deine Vorschläge umsetzen und mich dann sicherlich nochmal mit Rückfragen hier melden ;)

NachoMan

Community-Fossil

Beiträge: 3 885

Wohnort: Berlin

Beruf: (Nachhilfe)Lehrer (Mathematik, C++, Java, C#)

  • Private Nachricht senden

8

26.11.2012, 15:20

C-/C++-Quelltext

1
2
3
4
5
6
7
8
9
bool ???::Kollision()
{
   bool koll;
 
   for(int i = 0; i++; i<=Anzahl) 
         koll = Spieler.intersects(g[i]);
 
   return koll;
}

das wäre übrigens eine schlechte Idee, weil du eigentlich nur auf die Kollision beim letzten Element prüfst, weil du die vorherigen Werte überschreibst
"Der erste Trunk aus dem Becher der Erkenntnis macht einem zum Atheist, doch auf dem Grund des Bechers wartet Gott." - Werner Heisenberg
Biete Privatunterricht in Berlin und Online.
Kommt jemand mit Nach oMan?

Werbeanzeige