Karte anlegen (Arduino Esplora, Part 4)


Eine Figur durch einen leeren Raum zu steuern, ist auf Dauer sehr öde. Man kann nun den Hintergrund zunächst eine Farbe geben, ist aber dennoch sehr eintönig ist. Schauen wir uns andere Spiele an, könnte man meinen, dass alles in der Umgebung in Blöcken unterteilt ist.
Und so wird dies auch in diesem Beispiel umgesetzt. Die Karte wird Blockweise angelegt. Das ermöglicht uns weiterhin nur die Bereiche neu zu rendern, die sich auch geändert haben.

Karten Eigenschaften
Mit der Unterteilung in Blöcken, kann ein Block verschiedene Eigenschaften aufweisen. Hier stellt die '0' die Frei Begehbaren Blocke da, in dem sich die Figur bewegen kann. Der Wert '1' wiederum stellt eine Mauer da, an dem die Figur nicht hindurch gehen kann. Die Fläche eines Blockes ist etwas größer als die der Figur. Daher weist die Kanten länge 16 Pixel mal 16 Pixel auf.


Ordnung ist das halbe Leben
Zunächst muss vorweg etwas Ordnung eingebracht werden. Zwar habe ich bereits mit dem letzten Post die Funktionen zu der Figur in einen eignen Tab/Seite eingesetzt, aber ich bin nicht weiter darauf eingegangen.
Damit nicht zu viel Code auf einer Seite ist, teilen wir die Inhalte in Zugehörigkeiten auf. Somit kommen die Funktionen/Methoden für die Farbe und das Ausfüllen eines Quadrates in einen eignen neuen Tab mit dem Namen 'RenderComponent'. Die Funktionen zur Karte werden unter 'MapComponent' abgelegt und 'FigureComponente' sollte nur noch die Inhalte zur Spielfigur haben.












Das Schreiben von Pixeln
Die Methode mit dem die Farbnummern, die die Farbwerte für die Ausgabe zurückgibt, kommt in den 'RenderComponent'.  Die zuvor verwendete Funktion/Methode drawFigurArray wird zusammengefasst und um weitere Parameter erweitert, die dann drawTile genannt wird. Im späteren Blog Post Teil, wird die Funktion/Methode nicht nur für das Rendern der Figur eingesetzt.

 void drawTile(int relationX, int relationY, byte tileWidth, byte tileHeight, byte tilePic[], boolean mirror) {  
  int index = 0;  
  for(int y = 0; y < tileHeight; y++) {  
   for(int x = 0; x < tileWidth; x++) {  
     int indexTarget = index;  
     if(mirror) {  
      indexTarget = index - x + (tileWidth - x) - 1;  
     }  
     byte colorNumber = tilePic[indexTarget];  
     // Nur Farbe  
     if(colorNumber != 0) {  
      EsploraTFT.drawPixel(relationX+x, relationY+y, mapNumberToColor(colorNumber));  
     }  
     index++;  
   }  
  }  
 }  

 uint16_t mapNumberToColor(byte c) {  
  uint16_t result = ST7735_RED;  
  switch(c) {  
   case(1):{ result = ST7735_BLACK; break; }  
   case(2):{ result = 0xF590; break; } // haut  
   case(3):{ result = 0x81E1; break; } // braun  
   case(4):{ result = 0xC2C2; break; } // hell braun  
   case(5):{ result = 0x8300; break; } // braun gelb  
   case(6):{ result = 0x5406; break; } // gruen  
   case(7):{ result = 0x32A4; break; } // dunkel gruen  
   case(8):{ result = 0xAE91; break; } // hell gruen  
   case(9):{ result = 0x2146; break; } // dunkel grau blau  
   case(10):{ result = 0x31E9; break; } // grau blau  
   case(11):{ result = 0x84B6; break; } // hell blau  
   case(13):{ result = 0xFC08; break; } // orange  
   case(14):{ result = 0xFA8A; break; } // hell rot  
   case(15):{ result = 0xD759; break; } // hell gruen 2  
   default: {  
    result = 0;  
    break;  
   }  
  }  
  return result;  
 }  

Sprite Render Methode ändert sich
Nun sollte auch der Programmcode in 'FigureComponent' angepasst werden. Die Methode 'drawFigure' hatte zum Zeichnen die Methode 'drawFigureArray' und wird nun mit der Methode 'drawTile' aus dem Tab 'RenderComponent' ersetzt. Folgender Code zeigt einen Ausschnitt der Änderung. Wie zu sehen ist, wird nun die Breite und Höhe das Sprite übergeben und der Parameter für 'Clear' entfällt.

…
    if(directionX == 0 && directionY == 1) {
      switch(animStep){
        case(0): { drawTile(relationX, relationY, 10, 16, spriteFigureFrontLeft, false); break; }
        case(1): { drawTile(relationX, relationY, 10, 16, spriteFigureFrontMiddle, false); break; }
        case(2): { drawTile(relationX, relationY, 10, 16, spriteFigureFrontLeft, true); break; }
        default: {  drawTile(relationX, relationY, 10, 16, spriteFigureFrontMiddle, false); break; }
      }
    }
…

