Teil 14: Tiefensortierung

Bisher werden die Objekte im All in der Reihenfolge gezeichnet, in der sie angelegt werden, was zur Folge hat, daß eigentlich jedes Schrottteil sich “vor” unserem Gleiter befindet, also näher am Betrachter.
Eigentlich aber möchten wir doch erreichen, daß sich die kleinen Schrottteile weiter hinten befinden – schließlich haben wir sie ja auch schon langsamer fliegen lassen um ein wenig räumliche Tiefe zu simulieren.
Die STL bietet auch hierfür eine Lösung: std::sort. Der Funktion übergibt man einfach zwei Iteratoren für Anfang und Ende des zu sortierenden Bereichs eines Containers und das war’s. In unserem Fall wollen wir die gesamte Liste von Entities, also m_vecEntities in CEntityManager, sortieren, und rufen dementsprechend std::sort( m_vecEntities.begin(), m_vecEntities.end() ) auf. Zur Sortierung werden einfach die Iteratoren dereferenziert und der < Operator zum Vergleich auf die Objekte angewandt.
Na, wem fällt was auf?
Die Lösung wäre sinnvoll, wenn wir direkt Kopien von CEntity in die Liste pushen würden. Dann könnten wir die < und == Operatoren implementieren, die einfach den z-Wert des übergebenen Objekts mit dem eigenen vergleichen. Derefenziert der Sortieralgorithmus aber nun die Iteratoren, kriegt er nur die CEntity* - und würde damit die Speicheradressen vergleichen. Eine Sortierung nach Adresse im Speicher bringt uns aber nicht viel.
Aber auch dafür hat die STL diverse Lösungsmöglichkeiten vorgesehen. Wir wählen die einfachste und übergeben der sort-Methode als zusätzlichen Parameter eine Methode, die zwei CEntity* miteinander vergleicht. Der veränderte Code mit Aufruf der sort-Methode in CEntityManager sieht dann so aus:
Datei /game/EntityManager.cpp:

// ---------------------------------------------------------------------------
bool CEntityManager::CompareEntities( const CEntity* pEntity1, const CEntity* pEntity2 )
{
  return ( pEntity1->m_fZ < pEntity2->m_fZ );
}
// ---------------------------------------------------------------------------
void CEntityManager::Idle( DWORD dwDeltaTime )
{
  vecEntityPtr::iterator it;
  CEntity* pEntity;
  // delete all entities that were destroyed in the previous frame
  it = m_vecEntities.begin();
  while( it != m_vecEntities.end() )
  {
     pEntity = *it;
     if( pEntity->m_bDestroy )
     {
        delete pEntity;
        it = m_vecEntities.erase( it );
     }
     else
     {
        ++it;
     }
  }
  // sort all active entities
  std::sort( m_vecEntities.begin(), m_vecEntities.end(), CompareEntities );
  // process all active entities
  for( it = m_vecEntities.begin(); it != m_vecEntities.end(); ++it )
  {
     pEntity = *it;
     pEntity->Idle( dwDeltaTime );
  }
}

Um die sort-Methode benutzen zu können, müssen wir ausserdem den algorithm-Header der STL einbinden:
Datei /game/EntityManager.cpp:

#include <algorithm>

Und letztendlich müssen wir die neue CompareEntities-Methode im Header bekanntmachen. Wichtig ist hier, daß die Methode statisch deklariert ist, so können wir sie am einfachsten als Funktionszeiger übergeben:
Datei /game/EntityManager.h:

static bool CompareEntities( const CEntity* pEntity1, const CEntity* pEntity2 );

Für unsere Tiefe legen wir fest, daß sich das Raumschiff des Spielers immer bei z = 0 bewegt. Jetzt müssen wir noch bestimmen, wie "tief" das Raumschiff selbst ist, damit wir sagen können, welche Objekte mit dem Gleiter kollidieren können. Wir sagen hier einfach, daß alles im Bereich z = -1 ... 1 auf der "Tiefe" des Gleiters fliegt und damit kollidieren kann. Wir passen also auch noch die Initialize-Methode unseres Schrotts an, um hier einen z-Wert festzulegen. Der große Schrott soll auf Höhe des Spielers fliegen, so daß er gezwungen ist, diesem auszuweichen oder ihn abzuschießen, während der kleine Schrott weiter im Hintergrund fliegen soll, ohne eine Gefahr für den Spieler darzustellen.
Datei /objects/Scrap.cpp:

  switch( iRandom )
  {
  case 0: // big scrap tiles
  case 2:
     m_fSpeed = 0.8f + (float)( rand() % 40 ) / 100.0f;
     m_usWidth = 50;
     m_usHeight = 50;
     m_fZ = (float)( rand() % 21 ) / 10.0f - 1.0f;
     break;
  case 1: // small scrap tiles
  case 3:
     m_fSpeed = 0.4f + (float)( rand() % 20 ) / 100.0f;
     m_usWidth = 25;
     m_usHeight = 25;
     m_fZ = (float)( rand() % 50 ) / -10.0f - 2.0f;
     break;
  }

Großer Schrott fliegt damit immer im kritischen Bereich z = -1 ... 1, kleiner Schrott im Bereich -2 ... -7.

Leave a Reply

Your email address will not be published. Required fields are marked *