Sonntag, 17. September 2017

Eigene Sprites erstellen (Arduino Esplora, Part 3.1)


Im Internet habe ich nach einer Einfachen Lösung gesucht, wie mein ein Sprite bzw. Bild  auf seine eigenen Anforderungen erstellen kann. Damit ist gemeint, dass eine durchgehende Farbpallette für das Eigene Ziel abbilde und Numerisch bezeichnen kann. Zudem sollte dies in einer Byte Folge ausgegeben werden, so dass ich diese im Programmcode ablegen kann.
Natürlich gibt es so ein Programm nicht. Im Grunde ist die Ausgabe eines Bildes durch ein Skript zu übersetzen relative einfach oder auch mal schnell ein eigenes kleines Programm schreiben. Denn das habe ich zunächst gemacht, um schnell eigene Sprites anzulegen. Also zwei Abende dran ran gesetzt und fertig war der Bildeditor. Die Benutzbarkeit beschränkte sich auf die mehr auf die Verwendung der Funktionen.
Mit der Zeit wurden dann noch ein paar Farben hinzugefügt und neu Sortiert, ansonsten hat sich nichts weiter geändert.


  
Übung ist trainieren
Irgendwann bekam es mich doch. Ich schrieb zu Übungszwecken weiter und räumte einiges an Programmcode auf, fügte ein paar weitere Funktionen hinzu und verpasste noch ein paar Optische Verbesserungen. Da ich nicht immer Zeit hatte, verstrichen Monate und ich stellte mal wieder fest, wenn man etwas ordentlich macht, dann kann dabei schon eine Menge Zeit vergehen. Oberflächlich wird man die Mühe nicht sehen, nur die Dinge die nicht funktionieren. Schließlich sind wir in Deutschland und gemeckert wird immer.

Wie verwende ich das Programm
Man schreibt ein Programm, kopiert sich die fertigen Code aus dem Beispiel, malt ein Bild und klickt auf Exportieren. Na gut, etwas mehr Detaillierter darf die Beschreibung dann doch sein.

Am besten verwendet ihr den folgenden Beispiel Programmcode. Je nach Größe des Bildes, müssen die Breite und Höhe angepasst werden. Das sind die Member Variablen 'pictureWidth' und 'pictureHeight', sowie auch die Array Größe und dessen eingetragenen Byte Werte. Im Beispiel ist ein Bild das 10 Pixel Breit und 16 Pixel hoch ist. Multipliziert man die beiden Werte, dann bekommen wir den Wert 160, dass hier die Byte Array Größe festlegt.

 #include <SPI.h>  
 #include <TFT.h>  
 #include <Esplora.h>  
 // zu renderndes Bild  
 int pictureWidth = 10;  
 int pictureHeight = 16;  
 byte picture[160] = {  
  0,0,1,1,1,1,1,1,0,0,0,1,3,3,3,3,3,3,1,0,1,3,3,3,3,3,3,3,3,1,1,3,3,4,4,2,4,3,3,1,1,3,5,5,2,2,5,5,3,1,1,3,4,1,2,2,1,4,3,1,0,1,2,1,2,2,1,2,1,0,0,0,1,2,2,2,2,1,1,0,0,1,8,8,3,3,8,8,6,1,1,8,8,8,8,8,8,6,2,1,1,2,1,8,8,8,8,1,1,0,0,1,1,10,10,7,7,1,0,0,0,1,9,10,1,7,6,1,0,0,0,0,1,1,1,7,6,1,0,0,0,0,0,0,1,9,11,1,0,0,0,0,0,0,0,1,1,0,0,0  
  };  
 void setup() {  
  // init display  
  EsploraTFT.begin();  
  EsploraTFT.initR(INITR_BLACKTAB);  
  EsploraTFT.setRotation(1);  
  EsploraTFT.background(0, 0, 0);  
 }  
 void loop() {  
   drawPictureArray(40, 40, picture);  
 }  
 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(12):{ result = 0xFFE0; break; } // gelb  
   case(13):{ result = 0xFC08; break; } // orange  
   case(14):{ result = 0xFA8A; break; } // hell rot  
   case(15):{ result = 0xD759; break; } // hell gruen 2  
   case(16):{ result = 0xF800; break; } // rot  
   case(17):{ result = 0x8208; break; } // dunkel braun  
   case(18):{ result = 0xC618; break; } // grau  
   case(19):{ result = 0xF7BE; break; } // sehr hell grau  
   case(20):{ result = 0xFE97; break; } // hell haut  
   default: {  
    result = 0;  
    break;  
   }  
  }  
  return result;  
 }  
 void drawPictureArray(int relationX, int relationY, byte pictureArray[]) {  
  int index = 0;  
  for(int y = 0; y < pictureHeight; y++) {  
   for(int x = 0; x < pictureWidth; x++) {  
     EsploraTFT.drawPixel(relationX+x, relationY+y, mapNumberToColor(pictureArray[index]));  
     index++;  
    }  
   }  
 }  

