**Dies ist eine alte Version des Dokuments!**
Pygame Legacy 2020
Auch in diesem Semester arbeiten wir wieder mit GitHub. Erstelle für die Aufgaben zu Pygame ein neues Repository mit Namen pygame_projects
und füge diesem die jeweiligen Aufträge hinzu. Gib dieses Repo wieder frei für den Benutze anschae.
1. Installation
Achtung: Pygame funktioniert unter Umständen nicht mit der aktuellsten Version von Python. Im Oktober 2020 sollte Python 3.8 am besten funktionieren (nicht getestet).
Auftrag:
- Installiere Pygame für deine Python 3.7 Installation. Der Pfad dieser Executable dürfte wie folgt ausschauen
C:\Users\<username>\AppData\Local\Programs\Python\Python37\python.exe
- Installiere Pygame mit pip
- Wichtig: Wenn du an einem Pygame Projekt arbeitest, solltest du in VSCode immer den python Pfad dieser Python Installation verwenden. Dies erreichst du, in dem du in den Einstellungen des aktuellen Pojekts (Preferences: Open Workspace Settings → wechsle in JSON Ansicht) den die Variable
"python.pythonPath"
richtig setzt.
2. Theorie I: Uniforme Bewegung, Vektoren & Kollisionen
2.1 Uniforme Bewegung
Uniforme Bewegung bedeutet, dass man sich mit konstanter Geschwindigkeit fortbewegt.
Bewegt man sich während der Zeit $t$ mit der konstanten Geschwindigkeit $v$, so legt man folgende Distanz zurück: $$s = v t$$
2.1.1 Uniforme Bewegung in 2 Dimensionen
Betrachtet man die Bewegung in 2 Dimensionen, so ist es am einfachsten, wenn man die Position und die Geschwindigkeiten als Vektor betrachtet:
$$\vec{s} = \begin{pmatrix}s_x \\ s_y \end{pmatrix}$$ $$\vec{v} = \begin{pmatrix}v_x \\ v_y \end{pmatrix}$$
Dabei nennt man …:
- $v_x$ die $x-$Komponente des Geschwindigkeitsvektors $\vec{v}$.
- $v_y$ die $y-$Komponente des Geschwindigkeitsvektors $\vec{v}$.
Ein Vektor hat immer sowohl eine Richtung als auch eine Länge. Die Länge berechnet man mit dem Satz von Pythagoras: $$v = |\vec{v}| = \sqrt{v_x^2 + v_y^2}$$
Wie beschreibt man nun die Bewegung eines Objekts in 2 Dimensionen? Dieses Objekt befinde sich zum Zeitpunkt $t=0$ am Ort $\vec{s_0} = \begin{pmatrix}s_x^0 \\ s_y^0 \end{pmatrix}$ und es bewege sich mit der Geschwindigkeit $\vec{v_0} = \begin{pmatrix}v_x^0 \\ v_y^0 \end{pmatrix}$. Die Position des Objekts ist dann gegeben durch:
$$\vec{s}(t) = \vec{s_0} + \vec{v_0}t$$
Beachte, dass die Position eine Funktion der Zeit ist: Je länger sich das bewegt, desto weiter ist es von seiner ursprünglichen Position $\vec{s}_0$ entfernt.
2.1.2 Beispiel
Machen wir ein Beispiel.
- Ein Ball befinde sich anfangs (zum Zeitpunkt $t=0$) am Ort $\vec{s}_0 = \begin{pmatrix}5 \\ 2 \end{pmatrix}$. Dies bedeutet, dass sich der Ball an der Position $(5|2)$ im Koordinatensystem befindet.
- Der Ball bewege er sich mit dem Geschwindigkeitsvektor $\vec{v} = \begin{pmatrix}-2 \\ 4 \end{pmatrix}$. Das bedeutet, dass sich der Ball um 2 Teile in die negative $x-$Richtung (nach links) und $4$ in die positive $y-$Richtung (nach oben in der Mathe, nach unten in Pygame) bewegt. Die Länge des Vektors ist $v = \sqrt{(-2)^2+4^2} = 5$, hat also die Geschwindigkeit $5$.
An welcher Position befindet man sich nach der $1$ Zeiteinheit , $2$ Zeiteinheiten oder $60$ Zeiteinheiten? Die Position ist gegeben durch: $$\vec{s}(t) = \vec{s_0} + \vec{v_0}t = \begin{pmatrix}5 \\ 2 \end{pmatrix} + \begin{pmatrix}-2 \\ 4 \end{pmatrix}t = \begin{pmatrix}5 - 2 t \\2 - 4 t \end{pmatrix}$$
Nach der Zeiteinheit $1$ befindet man sich also an der Position $\vec{s}(1) = \begin{pmatrix}3 \\-2 \end{pmatrix}$
2.2 Vektoren in Numpy
Numpy ist eine Python-Library, die dafür konzipiert wurde, Mathematik zu betreiben. Die darin vorhandenen Numpy-Arrays sind dafür geeignet, Vektoren in Python darzustellen.
Das Erstellen eines solchen Numpy-Arrays geht ganz einfach: Nimm eine normale Liste L
und wandle diese in ein solches Array um: np.array(L)
. Wichtig: Importiere dazu zuerst numpy wie folgt: import numpy as np
.
Das oben aufgeführte Beispiel kann man in Numpy wie folgt aufsetzen:
s0 = np.array([5,2]) v0 = np.array([-2,4]) t = 6 s = s0 + v0*t
2.3 Kollisionen
Seien v1
und v2
die Geschwindigkeitsvektoren kollidierender Bälle bevor sie kollidieren. Welche Geschwindigkeitsvektoren haben sie nach der Kollision? Diese sind gegeben durch eine Formel, welche du im folgenden Dossier findest: Dossier Kollision. Eine solche Kollision heisst elastische Kollision oder elastischer Stoss.
Tipp: Am einfachsten arbeitet man mit Numpy-Arrays (z.B. v1 = np.array([3,-7])
). Dies macht die Berechnung der neuen Geschwindigkeitsvektoren relativ einfach.
Quelle: https://en.wikipedia.org/wiki/Elastic_collision#Two-dimensional (letzte beiden Formeln des Anschnitts)
3. Bouncing Balls v01 (Auftrag 1)
3.1 Auftrag in Kürze
- Lese zuerst den Auftrag hier und die unten aufgeführten Bemerkungen usw. genau durch.
- Lade den Code, der dir für diese Aufgabe zur Verfügung gestellt wird, herunter (bouncing_balls_v01.py) und füge diesen deinem GitHub Repo pygame_projects hinzu. Falls du dieses noch nicht für die Lehrperson freigegeben hast, hole dies gleich nach!
- Führe den Code aus.
- Verschaffe dir nun einen Überblick über den Code. Setze einen Breakpoint ganz zu Beginn der
play()
-Methode und gehe Schritt-für-Schritt durch den Code. Es ist wichtig, dass du verstehst, was in jedem Schritt passiert! - Erweitere dann den Code so, dass die Punkte unten umgesetzt sind. Wichtig: Die Bälle sollen sich nach den Regeln der Physik verhalten.
3.2 Erweiterungen für Code
- Jedem Ball wird bei dessen Erstellung die Geschwindigkeitskomponenten in x- und y-Richtung mitgegeben. Die Bälle sollen sich entsprechend bewegen.
- An den Wänden sollen die Bälle abprallen (Einfallswinkel = Ausfallswinkel).
- Kollidieren zwei Bälle, so sollen wie voneinander abprallen (siehe Theorie unten).
- Bei Kollisionen kann es zu 'Tänzen' kommen. Baue einen Schutzmechanismus ein, der solche Tänze so gut wie möglich verhindert.
Zusatzaufgaben:
- Gib eine Anzahl Bälle vor. Der Code soll dann die Position der Bälle per Zufall bestimmen. Stelle dabei sicher, dass sich die Bälle nicht überlappen und dass sie nicht über den Rand hinaus gehen.
- Auch die Geschwindigkeit, Masse und Farbe sollen (in einem gewissen Range) per Zufall bestimmt werden.
- Einem der Bälle soll man mit den Pfeiltasten Geschwindigkeitsschübe in die versch. Richtungen geben können.
3.3 Bemerkungen
- Erstelle im Ordner, wo dieses File gespeichert wurde, einen Unterordner 'data'
- Verändere die grundlegende Struktur des Games nicht.
- Du sollst keine neuen Methoden deklarieren, du sollst nur die bestehenden Klassen und Methoden erweitern.
3.4 Erklärungen Pygame
- Figuren, sich bewegende Bauteile und alle weiteren Objekte sind sogenannte Sprites. Deshalb sollten diese alle von der Klasse ??
pygame.sprite.Sprite
erben. Damit macht man sich, z.B. bei Kollisionen, das Leben sehr viel einfacher. - Kollisionen: Pygame enthält verschiedene Methoden zur Erkennung von Kollisionen. Studiere das Manual und überlege dir, welche für dieses Programm am besten geeignet ist: https://www.pygame.org/docs/ref/sprite.html#pygame.sprite.spritecollide
- Wenn man mehrere Objekte der gleichen Sprite-Unterklasse (hier z.B. von der Klasse Ball) hat, lohnt es sich, diese in einer Gruppe zusammenzufassen (hier:
Balls = pygame.sprite.Group()
). Dann kann die Position aller Bälle mit einem Kommando updated werden: Balls.update()
3.5 Code
Dieser Code dient als Startpunkt:
- bouncing_balls_v01.py
import os import pygame from PIL import Image, ImageDraw ### CONSTANTS ### # Windows WIN_HEIGHT = 800 WIN_WIDTH = 500 # Framerate FPS = 30 TIME_DELAY = int(1000 / FPS) # Colors WHITE = (255, 255, 255) BLACK = (0, 0, 0) RED = (255, 0, 0) GREEN = (0, 255, 0) BLUE = (0, 0, 255) BG_COLOR = GREEN ### METHODS ### def draw_bmp_circle(radius, color,path): """ creates circle image and saves on disk """ img = Image.new('RGBA', (radius,radius), (255, 0, 0, 0)) draw = ImageDraw.Draw(img) draw.ellipse((0,0,radius-1,radius-1), fill=color) img.save(path, 'PNG') ### CLASSES ### class Ball(pygame.sprite.Sprite): def __init__(self, xy_center, v, radius, mass, color): super().__init__() # call __init__ of parent class (i.e. of pygame.sprite.Sprite) # CREATE BALL IMAGE (if does not exist yet) file_path = f"data/ball_r={radius}_col={str(color).replace(' ','').replace(',','_')}.png" if not os.path.isfile(file_path): draw_bmp_circle(radius,color,file_path) # draw image and save on disk if doesnt exist yet # ASSIGN CLASS ATTRIBUTES self.image = pygame.image.load(file_path) # load image self.rect = self.image.get_rect() # create rectangle containing ball image self.rect.center = (int(xy_center[0]),int(xy_center[1])) # set center coords of ball self.mass = mass # is relevant for realistic collisions self.color = color self.vx = v[0] # velocity self.vy = v[1] self.mask = pygame.mask.from_surface(self.image) # creates a mask, used for collision detection (see manual about pygame.sprite.collide_mask()) def update(self): """ determines motion of particles: """ # TODO: # 1. General motion of particles # 2. Bouncing off walls # do NOT deal with collisions in here def collide(self,ball): """ ball self collides with other ball, given as argument this method updates velocities of BOTH balls """ # TODO # 1. two balls (self and ball) collide -> deal with collision # Theory of collision btw two balls: https://en.wikipedia.org/wiki/Elastic_collision#Two-dimensional (last formula) # 2. try to avoid 'ball dances' class Game: """ Main GAME class """ def __init__(self): pygame.init() pygame.font.init() self.time_delay = TIME_DELAY self.screen = pygame.display.set_mode((WIN_WIDTH, WIN_HEIGHT)) # create screen which will display everything self.win = pygame.display.set_mode((WIN_WIDTH, WIN_HEIGHT)) pygame.display.set_caption("Bouncing Balls") # Game title def play(self): # CREATE GAME GROUPS Balls = pygame.sprite.Group() # CREATE OBJECTS AND ASSIGN TO GROUPS balls_list = [ Ball([0.25*WIN_WIDTH, 0.75*WIN_HEIGHT], [ -4, 7], 100, 1, BLUE) ,Ball([0.25*WIN_WIDTH, 0.50*WIN_HEIGHT], [ -5, -10], 100, 1, BLACK) ,Ball([0.50*WIN_WIDTH, 0.75*WIN_HEIGHT], [ 7, -7], 100, 1, WHITE) ,Ball([0.50*WIN_WIDTH, 0.25*WIN_HEIGHT], [ -4, 7], 100, 1, RED) ] Balls.add(balls_list) # GAME PERMANENT LOOP while True: pygame.time.delay(TIME_DELAY) # KEY EVENTS for event in pygame.event.get(): # Exit app if click quit button if event.type == pygame.QUIT: run = False keys = pygame.key.get_pressed() if keys[pygame.K_ESCAPE]: self.exit() self.screen.fill(BG_COLOR) # draw empty screen # COLLISION DETECTION # see manual for all types of collisions: https://www.pygame.org/docs/ref/sprite.html#pygame.sprite.spritecollide # TODO: check for collisions between any two balls. If there is any, call the collision() method of the Ball class. # DRAW Balls.draw(self.screen) # UPDATE Balls.update() pygame.display.update() pygame.quit() if __name__ == "__main__": Game().play()
4. Theorie II: Uniforme Beschleunigung & Gravitation
4.1 Uniforme Beschleunigung
Eine Beschleunigung ist eine Änderung der Geschwindigkeit pro Zeit
$$a = \frac{\Delta v}{t}$$
wobei:
- $\Delta v:$ Änderung der Geschwindigkeit, SI-Einheit $\frac{\text{m}}{\text{s}}$
- $t:$ Zeit, in der Geschwindigkeit verändert wird, SI-Einheit $\text{t}$
- $a$: Beschleunigung (acceleration), SI-Einheit: $\frac{\text{m}}{\text{s}^2}$
Eine Uniforme Beschleunigung ist einfach eine konstante Beschleunigung. Die Beschleunigung ist also immer gleich stark und verändert sich nicht mit der Zeit.
4.1.1 Uniforme Beschleunigung in 1D
Bewegung entlang der $x-$Achse:
- $s_0:$ Position bei Beginn der Beschleunigung (zum Zeitpunkt $t=0$)
- $v_0:$ Geschwindigkeit bei Beginn der Beschleunigung (zum Zeitpunkt $t=0$)
- $a:$ Beschleunigung, mit der beschleunigt wird
- $t:$ Zeit, in der Beschleunigt wird
Nachdem für die Zeit $t$ mit $a$ beschleunigt wurde, befindet man sich an der Position $s$ und hat Geschwindigkeit $v$, gegeben durch:
$$v(t) = v_0 + a t$$ $$s(t) = s_0 + v_0 t + \frac12 a t^2$$
4.1.2 Uniforme Beschleunigung in 2D
Ein Objekt befindet sich zum Zeitpunkt $t=0$ am Ort $\vec{s_0} = \begin{pmatrix}s_x^0 \\ s_y^0 \end{pmatrix}$ und bewegt sich mit der Geschwindigkeit $\vec{v_0} = \begin{pmatrix}v_x^0 \\ v_y^0 \end{pmatrix}$. Das Objekte werde jetzt durch den Beschleunigungsvektor $\vec{a} = \begin{pmatrix}a_x \\ a_y \end{pmatrix}$ beschleunigt. Dann verändern sich Geschwindigkeit und Position wie folgt mit der Zeit:
$$\vec{v}(t) = \vec{v_0} + \vec{a}t$$ $$\vec{s}(t) = \vec{s_0} + \vec{v_0}t + \frac12 \vec{a} t^2$$
4.2 Gravitation
Lässt man etwas fallen, so wird es mit der Beschleunigung $g = 9.81\text{m}/\text{s}^2$ nach unten beschleunigt. Da diese Beschleunigung sehr wichtig ist, hat sie einen eigenen Namen erhalten und man schreibt typischerweise $g$ anstelle von $a$. Auf der Erdoberfläche ist die Gravitationsbeschleunigung in etwa konstant, weshalb man guten Gewissens bei der Gravitation von ein uniformen Beschleunigung sprechen kann.
In 2 Dimensionen beschreibt man die Gravitation mit dem folgenden Vektor: $$\vec{g} = \begin{pmatrix}0 \\ - 9.81\text{m}/\text{s}^2 \end{pmatrix}$$
Beachte: Hier wird die übliche Konvention in Physik und Mathematik verwendet, dass nach oben durch die positive $y-$Richtung dargestellt wird. In Pygame ist dies anders. Dort sollte der Gravitationsvektor z.B. wie folgt aussehen: g = np.array([0,1])
. Beachte, dass der genaue Zahlenwert nicht relevant ist und durch ausprobieren eingestellt werden sollte.
Ganz wichtig: Eine Änderung der Geschwindigkeit in eine Richtung tritt nur dann auf, wenn in diese Richtung eine Beschleunigung wirkt. Beispiele:
- Die Gravitation wirke in die y-Richtung: $\vec{g} = \begin{pmatrix}0 \\ - 9.81\text{m}/\text{s}^2 \end{pmatrix}$. Damit hat sie nur einen Einfluss auf die $y-$Komponente eines Geschwindigkeitsvektors. Ein Ball habe zum Zeitpunkt $t = 0$ den Geschwindigkeitsvektor $\vec{v} = \begin{pmatrix}2 \\ 3 \end{pmatrix}$. Der Ball bewegt sich also nach rechts oben, allerdings mehr nach oben als nach rechts. Die Gravitation beeinflusst also nur die $y-$Komponente: Zu Beginn ist sie $3$ und nimmt dann stetig ab bis sie Null ist (höchster Punkt der Flugbahn). Danach nimmt sie weiter ab und wird also negativ: Der Ball bewegt sich jetzt nach unten. Je länger man wartet, desto schneller bewegt es sich nach unten. Das Ganze hat aber keinen Einfluss auf die $x-$Komponente des Geschwindigkeitsvektors; diese ist und bleibt $2$.
5. Bouncing Balls v02 (Auftrag 2)
Mache eine Kopie der letzten Aufgabe und benenne das File um in bouncing_balls_v02.py.
5.1 Auftrag in Kürze
Füge deinem Programm Gravitation hinzu.
5.2 Auftrag im Detail
- Kommentiere alle Bälle bis auf einen aus. Setze dessen Geschwindigkeit auf Null. Startet man nun das Programm, so soll ein Ball gezeigt werden, der sich nicht bewegt.
- Studiere die Theorie oben über uniforme Beschleunigung und Gravitation genau.
- Modifiziere nun die
update()
Methode der Ball-Klasse: Füge Gravitation hinzu. Bei jedem Update soll nun ein Ball nach unten beschleunigt werden. - Teste deinen Code: Der Ball sollte nun nach unten beschleunigt werden, am Boden abprallen und wieder genau die ursprüngliche Höhe erreiche. Springt der Ball höher oder weniger hoch, so ist etwas falsch.
- Füge nun weitere Bälle hinzu und geniesse das Spektakel.
- Falls die Bälle zu schnell werden, könntest du eine maximale Geschwindigkeit einführen: Sobald eine Ball diese Geschwindigkeit erreicht hat, wird er nicht mehr schneller.
6. Einfaches Spiel mit uniformer Beschleunigung (Auftrag 3)
Programmiere ein einfaches Spiel (ähnlich zu Jumping Dagobert), welches unten aufgeführte Eigenschaften haben soll.
6.1 Bewegung der Spielfigur
- Erstelle ein neues File jump_game_v01.py und pushe dieses regelmässig wie gehabt in das entsprechende GitHub Repo.
- Der Code soll den gleichen grundsätzlichen Aufbau haben wie derjenige im vorherigen Projekt. Der Code soll z.B. wieder die Klassen Player und Game beinhalten.
- Das Spiel beinhaltet eine Spielfigur (Sprite). Zum jetztigen Zeitpunkt kann diese einfach ein Rechteck oder ein Kreis sein. Später wird diese durch einen animierten Sprite ersetzt.
- Diese Figur kann man mit der Links- und Rechtspfeiltaste bewegen.
- Die Bewegung erfolgt nach den physikalischen Gesetzen der uniformen Beschleunigung: Steht die Figur still oder bewegt sich langsamer als eine vorgegebene Maximalgeschwindigkeit, so wird die uniform, also konstant, Beschleunigt, wenn man eine der Pfeiltasten gedrückt hält. Hat man die Maximalgeschwindigkeit erreicht, so bewegt sich die Figur mit dieser Geschwindigkeit.
- Lässt man die Pfeiltaste los, so bremst die Spielfigur ab, sie bleibt also nicht einfach plötzlich stehen. Auch für dieses Abbremsen sollen wieder die Gesetze der uniformen Beschleunigung (Erinnerung: Abbremsen ist negative Beschleunigung) angewandt werden.
- An den Wänden links und rechts soll die Figur abprallen.
- Drückt man die Leertaste oder die Pfeiltaste nach oben, so soll die Figur springen.
- Der Sprung soll physikalisch korrekt sein: Sobald man in der Luft ist, kann man die Flugbahn nicht mehr kontrollieren, man ist dann ganz der Gravitation ausgeliefert.
- Damit die Figur springt, muss man deren Geschwindigkeitsvektor eine $y-$Komponente hinzufügen.
- Die Figur soll höher springen, wenn sich die Figur schneller bewegt. Ist sie in Ruhe, soll sie nur einen kleinen Hopps machen. Das bedeutet also, dass diese $y-$Komponente von der aktuellen Geschwindigkeit abhängen muss.
- Sämtliche Zahlenwerte sollen nach Gefühl ausgewählt und durch Ausprobieren optimiert werden. Es gibt hier kein richtig oder falsch, nur ein 'es fühlt sich eher natürlich an oder nicht'. Ob für das positive und negative Beschleunigen jeweils der gleiche Wert genommen wird, ist die überlassen.
6.2 Punkte sammeln
- Bisher hast du eine einfache Physiksimulation erstellt. Nun soll daraus ein Game werden.
- Erstelle eine Coin-Klasse (Coin: Münze).
- Auf dem Bildschirm soll dann immer eine festgelegte Anzahl Coins angezeigt werden.
- Das Spiel besteht dann darin, diese Coins einzufangen: Sobald die Spielfigur einen Coin berührt, verschwindet dieser und es wird ein neuer Coin erzeugt.
- Die Position der Coins soll zufällig gewählt werden, verwende dazu da random Modul.
- Achtung: stelle sicher, dass alle Coins so gewählt sind, dass sie auch erreichbar sind.
- Für Physiknerds: Berechne die maximale Flughöhe der Spielfigur. Nutze dazu die Energieerhaltung. Coins sollen dann nicht höher positioniert werden.
- Was nun noch fehlt ist ein Game-Mode: Was soll passieren, wenn man einen Coin eingefangen hat? Überlege die selbst, wie das Game funktionieren soll. Für eine Liste mit Ideen, siehe die Liste unten.
- Auf irgend eine Art und Weise sollten Punkte gesammelt werden. Füge eine Punkteanzeige zu deinem Spiel hinzu.
Ideensammlung Game Mode:
- Punkte werden einfach gesammelt: 1 Punkt pro Coin.
- Gleich wie oben, allerdings hat man nur eine gewisse Zeit zur Verfüger. Man muss dem Game also einen Counter hinzufügen.
- Man kann Zusatzpunkte geben, wenn man mehrere Coins während einem Sprung einfängt.
- …
7. Sprite Animationen (Auftrag 4)
Nutze dein Programm jump_game_v01.py als Startpunkt und speichere es ab unter jump_game_v02.py. Erweitere den Code nun so, dass du ein richtiges Spiel erhälst.
Dein Spiel soll animierte Sprites beinhalten
- Spielfigur soll sich bewegen
- z.B. rotierende Coins
Erstelle Pixel Art (inkl. Animationen) mit Piskel: https://www.piskelapp.com/
Schaue dazu das Tutorial: https://www.youtube.com/watch?v=lJN2C7-dyxE&list=PLO3K3VFvlU6Akj3W29_nMLZFnwNOVbAzI&index=1
8. Projekt (Auftrag 5)
Auftrag in Kürze: Programmiert in 1er oder 2er Gruppen ein Retro Game im Jump 'n Run Stil.
Auftrag in Detail:
- Alleine oder in 2er Gruppe (2er Gruppe empfohlen!)
- Falls in 2er Gruppe: Zusammenarbeit in einem GitHub Repository
- Gesetze der Physik dürfen verletzt werden - aber nur bewusst!
- muss beinhalten: Sprite Animation
- Abgame: Wann?
Ideen:
- Platforms
- Maps, die grösser sind als Bild → Navigation in Map?
9. Sammlung nützliche Befehle, Links usw.
Lade Bild und lasse alle weissen Bereiche transparent werden:
im = pygame.image.load('fig.png').convert() im.set_colorkey((255,255,255))
9.1 Programme
Gratis online Programm für Pixelart: https://www.piskelapp.com/