Von Händlern, Kisten und Münzen (Arduino Esplora, Part 8)


Ok, so richtiger Handel wird hier nicht stattfinden. Dafür reicht der Speicher nicht. Oder? In erster Linie sollen nur Grund Funktionen Umgesetzt werden. Ziel wird sein, wenn die Figur vor dem Händler oder einer Kiste steht, dann sollte sich der Inhalt Zeigen. Anschließend kann ein Objekt Ausgewählt und in die eigene Tasche übertragen werden. Leider passt das nicht alles in einen Blogpost, so dass der Inhalt mit der Waren Anzeige in einem späteren Post kommt.

Anforderung
Beim Händler können Gegenstände erworben werden und diese in Kisten abgelegt werden. Das erfordert einige mehr Programmcodezeilen und daher muss an der Stelle wieder eine neue Seite  mit dem Namen 'TraderComponent' angelegt werden.


Am Anfang werden die Werte für Händler und Kisten hinterlegt, die später über den Flashspeicher abgerufen werden. Die Münzen werden hier ebenfalls abgelegt als Funktionsvariable, wird aber erst in einen späteren Post weiter behandelt. (im Folgender Programmcode sind Kommentare und Bilddaten gekürzt, ggf. schaut ihr am besten in die Github Sourcen)

 // # Coins, im Besitz  
 int16_t coins = 25;  
 int16_t lastStateCoins = 0;  
 // # Common Text   
 // Begruessungstext (Sollte immer verschieden sein.)  
 const PROGMEM char traderStartText[] = "Hallo, was darf ich ihnen verkaufen?";  
 // Wenn zu wenig Muenzen zum Kaufen da sind  
 const PROGMEM char traderNotEnough[] = "Du hast nicht genug Muenzen.";  
 // Frage zum Kauf  
 const PROGMEM char traderYouWantToBuy[] = "Kaufen?";  
 // # Common Sprite  
 // Bild vom Handler / Die Farbe des Shirts, kann veraendert werden.  
 const PROGMEM byte traderSpriteFrontMen[160] = { … };  
 const PROGMEM byte traderSpriteFrontWomen[160] = { … };  
 const PROGMEM byte boxSpriteFront[100] = { … };  
 const PROGMEM byte coinSpiteIcon[49] = { … };  
 // # TRADER  
 // temp Variablen zum zwischen laden.  
 char traderName[1];  
 char traderdescription[1];  
 byte traderItems[4];  
 // '0' bedeutet immer nicht belegt.  
 // #######################################  
 // ID 1  
 // Name des Handlers  
 const PROGMEM char trader01Name[5] = "Surie";  
 // Kurze Beschreibung  
 const PROGMEM char trader01Description[11] = "Verkaeferin";  
 // Dinge zum verkauf  
 const PROGMEM byte trader01Items[4] = { 2, 0, 0, 0 }; // 2 = Kamera  
 // 0 = Taschenplaetze werden wie angegeben befullt.  
 // Stellen werden Stellenweise in Bit herausgenommen  
 byte trader01ItemsClear = 0;  
 // # Box  
 // '0' bedeutet immer nicht belegt.  
 // #######################################  
 // ID 1  
 // Name des Handlers  
 const PROGMEM char box01Name[11] = "Meine Kiste";  
 // Kurze Beschreibung  
 const PROGMEM char box01Description[25] = "Dinge die man so braucht.";  
 // Dinge zum verkauf  
 const PROGMEM byte box01Items[4] = { 3, 0, 0, 0 }; // 3 = Foto  
 void memCopyItems(byte arrayContent[], byte traderItemsClear) {  
   if(traderItemsClear == 128) {  
   traderItemsClear-= 128;  
   traderItems[0] = 0;  
  }  
  else { traderItems[0] = pgm_read_byte_near(arrayContent + 0); }  
  if(traderItemsClear >= 64) {  
   traderItemsClear-= 64;  
   traderItems[1] = 0;  
  }  
  else { traderItems[1] = pgm_read_byte_near(arrayContent + 1); }  
  if(traderItemsClear >= 32) {  
   traderItemsClear-= 32;  
   traderItems[2] = 0;  
  }  
  else { traderItems[2] = pgm_read_byte_near(arrayContent + 2); }  
  if(traderItemsClear >= 16) {  
   traderItemsClear-= 16;  
   traderItems[3] = 0;  
  }  
  else { traderItems[3] = pgm_read_byte_near(arrayContent + 3); }  
 }  
 void drawTrader(int16_t traderId, int16_t positionX, int16_t positionY) {  
  if(!mapFigureRerender) {  
   return;  
  }  
  mapFigureRerender = false;  
  switch(traderId) {  
   case(1): { // Farbe des Haenderls/in  
    spriteHairColor1 = 0xEEEC; spriteHairColor2 = 0xE662; // hell Braun 1, hell braun 2  
    spriteShirtColor1 = 0xD69A; spriteShirtColor2 = 0xB596; // hell grau, grau  
    spritePantsColor1 = 0x0418; spritePantsColor2 = 0x0312; // Blau 1, blau  
    memCopy(traderSpriteFrontWomen);             // sprite einer Weiblichen figur  
    memCopyItems(trader01Items, trader01ItemsClear);     // Taschen Inhalt  
    break;  
   }  
   case(2): { // Farbe des Haenderls/in  
    spriteHairColor1 = 0xD615; spriteHairColor2 = 0xBD30; // hell Braun 1, hell braun 2  
    spriteShirtColor1 = 0xD69A; spriteShirtColor2 = 0xB596; // hell grau, grau  
    spritePantsColor1 = 0x0418; spritePantsColor2 = 0x0312; // Blau 1, blau  
    memCopy(traderSpriteFrontMen);  
    break;  
   }  
   default: { break; }  
  }  
  drawTile(positionX, positionY, 10, 16, tempArray, false);  
 }  
 void drawBox(int16_t boxId, int16_t positionX, int16_t positionY) {  
  switch(boxId) {  
   case(1): {  
    boxColor = 0xDCFE;  
    break;  
   }  
   default: { break; }  
  }  
  memCopy(boxSpriteFront);  
  drawTile(positionX, positionY, 10, 10, tempArray, false);  
 }  
 void drawCoinsStatus(bool redraw) {  
  if(coins != lastStateCoins || redraw) {  
   EsploraTFT.fillRect(2, 2, 30, 9, mapNumberToColor(1));  
   memCopy(coinSpiteIcon);  
   drawTile(3, 3, 7, 7, tempArray, false);  
   writeValue(12, 3, coins, false);  
   lastStateCoins = coins;  
  }  
 }  