Die Karte
Für das Anlegen einer Karte wird ein weiteres Byte Array angelegt. Ein Byte stellt eine Kachel Information da. Wie bereits am Anfang des Posts beschrieben, ist '0' Begehbar und '1' wiederum nicht. Da pro Kachel 16 Pixel mal 16 Pixel groß ist, können in der Breite zehn Kachel Nebeneinander aufgestellt werden. Untereinander werden sechs Kacheln angelegt. Der Restliche Bereich unten bleibt frei für später kommende Spieldaten.

Über die Kacheln und anecken
Die erste Methode 'renderMap' liest das Array ein, dass die Karteninformation auf das Display Zeichnet. Mit 'canEnterArea' wird auf einfache Weise die zu betretende Kachel geprüft, ob diese begehbar ist. Wo die Funktion eingesetzt wird, komme ich in einen späteren Absatz . In einen späteren Absatz gehe beschreibe ich, wo diese Funktion ihren Einsatz findet.
Hier sei Angemerkt, dass die Kollisionsabfrage wirklich sehr simple ist, so dass ein durchlaufen unter Umständen dennoch möglich ist. Eine bessere Lösung zu diesen Thema, gehe ich jedoch erst in einen späteren Post darauf ein.

Zuletzt für das Zeichnen einer Kachel, unternimmt die Methode 'renderMapTile' eigentlich zwei Aufgaben. Sie ruft zu dem Byte eine Farbnummer ab und verwendet diesen Wert, um eine Kachel Ausgefüllt auf dem Display zu zeichnen. Genauere Beschreibungen zu den Funktionen und Variablen findet im Sourcecode.

// Karte
byte mapContent[160] =  { 
  1,1,1,1,1,1,1,1,1,1,
  1,0,1,0,0,0,0,0,0,1,
  1,0,1,0,0,1,0,0,0,1,
  1,0,1,0,0,1,1,1,0,1,
  1,0,0,0,0,0,0,0,0,1,
  1,1,1,1,1,1,1,1,1,1,
};

// Groeße einer Kachel
byte mapTileSize = 16;
// Anzahl Kacheln auf der X Achse
byte mapCountX = 10;
// Anzahl Kacheln auf der Y Achse
byte mapCountY = 6;

void renderMap(int positionX, int positionY, boolean renderAll) {
  byte index = 0;
  for(byte y = 0; y < mapCountY; y++) {
    for(byte x = 0; x < mapCountX; x++) {

      if(((positionX >= (int)(x * mapTileSize) - (int)mapTileSize && positionX <= (int)(x + 1) * (int)mapTileSize && 
          positionY >= (int)(y * mapTileSize) - (int)mapTileSize && positionY <= (int)(y + 1) * (int)mapTileSize)) || 
          renderAll) {
        renderMapTile(x, y, mapContent[index]);
      }
      index++;
    }
  }
}

// Einfache Kollisionsabfrage
boolean canEnterArea(byte positionX, byte positionY) {

  // Kachel Kordinate abrufen
  byte tileX = positionX / 16;
  byte tileY = positionY / 16;

  // Index aus dem Array abfragen
  byte index = (tileY * mapCountX) + tileX;

  // ist das Feld begehbar
  if(mapContent[index] == 0) {
    return true;
  }
  
  return false;
}

// rendert die Kacheln Einfarbig.
void renderMapTile(byte x, byte y, byte mapSegment) {
  
  byte mapTileColorNumber = 0;
  switch(mapSegment) {
    case(1): { mapTileColorNumber = 10; break; }
    default: { mapTileColorNumber = 15; break; }
  }

  EsploraTFT.fillRect(x * mapTileSize, y * mapTileSize, mapTileSize, mapTileSize, mapNumberToColor(mapTileColorNumber));
}

Blockade prüfen
In der 'loop' Funktion wird je nach Ausrichtung die Position um einen hoch oder runter gezählt. Mit der neuen Funktion 'canEnterArea' aus dem Tab 'MapComponent' kann nun verhindert werden, dass die Figur nicht in eine Blockwand laufen kann. Der folgende Code zeigt die Bedingung für Links mit der neuen Funktion und der Parameter Übergabe über die nächste Position.

…
if(buttonLeft && !buttonRight && lastPosX > 0) {
    // nach links und letzte Position Y ist groesser als '0'.
    if(canEnterArea(lastPosX - 1, lastPosY)) {
      lastPosX--;
    }
  }
…

Offenes und ausbessern
Es funktioniert zwar schon, aber so ganz Rund sind die gängigen Funktionen noch nicht. Die kollisionsabfrage funktioniert nicht unter jeder Bedingung und die Figur flimmert. Dennoch sind diese Groben Ausführungen keine Primären Probleme und werden daher später verbessert, sobald diese ein Problem darstellen.
Der Nächste Schritt ist die Arbeitsspeicherauslastung zu verbessern. Denn derzeitig werden noch nicht viele Inhalte angezeigt, aber der Arbeitsspeicher ist mit den jetzigen Inhalten ist fast voll.

Nächster Post: Voller Arbeitsspeicher (Arduino Esplora, Part 5)


Kommentare

Beliebte Posts aus diesem Blog

Arduino Control (Teil 5) - PWM Signal einlesen

RC Fahrtenregler für Lego Kettenfahrzeug

Angular auf dem Raspberry Pi