<p>In Python, und vielen anderen Programmiersprachen, gibt es Klassen.
Klassen beschreiben, welche Eigenschaften und Funktionen Objekte dieser
Klasse haben. Klassen sind quasi wie ein Bauplan aus dem man dann
mehrmals dasselbe Objekt herstellen kann. Von diesem Bauplan kann man
mit Hilfe von <em>Parametern</em> abweichen, zum Beispiel kann man jedes
mal ein anderes Bild einfügen. Gibt es mehrere Klassen, die viele
gemeinsame Eigenschaften haben, ist es sinnvoll, eine übergeordnete
Klasse (auch <em>Super-Klasse</em>) mit den allgemeinenen Eigenschaften
und Kinderklassen mit den speziellen Eigenschaften zu erstellen.</p>
<p>Für uns heißt das: Die Klasse <em>_Character</em> hat die Parameter
<em>location</em> (Spawnkoordinaten), <em>scale</em> (Größe) und
<em>image</em> (gewünschtes Bild). Die <em>Player</em> im Spiel, als
auch die <em>Enemies</em>, sind solche <em>Character</em>, daher haben sie
alles auch diese Attribute. Es gibt aber auch Unterschiede, z.B. lassen
sich <em>Player</em> steuern, <em>Enemies</em> nicht. Hier kommt das
Konzept der <em>Vererbung</em> ins Spiel. Die gemeinsamen Eigenschaften
sind in der Klasse <em>_Character</em> definiert. Darauf basierend gibt es die
Klassen <em>LinearPlayer</em> und <em>AnimatedCoin</em>, welche beide
von der Klasse <em>_Character</em> erben. Das bedeutet, dass sie deren
Eigenschaften ebenfalls haben, und zusätzlich neue Eigenschaften und
Funktionen definieren können. Am Ende übergibt man also der Klasse
<em>Player</em> die vier <em>Parameter</em>, die die <em>_Character</em>-Klasse
braucht und ein weiteres.</p>
<h3id="wie-fügt-ich-selber-sprites-ein">Wie fügt ich selber Sprites
ein?</h3>
<p>Um selber einen Sprite hinzuzufügen, müssen wir erstmal eine passende
Klasse, also einen passenden Bauplan finden. Folgende gibt es schon
vorgefertigt:</p>
<imgsrc=mermaid.pngalt="Entscheidungshilfe um Funktionen auszuwaehlen"width=1116height=753>
<p>Jedes Objekt braucht unterschiedliche Parameter, wie oben
beschrieben. Hier sind sie jeweils aufgelistet:</p>
<ul>
<li>Beide Spieler
<oltype="1">
<li>Spawnkoordinaten (x, y)</li>
<li>Gewünschte Größe in pixel (Breite, Höhe)</li>
<li>Dateiname des Bildes (mit Dateiendung)</li>
<li>Genutzte Karte (standardmäßig tilemap)</li>
<li>Spritegroup der Wände (standardmäßig platforms)</li>
</ol></li>
<li>Gegner
<oltype="1">
<li>Spawnkoordinaten (x, y)</li>
<li>Gewünschte Größe in pixel (Breite, Höhe)</li>
<li>Dateiname des Bildes (mit Dateiendung)</li>
<li>Genutzte Karte (standardmäßig tilemap)</li>
<li>Spritegroup der Wände (standardmäßig platforms)</li>
<li>Die Geschwindigkeit des Gegners</li>
<li>Entweder:</li>
</ol>
<ul>
<li>Die Richtung der Bewegung ([speedx, speedy]) (Bei LinearEnemy)</li>
<li>Den Spieler der verfolgt werden soll (meist player) (Bei
FollowingEnemy)</li>
<li>Wie oft die Richtung geändert werden soll (in Sekunden) (Bei
RandomEnemy)</li>
</ul></li>
<li>Objekte</li>
</ul>
<oltype="1">
<li>Spawnkoordinaten (x, y)</li>
<li>Größe des Bildes (Breite, Höhe)</li>
<li>Dateiname des Bildes (mit Dateiendung)</li>
<li>Bei Animationen: Größe des einzelnen Sprites</li>
</ol>
<ul>
<li>Projektile</li>
</ul>
<oltype="1">
<li>Spawnkoordinaten (bei schießender Figur: rect.topleft/topright der
Figur)</li>
<li>Variable in der Bewegung gespeichert ist (bei schießender Figur:
z.B. player.move oder enemy.move) oder Winkel des Pfeils (bei
AngleProjectile. Bei schießendem Spieler player.angle)</li>
<li>Größe des Bildes [x, y]</li>
<li>Dateiname des Bildes (mit Dateiendung)</li>
<li>Geschwindigkeit der Pfeile</li>
</ol>
<p>Wenn man diese Parameter übergibt sieht das etwa so aus:</p>
<divclass="sourceCode"id="cb2"><pre
class="sourceCode python"><codeclass="sourceCode python"><spanid="cb2-1"><ahref="#cb2-1"aria-hidden="true"tabindex="-1"></a>player <spanclass="op">=</span> LinearPlayer((<spanclass="dv">32</span>,<spanclass="dv">32</span>),(<spanclass="dv">48</span>,<spanclass="dv">48</span>) ,<spanclass="st">"yeti.png"</span>, tilemap, platforms) <spanclass="co"># Hier wird der Spieler erstellt. Die Parameter sind:</span></span>
<spanid="cb2-3"><ahref="#cb2-3"aria-hidden="true"tabindex="-1"></a><spanclass="co">(32,32) -> Die Koordinaten</span></span>
<spanid="cb2-4"><ahref="#cb2-4"aria-hidden="true"tabindex="-1"></a><spanclass="co">(48,48) -> Die Größe in Pixel</span></span>
<spanid="cb2-5"><ahref="#cb2-5"aria-hidden="true"tabindex="-1"></a><spanclass="co">"yeti.png" -> Der Dateiname</span></span>
<spanid="cb2-6"><ahref="#cb2-6"aria-hidden="true"tabindex="-1"></a><spanclass="co">tilemap -> Die Karte</span></span>
<spanid="cb2-7"><ahref="#cb2-7"aria-hidden="true"tabindex="-1"></a><spanclass="co">platforms -> Die Spritegruppe der Plattformen und Wände</span></span>
<li><p>Erstelle Objekte in Tiled mit gleichen Namen</p></li>
<li><p>Erstelle eine Spritegruppe und nutze eine <em>for-Schleife</em>
um alle Objekte mit dem Namen der Gruppe hinzuzufügen. Dann kannst du
sie zeichnen</p></li>
</ol>
<divclass="sourceCode"id="cb4"><pre
class="sourceCode python"><codeclass="sourceCode python"><spanid="cb4-1"><ahref="#cb4-1"aria-hidden="true"tabindex="-1"></a>coins <spanclass="op">=</span> pygame.sprite.RenderClear() <spanclass="co"># Erstellen der Spritegruppe</span></span>
<spanid="cb4-2"><ahref="#cb4-2"aria-hidden="true"tabindex="-1"></a><spanclass="cf">for</span> objects <spanclass="kw">in</span> tilemap.data.objects: <spanclass="co"># Alle Objekte, die Tiled hat, werden durchgeprüft</span></span>
<spanid="cb4-3"><ahref="#cb4-3"aria-hidden="true"tabindex="-1"></a><spanclass="cf">if</span> objects.name <spanclass="op">==</span><spanclass="st">"coin"</span>: <spanclass="co"># Wenn der Name des Objektes "wall" ist, dann...</span></span>
<spanid="cb4-4"><ahref="#cb4-4"aria-hidden="true"tabindex="-1"></a> coins.add(Coin(objects.x, objects.y, objects.width, objects.height)) <spanclass="co"># Erstelle ein Objekt aus der Klasse "Coin" mit exakt den Koordinaten und Größe und füge es der Spritegruppe "coins" hinzu.</span></span>
<spanid="cb4-10"><ahref="#cb4-10"aria-hidden="true"tabindex="-1"></a> coins.draw(objects_surface) <spanclass="co"># Jetzt zeichne die gesamte Spritegruppe auf den Screen (siehe unten)</span></span></code></pre></div>
<h2id="wie-zeichne-ich-auf-den-bildschirm">Wie zeichne ich auf den
Bildschirm?</h2>
<p>Ähnlich zu einem Film, der aus vielen Bildern (<em>Frames</em>)
besteht, funktioniert auch PyGame. Erst wird eine einfarbige Fläche
erstellt, auf die dann die Sprites vorgezeichnet werden und wenn alles
fertig wird, wird es auf einmal gemalt. Dabei gilt:</p>
<p>Der Code wird von oben nach unten gelesen. Daher muss sie immer
<spanid="cb5-2"><ahref="#cb5-2"aria-hidden="true"tabindex="-1"></a> screen.fill((<spanclass="dv">0</span>,<spanclass="dv">0</span>,<spanclass="dv">0</span>)) <spanclass="co"># Das füllt den Bildschirm mit einer Farbe. Daher bitte immer zuerst.</span></span>
<spanid="cb5-3"><ahref="#cb5-3"aria-hidden="true"tabindex="-1"></a> screen.blit(tilemap_image, (<spanclass="dv">0</span>,<spanclass="dv">0</span>)) <spanclass="co"># Auf den schwarzen Bildschirm wird die Karte gemalt.</span></span>
<spanid="cb5-5"><ahref="#cb5-5"aria-hidden="true"tabindex="-1"></a><spanclass="co"># Hier werden Charaktere und Objekte "vorgezeichnet". Da ist die Reihenfolge relativ egal.</span></span>
<p>Das findet für jeden Frame erneut statt und steht in der
<em>update-Funktion</em>, die immer wieder aufgerufen wird. Damit in dem
Spiel etwas passiert und nicht immer das gleiche Bild entsteht, muss
zwischen den Aufrufen der <em>update-Funktion</em> noch etwas geschehen:
Spiel-Logik.</p>
<h2id="die-spiel-logik">Die Spiel-Logik</h2>
<p>Ein interaktives Spiel braucht eine Steuerung. Vor jedem
<em>update</em>-Aufruf wird geprüft, ob gerade eine Pfeiltaste gedrückt
ist. Wenn das so ist, wird die Position des Players verändert, sodass
die Figur im nächsten Frame eine andere Position hat.</p>
<divclass="sourceCode"id="cb6"><pre
class="sourceCode python"><codeclass="sourceCode python"><spanid="cb6-1"><ahref="#cb6-1"aria-hidden="true"tabindex="-1"></a><spanclass="cf">for</span> event <spanclass="kw">in</span> pygame.event.get(): <spanclass="co">#Jedes Event wird in jedem Frame geprüft</span></span>
<spanid="cb6-2"><ahref="#cb6-2"aria-hidden="true"tabindex="-1"></a><spanclass="cf">if</span> event.<spanclass="bu">type</span><spanclass="op">==</span> pygame.QUIT: <spanclass="co"># Hier wird geprüft ob auf das X oben rechts gedrückt wurde.</span></span>
<spanid="cb6-4"><ahref="#cb6-4"aria-hidden="true"tabindex="-1"></a><spanclass="cf">if</span> event.<spanclass="bu">type</span><spanclass="op">==</span> pygame.KEYDOWN: <spanclass="co"># Hier wird geprüft ob irgendeine Taste gedrückt wurde.</span></span>
<spanid="cb6-5"><ahref="#cb6-5"aria-hidden="true"tabindex="-1"></a><spanclass="cf">if</span> event.key <spanclass="op">==</span> pygame.K_LSHIFT: <spanclass="co"># Danach schaut man, *welche* Taste das ist.</span></span>
<spanid="cb6-7"><ahref="#cb6-7"aria-hidden="true"tabindex="-1"></a><spanclass="cf">if</span> event.<spanclass="bu">type</span><spanclass="op">==</span> pygame.KEYUP: <spanclass="co"># Hier wird geprüft ob irgendeine Taste losgelassen wurde.</span></span>
<spanid="cb6-8"><ahref="#cb6-8"aria-hidden="true"tabindex="-1"></a><spanclass="cf">if</span> event.key <spanclass="op">==</span> pygame.K_LSHIFT: <spanclass="co"># Danach schaut man wieder, welche Taste genau.</span></span>
<h3id="wie-ändere-ich-die-vorgegebenen-templates">Wie ändere ich die
vorgegebenen Templates?</h3>
<p>Ein großer Teil der Arbeit besteht darin, das was es schon gibt, auf
die eigenen Bedürfnisse anzupassen. Sagen wir mal, wir wollen, dass
Spieler nicht nur mit WASD steuerbar ist sondern auch mit den
Pfeiltasten. Dann tun wir folgendes:</p>
<oltype="1">
<li>Wir erstellen eine neue <em>Klasse</em> (kein Objekt!) mit der
Vorlage, die wir wollen (hier LinearPlayer)</li>
</ol>
<divclass="sourceCode"id="cb8"><pre
class="sourceCode python"><codeclass="sourceCode python"><spanid="cb8-1"><ahref="#cb8-1"aria-hidden="true"tabindex="-1"></a><spanclass="kw">class</span> Player(LinearPlayer): <spanclass="co"># Wir erstellen einen neue Klasse, die von *LinearPlayer* erbt</span></span>
<spanid="cb8-3"><ahref="#cb8-3"aria-hidden="true"tabindex="-1"></a><spanclass="bu">super</span>().<spanclass="fu">__init__</span>(<spanclass="va">self</span>, location, size, image, tilemap, walls) <spanclass="co"># Diese Klasse hat exakt dieselben Eigenschaften wie die Vorlage (=Vererbung). Mit super() erhält man die Super-Klasse, also LinearPlayer</span></span></code></pre></div>
<p>Nun können wir die Klasse verändern oder neue Dinge hinzufügen.</p>
<olstart="2"type="1">
<li>Bestehendes Abändern</li>
</ol>
<divclass="sourceCode"id="cb9"><pre
class="sourceCode python"><codeclass="sourceCode python"><spanid="cb9-1"><ahref="#cb9-1"aria-hidden="true"tabindex="-1"></a><spanclass="kw">class</span> Player(LinearPlayer): <spanclass="co"># Wir erstellen einen neue Klasse, die von <em>LinearPlayer</em> erbt</span></span>
<spanid="cb9-3"><ahref="#cb9-3"aria-hidden="true"tabindex="-1"></a><spanclass="bu">super</span>().<spanclass="fu">__init__</span>(<spanclass="va">self</span>, location, size, image, tilemap, walls) <spanclass="co"># Diese Klasse hat exakt dieselben Eigenschaften wie die Vorlage (=Vererbung)</span></span>
<spanid="cb9-4"><ahref="#cb9-4"aria-hidden="true"tabindex="-1"></a><spanclass="va">self</span>.keys <spanclass="op">=</span> { <spanclass="co"># self.keys ist schon in der übergeordneten Klasse definiert. Das überschreiben wir hier.</span></span>
<spanid="cb9-8"><ahref="#cb9-8"aria-hidden="true"tabindex="-1"></a><spanclass="st">"right"</span>: pygame.K_RIGHT, <spanclass="co">#Hier standen vorher die Kasten W, A, S und D. Eine Übersicht über alle Tasten ist hier zu finden: https://www.pygame.org/docs/ref/event.html </span></span>
class="sourceCode python"><codeclass="sourceCode python"><spanid="cb10-1"><ahref="#cb10-1"aria-hidden="true"tabindex="-1"></a><spanclass="kw">class</span> Player(LinearPlayer): <spanclass="co"># Wir erstellen einen neue Klasse, die von <em>LinearPlayer</em> erbt</span></span>
<spanid="cb10-3"><ahref="#cb10-3"aria-hidden="true"tabindex="-1"></a><spanclass="bu">super</span>().<spanclass="fu">__init__</span>(<spanclass="va">self</span>, location, size, image, tilemap, walls) <spanclass="co"># Diese Klasse hat exakt dieselben Eigenschaften wie die Vorlage (=Vererbung)</span></span>
<spanid="cb10-4"><ahref="#cb10-4"aria-hidden="true"tabindex="-1"></a><spanclass="va">self</span>.keys <spanclass="op">=</span> { <spanclass="co"># self.keys ist schon in der übergeordneten Klasse definiert. Das überschreiben wir hier.</span></span>
<spanid="cb10-8"><ahref="#cb10-8"aria-hidden="true"tabindex="-1"></a><spanclass="st">"right"</span>: pygame.K_RIGHT, <spanclass="co">#Hier standen vorher die Kasten W, A, S und D. Eine Übersicht über alle Tasten ist hier zu finden: https://www.pygame.org/docs/ref/event.html </span></span>
<spanid="cb10-14"><ahref="#cb10-14"aria-hidden="true"tabindex="-1"></a><spanclass="kw">def</span> say_hi(<spanclass="va">self</span>, sound): <spanclass="co"># Ab hier beginnt eine neue Funktion</span></span>
<spanid="cb10-18"><ahref="#cb10-18"aria-hidden="true"tabindex="-1"></a> hi <spanclass="op">=</span> pygame.mixer.Sound(<spanclass="st">"./sound/"</span><spanclass="op">+</span> sound)</span>
<p>Die Schriftgröße kann in der settings.py editiert werden.</p>
<h2id="bilder-spiegeln-und-rotiern">Bilder spiegeln und rotiern</h2>
<p>Während deiner Arbeit kann es passieren, dass du ein Bild aus
verschienden Gründen spiegeln möchtest. Dafür benutzt du die Funktion
<b>reflect_image()</b>.</p>
<p>Diese benutzt du so:</p>
<divclass="sourceCode"id="cb15"><pre
class="sourceCode python"><codeclass="sourceCode python"><spanid="cb15-1"><ahref="#cb15-1"aria-hidden="true"tabindex="-1"></a>reflect_image(<spanclass="st">"Dateiname des Bildes"</span>, (<spanclass="va">True</span><spanclass="op">/</span>Fales wenn du das Bild horizontal spieln willst), (<spanclass="va">True</span><spanclass="op">/</span><spanclass="va">False</span> wenn du dsa Bild vertikal gespiegelt werden soll))</span></code></pre></div>
<p>Wenn du aber ein Bild drehen willst benutzt du
<b>rotate_image</b>.</p>
<divclass="sourceCode"id="cb16"><pre
class="sourceCode python"><codeclass="sourceCode python"><spanid="cb16-1"><ahref="#cb16-1"aria-hidden="true"tabindex="-1"></a>rotate_image(<spanclass="st">"Dateiname des Bildes"</span>, (Zahl um wie viel Grad das Bild gedreht werden soll))</span></code></pre></div>
<h2id="neustarten-des-programmes">Neustarten des Programmes</h2>
<p>Code immer wieder zu stoppen und zu starten kann nervig sein. Daher
kann die <b>retry()</b> ein Programm einfacher neustarten. Dabei
braucht sie immer <b>__file__</b> als <em>Parameter</em>. Das hat
einen ganz einfachen Grund: Die Funktion ruft einen neuen Prozess auf,
der die ganze Datei neustartet und daher den genauen Dateipfad braucht.
Deswegen ist es auch ganz wichtig, dass die Funktion nicht dazu benutzt
wird, um von einem Chekpoint neuzustarten, das wird nicht funktionieren.
Nur wenn man das ganze Programm neustarten will, ist das eine