Der Händler oder Händlerin sollten für die Kollisionsabfrage den selben Raum einnehmen, wie die eigene Spielfigur. Damit dies funktioniert und der Händler nicht wie ein Karten Block (Kachelgröße) registriert wird, ist eine kleine Erweiterung an der Methode "CanEnterArea" mit "checkCollideOther" notwendig. Etwas abwegig ist die Abfrage der Position, weil diese wiederum über das Byte Array der Karte weiterhin abgefragt wird. Dafür habe ich eine relativ simple Lösung (ggf. in den Github Source schauen)

boolean checkCollideOther(boolean resultColide, int positionX, int positionY) {
  // anderes bewegbares objekt
  if(resultColide) {

    int overlap = 4;
    resultColide = checkCollide(positionX, positionY, mapFigurePositionX + (overlap / 2), mapFigurePositionY + (overlap), 10 - overlap, 16 - (overlap * 2));

    // zum testen Fenster oeffnen
    showWindow = !resultColide;
    menueNavigation = showWindow;
  }

  return resultColide;
}

Message Box
Der Text bekommt sein Platz in einem eigenen Fenster Bereich. Für diese Funktion wird ebenfalls eine weiter Seite angelegt mit dem Namen "WindowComponent". Das Fenster (MessageBox) wird angezeigt, sobald man mit seiner gesteuerten Figur in den Kollisionsradius des Händlers kommt.
Solange der Dialog offen ist, sollte die Figur nicht mehr bewegbar sein und mit dem Joystick kann nur noch in den Taschenplätzen Navigiert werden. Nachdem der Spieler die Schließen-Option Auswählt, verschwindet das Fenster und die Figur sollte sich wieder frei bewegen können.
Was im folgenden Code nicht zu sehen ist, ist die Ausführung des Schließen der MessageBox über den Button 2 bzw. Switch 2.

// Legt ein Fenster in den Vordergrund
bool lastStateShowWindow = false;
bool windowHasRendered = false;

void drawWindow(bool rightSide) {
  if(lastStateShowWindow != showWindow && !showWindow) {
    lastStateShowWindow = showWindow;
    drawStack(true);
  }

  lastStateShowWindow = showWindow;
  
  if(!showWindow) {
    windowHasRendered = false;
    return;
  }

  if(windowHasRendered) {
    return;
  }
  
  // Mitte des Bildschirm schreiben
  int sizeX = 100; int sizeY = 40;
  int winPosX = (EsploraTFT.width() / 2) - (sizeX / 2);
  int winPosY = (EsploraTFT.height() / 2) - (sizeY / 2);

  EsploraTFT.fillRect(winPosX, winPosY, sizeX, sizeY, mapNumberToColor(0));
  EsploraTFT.drawRect(winPosX, winPosY, sizeX, sizeY, mapNumberToColor(18));
  EsploraTFT.drawRect(winPosX + 2, winPosY + 2, sizeX - 4, sizeY - 4, mapNumberToColor(18));

  // Text schreiben
  writeText(winPosX + 5, winPosY + 5, "Hallo!");
  writeText(winPosX + 5, winPosY + 28, "Schliessen [2]");
   windowHasRendered = true;
}


Das Stehenbleiben der Figur muss wiederum auf der Hauptseite festgelegt werden. Dazu muss die Funktion für das Laufen erweitert werden, damit die Figur sich erst nach der Option "Schließen" bewegen kann. Zudem müssen alle Inhalte nach dem Schließen neu gerendert werden mit der  Methode 'drawStack'.

void loop() {
  …  
  // Wenn sich X oder Y Position unterscheiden, dann den zu bewegenden Punkt neu zeichnen.
  if(!menueNavigation && lastPosX != lastPosXtemp || lastPosY != lastPosYtemp) {

    drawStack(false);
  }
  else if(menueNavigation) {
    menueNavigateWithDelay();
  }

  drawWindow(lastPosX > EsploraTFT.width() / 2);
  drawCoinsStatus(false);
}

Der Dialog ist noch nicht ganz fertig. Die Taschenplätze sollten mit dem Joystick erreichbar sein. Das fehlt derzeitig auch für den Rucksack. Dies würde jedoch den Rahmen des Posts sprengen und kommt daher im übernächsten. Für den nächsten Part wird der Programmcode dringend aufgeräumt, auf dass ich näher eingehen will.



Kommentare

Beliebte Posts aus diesem Blog

Arduino Control (Teil 5) - PWM Signal einlesen

Angular auf dem Raspberry Pi

RC Fahrtenregler für Lego Kettenfahrzeug