Du kommst hier nicht vorbei (Arduino Esplora, Part 6)


Sicherlich habt ihr entweder am Programcode oder beim Testen der Spielfunktionen bemerkt, dass die Kollisionsabfrage nur bedingt funktioniert. Sie ist zwar einfach, aber hier fehlt die Einschränkung, dass man sich nur von Block zu Block bewegen kann. Offen gestanden war ich kein Fan davon, das sich die Figur weiter bewegt bis der nächste Feld oder Kachel erreicht wurde.
Zu dem Thema Spieleprogrammierung und Kollisionsabfrage für 2D Spiele, können verschiedene Lösung im Internet gefunden werden. Ein Beispiel wird hier auf spieleprogrammierer.de/wiki beschrieben, wie man mit Geometrischen Objekten die Kollision Abfragen kann.


Die simple Form für die Kollisionserkennung ist das Verwenden von zwei Rechtecken. Im folgenden Code zeigt die Methode die wesentliche Abfrage von überschneidenden Rechtecken.

 // Kachel Position mit zukuenftiger Position der Figur abgeleichen,  
 // durch ansetzten von Rechtecken und ob diese sich ueberschneiden.  
 boolean checkCollide(byte positionX, byte positionY, byte mapOffsetX, byte mapOffsetY) {  
  if(positionX < mapOffsetX + mapTileSize &&  
     positionX + 10 > mapOffsetX &&  
     positionY < mapOffsetY + mapTileSize &&  
     positionY + 16 > mapOffsetY)  
   {  
    // DEBUG: Nur fuer debug und visuelle kontrolle  
    EsploraTFT.drawRect(positionX, positionY, 10, 16, 0xFA8A);  
    return false;  
   }  
  return true;  
 }  

Die Abfrage reicht jedoch nicht aus, um zu verhindern, dass die Figur wieder durch die Wand geht. Oft müssen auch übereinander oder nebeneinander liegende Kacheln zusätzlich geprüft werden. Für einen späteren Blogpost wird die Kachelgröße Reduziert von 16x16 auf 8x8. Spätestens dann wird die jetzige Abfrage erforderlich sein, alle Hindernisse zu erkennen. Das war leider nicht ganz ohne und zugegeben habe ich daran relativ viel Zeit damit verbracht, die Kollisionen durch zu debuggen.

 // Prüfen, ob in diesem Bereich sich bewegt werden kann.
boolean CanEnterArea(int positionX, int positionY) {
  boolean resultColide = true;
  
  // umliegende Kacheln auf hindernis prüfen
  // wenn hoch oder runter
  if(directionX == 0) {
    
    for(uint8_t i = 0; i < 3; i++) {
      
      int tileX = (positionX + (collisionTiles[i] * mapTileSize)) / mapTileSize;
      int tileY = -1;
 
      int tileYTemp = tileY;
      int positionYShift = 0;
 
      while(tileY == tileYTemp && tileY != 0) {
        if(directionY == -1) { 
          tileY = (positionY + directionY + positionYShift) / mapTileSize; 
          }
        else { tileY = (positionY + directionY + 16 + positionYShift) / mapTileSize; }
 
        positionYShift += mapTileSize * directionY;
      }
      
      resultColide = checkCollideNeighbor(positionX, positionY + directionY, tileX, tileY);
 
      if(!resultColide) {
        break;
      }
    }
  }
 
  // wenn links oder rechts
  if(directionY == 0) {
 
    for(uint8_t i = 0; i < 3; i++) {
      int tileX = positionX / mapTileSize;
      int tileY = (positionY + (collisionTiles[i] * mapTileSize)) / mapTileSize;
      
      int tileXTemp = tileX;
      int positionXShift = 0;
 
      while(tileX == tileXTemp) {
        if(directionX == -1) {  tileX = (positionX + directionX + positionXShift) / mapTileSize; }
        else { tileX = (positionX + directionX + 10 + positionXShift) / mapTileSize; }
        
        positionXShift += mapTileSize * directionX;
      }
  
      resultColide = checkCollideNeighbor(positionX + directionX, positionY, tileX, tileY);
  
      if(!resultColide) {
        break;
      }
    }
  }
  
  return resultColide;
}
 
// Laedt aus dem Flashspeicher die Kachelelemente ab und 
// prueft die Kollision mit neben anliegende Kacheln.
// Verhindert speziel den Fehhler zwischen zwei Kacheln, nur eine zu pruefen.
boolean checkCollideNeighbor(int positionX, int positionY, int tileX, int tileY) {
 
  boolean resultColide = true;
 
  int mapOffsetX = tileX * mapTileSize;
  int mapOffsetY = tileY * mapTileSize;
 
  int indexStart = (tileY * mapTileCountX) + tileX;
  // hole die content Nummer ab um die kollisionsart zu bestimmen
  byte bTile = pgm_read_byte_near(mapContent + indexStart);
 
  if(bTile == 1 && resultColide) {
 
    // DEBUG: Nur fuer debug und visuelle kontrolle
    EsploraTFT.drawRect(mapOffsetX, mapOffsetY,  mapTileSize, mapTileSize, 0xFA8A);
    resultColide = checkCollide(positionX, positionY, mapOffsetX, mapOffsetY);
  }
 
  return resultColide;
}
 

Nun eckt die Figur in positiven Sinne überall an und kann sich nicht mehr wie ein Geist durch die Wand bewegen. In einen späteren Post wird die Kollisionsabfrage auch für Türen verwendet, um z.B. einen Kartenwechsel auszulösen.
Für Debug und Demo Zwecke, werden die Rechtecke mit eingezeichnet, die visuell die Kollision abbilden.


Der Clip zeigt die ungenaue Kollisionsabfrage, wie sie zuvor war. Wie bereits beschrieben, war diese simple und schnell umgesetzt.


Mit der implementieren der Abfrage von überschneidenden Rechtecken sieht das Ergebnis besser aus.




Nächster Post: Ich packe in  meinen Rucksack (Arduino Esplora, Part 7)

Zu guter letzt der gesamte Programmcode auf Github

Github - BlogPost_06_BetterCollision

Kommentare

Beliebte Posts aus diesem Blog

Arduino Control (Teil 5) - PWM Signal einlesen

Angular auf dem Raspberry Pi

RC Fahrtenregler für Lego Kettenfahrzeug