Wenn ihr den Bild Editor Startet, dann wird gleich ein neues Bild von 10 mal 16 Pixel angelegt.


Über ‚Edit' kann die Größe des Pixelfeldes geändert werden. ACHTUNG: Nach Umstellung der Größe, gehen bereits eingetragene Pixel verloren.


Ist das Bild fertig, dann kann über File -> Export die Byte Kette selektiert und per STRG+C kopiert werden.


Über STRG+V wird zwischen den geschweifte Klammern der Member Variable 'picture' hinzugefügt. Ggf. Breite, Höhe und byte Array Größe anpassen und schon kann der Programmcode auf den Arduino Esplora geschrieben werden.


Sobald das Programm auf dem Arduino Esplora ausgeführt wird, sollte nun das Bild zu sehen sein, dass als Byte Kette hinein kopiert wurde.


Hinweise:
  • Im Programm sind bereits mehr Farben hinterlegt, da ich zu diesen Zeitpunkt mit dem Eigentlichen Projekt weiter gearbeitet und weitere Farben ergänzt habe.
  • Touch Fähigkeit wird noch bearbeitet.
  • Ein Bild mit Programmcode darf nicht mehr Bytes verbrauchen als der Maximale Arbeitsspeicher des Arduinos.

Der Bildeditor ist an für sich fertig. Fehler sind nicht ausgeschlossen und können über die Kommentar Funktion des Blogs eingetragen werden.

Nächster Post: Karte anlegen (Arduino Esplora, Part 4)

Low Pixel Maker Download
Anmerkung: Ich räume noch das kleine Programm auf. Sobald ich damit fertig bin, lade ich die Solution auf Github hoch.

Sonntag, 3. September 2017

Bildanimation (Arduino Esplora, Part Teil 3)


Die Adafruit GFX Bibliothek gibt uns die Möglichkeiten Pixel für Pixel auf das TFT zu schreiben, womit sich dann auch ein Bild zusammen setzen lässt. Bei größeren Bilder sollte klar sein, dass der Bildaufbau mit 16MHz nur langsam abläuft. Als Ziel ist jedoch eine Darstellung zur Laufzeit zu verändert, wie z.B. eine Runde Analoge Anzeige.


Relativ schnell stellte sich heraus, dass die Umsetzung einer solchen Anzeige zwar einfach ist, aber ab einer bestimmten Größe zu langsam gerendert wird. Alternative und einfacher ist die das Verwenden von bereits fertigen Bildern in 16 mal 16 Format. Zugegeben ist eine Analoge Anzeige mit dieser Auflösung sehr grob und auf Dauer nicht zu friedend stellend. Eine Low Pixel Figur wiederum würde passen und das kombiniert mit den Tasten, könnte die Figur auch über den Bildschirm gesteuert werden. An dieser Stelle erinnerte ich mich wieder an den Anfang von Octoawesome von Tom Wendel, der in seinen ersten folgen ähnliche Schritte unternahm ein Spiel zu entwickeln mit C# und Windows Forms (später wurde die Windows form Oberfläche abgelöst  durch MonoGame).

Technische Umgebung und Anforderung
Kommen wir zu den Bildern die zunächst auf ein Format gebracht werden müssen, die möglichst wenig Ressourcen verbrauchen. Mit dem Format 16x16 muss jeder Pixel eine Farbe zugewiesen werden. Der Bildschirm unterstützt 16Bit, womit zwei Byte pro Pixel anfallen würden. Das wären dann 512 Bytes für ein Bild, womit dann eine Sinnvolle Animation mit 2,5 Kilobyte SRAM nicht sinnvoll wäre.
An der Stelle sollten man sich vor Augen halten, wie viele Farbabstufungen 16Bit haben. Und dann schaut man nochmal auf die Anforderung. Daraus stellen sich die Fragen:
Wie viele Farben werden benötigt?
Wie viele Pixel braucht meine Figur?


Auf die Hälfte und weniger reduzieren
Anstatt zwei Byte als Pixelfarbinformation zu hinterlegen, wird dies auf ein Byte reduziert. Der Byte Wert trägt später nur die Nummer aus einer Farbpallette. Mit einer entsprechenden Mapping Funktion, wird dann später die Farbe für den Pixel abgerufen. Durch diesen Vorgang reduziert sich das Bild von 16x16 von 512 auf 256 Bytes. Die Figur selbst benötigt in der Breite nur zehn Pixel, womit der Speicher verbrauch sich auf 160 Bytes weiter reduziert.

 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  
   default: {  
    result = 0;  
    break;  
   }  
  }  
    
  return result;  
 }  

Bild Editor in Arbeit
Kommen wir zum Erstellen eines Bildes. Ein Bild Byte für Byte zu schreiben ist so spaßig wie einen Film auf Indisch mit Kantonesischen Untertitel zu schauen. Hier für stelle ich demnächst ein kleines Tool zur Verfügung, mit denen ihr Farbpixel für Farbpixel euer Bild Zeichnen und nach Fertigstellung die byte Kette in den Sketch kopieren könnt.




Keine Metadaten
Die Byte Kette selbst enthält keine Metadaten. Das bedeutet, dass das Bild nicht weist wie Breit und wie hoch sie ist. Diese Informationen müssen vom Programmcode festgelegt werden.

 void drawFigurArray(int relationX, int relationY, byte figureArray[], boolean mirror, boolean clearColor) {  
  int index = 0;  
  for(int y = 0; y < 16; y++) {  
   for(int x = 0; x < 10; x++) {  
    if(clearColor) {  
      EsploraTFT.drawPixel(relationX+x, relationY+y, mapNumberToColor(figureArray[1]));  
    }  
    else {  
     int indexTarget = index;  
     if(mirror) {  
      indexTarget = index - x + (10 - x) - 1;  
     }  
   
     EsploraTFT.drawPixel(relationX+x, relationY+y, mapNumberToColor(figureArray[indexTarget]));  
     index++;  
    }  
   }  
  }  
 }  

Sprite Animation
Den Sketch den ich zur Verfügung stelle, kann die Farbnummern übersetzen aus dem Byte Array und so mit ein Bild auf den TFT schreiben. Hier fehlt nur die Sequenz abfolge der verwendeten Sprites als Animation. Würde man die paar Bilder direkt hintereinander abspielen, würden diese zu schnell ablaufen. Mit 'delay' lässt sich dieses Problem provisorisch lösen, führt jedoch zu Problemen mit dem späteren Abfragen der Taster. Deshalb werden zwei Variable vom Typ long und int im Funktionsvariable angelegt. Der mit dem Typ long (hier gametime benannt), wird pro Methoden Loop durchlauf, um einen hochgezählt. Der zweite Wert von Type int Mit einer Modulo Abfrage wird dann immer der vierte Durchlauf verwendet, um den Sequenz Bildindex der Animation um einen fortzusetzen oder von vorne abzuspielen.
Die Figur kann sich in vier Richtungen bewegen, womit dann auch vier verschiedene Animationsabläufe sich abbilden. Für Links und Rechts können die selben Bilder verwendet werden, da für die gegenteilige Richtung gespiegelt werden kann.
UPDATE: Zuvor war an der Methode der Parameter 'walk' übergeben worden, der jetzt entfällt. Und 'default' führt jeweils nochmal das zweite Sprite aus, womit dann die Figur weniger flimmern sollte.

 void drawFigure(int directionX, int directionY, int relationX, int relationY) {  
   
  if(gameTime % 4 > 0) {  
   if(animStep > 2) {animStep = 0;}  
   else {animStep++;}  
  }  
   
   if(directionX == 0 && directionY == 1) {  
    switch(animStep){  
     case(0): { drawFigurArray(relationX, relationY, spriteFigureFrontLeft, false, false); break; }  
     case(1): { drawFigurArray(relationX, relationY, spriteFigureFrontMiddle, false, false); break; }  
     case(2): { drawFigurArray(relationX, relationY, spriteFigureFrontLeft, true, false); break; }  
     default: { drawFigurArray(relationX, relationY, spriteFigureFrontMiddle, false, false); break; }  
    }  
   }  
   else if(directionX == -1 && directionY == 0) {  
    switch(animStep){  
     case(0): { drawFigurArray(relationX, relationY, spriteFigureSideLeft, false, false); break; }  
     case(1): { drawFigurArray(relationX, relationY, spriteFigureSideMiddle, false, false); break; }  
     case(2): { drawFigurArray(relationX, relationY, spriteFigureSideRight, false, false); break; }  
     default: { drawFigurArray(relationX, relationY, spriteFigureSideMiddle, false, false); break; }  
    }  
   }  
   else if(directionX == 0 && directionY == -1) {  
    switch(animStep){  
     case(0): { drawFigurArray(relationX, relationY, spriteFigureBackLeft, false, false); break; }  
     case(1): { drawFigurArray(relationX, relationY, spriteFigureBackMiddle, false, false); break; }  
     case(2): { drawFigurArray(relationX, relationY, spriteFigureBackLeft, true, false); break; }  
     default: { drawFigurArray(relationX, relationY, spriteFigureBackMiddle, false, false); break; }  
    }  
   }  
   else if(directionX == 1 && directionY == 0) {  
    switch(animStep){  
     case(0): { drawFigurArray(relationX, relationY, spriteFigureSideLeft, true, false); break; }  
     case(1): { drawFigurArray(relationX, relationY, spriteFigureSideMiddle, true, false); break; }  
     case(2): { drawFigurArray(relationX, relationY, spriteFigureSideRight, true, false); break; }  
     default: { drawFigurArray(relationX, relationY, spriteFigureSideMiddle, true, false); break; }  
    }  
   }  
 }  

Die Animation sollte nur dann abgespielt werden, wenn eines der Tasten gedrückt wurde. Im zweiten Teil der Blogpost Reihe wurde nur ein Kreis bewegt und wurde von der Methode in der folgenden If Abfrage ausgeführt.

 // Wenn sich X oder Y Position unterscheiden, dann den zu bewegenden Punkt neu zeichnen.  
  if(lastPosX != lastPosXtemp || lastPosY != lastPosYtemp) {  
   drawPoint(lastPosXtemp, lastPosYtemp, false);  
   drawPoint(lastPosX, lastPosY, true);  
  }  

Der Inhalt der If Abfrage Zeichnete einen Kreis, der durch die Sprite Animation ersetzt wird. Hier werden nun die Joystick eingaben auf ihre Ausrichtung geprüft bevor die Figur mit den richtigen Sprite Bilder gezeichnet werden.

 // Wenn sich X oder Y Position unterscheiden, dann den zu bewegenden Punkt neu zeichnen.  
  if(lastPosX != lastPosXtemp || lastPosY != lastPosYtemp) {  
   
   int directX = 0;  
   int directY = 0;  
   
   if(lastPosXtemp > lastPosX) { directX = -1; }  
   if(lastPosXtemp < lastPosX) { directX = 1; }  
   if(lastPosYtemp > lastPosY) { directY = -1; }  
   if(lastPosYtemp < lastPosY) { directY = 1; }  
   
   drawFigure(directX, directY, lastPosX, lastPosY);  
  }  

So das sollte erstmal alles sein für diesen Post. Im nächsten Post kommt eine Beschreibung zum Bild Editor, dass für das Erstellen der Sprites erleichtert.

Nächster Post: Eigene Sprites erstellen (Arduino Esplora, Part 3.1)

Ameisen Simulation und andere Dinge

Wer lange sich schon mit C# beschäftig, hat sicherlich schon mal was von AntMe gelesen oder gehört. Diese Idee hatte ich aufgegriffen und mi...