Retrogaming mit Python: Pyxel-Tutorial Stage 1

Pyxel
Python
Retrogaming
Spieleprogrammierung
Spyder
Autor:in

Jörg Kantel

Veröffentlichungsdatum

4. August 2024

Da meine Erkundungen letzte Woche über Python, Py5 und PyScript mich zu einer Denkpause veranlassten, dachte ich mir, daß ich ja in der Zwischenzeit ein wenig an einem schon im März angekündigten Tutorial zu Pyxel, der an TIC-80 angelehnten Python-»Konsole« für Retrospiele arbeiten könnte. Denn da die Webversion von Pyxel wie PyScript auf Pyodide aufsetzt und damit den kompletten Scientific Stack (und mehr) unterstützt, kann ich hier nicht viel falsch machen.

Also habe ich erst einmal – wie hier beschrieben – auch für Pyxel mit conda eine virtuelle Umgebung aufgesetzt, denn ich wollte als IDE gerne Spyder einsetzen, da mein Visual Studio Code mittlerweile schon wieder so überfrachtet ist, daß sich die einzelnen Extensions/Plugins gegenseitig in die Quere geraten.

Wie der Screenshot im Bannerbild oben zeigt, ist mir dies gelungen und die Spynne arbeitet wunderbar mit Pyxel zusammen. Doch nun zum Code dieses ersten Tutorials:

import pyxel as px

TS = 8   # Tilesize

class App:
    def __init__(self):
        px.init(10*TS, 10*TS, "Pyxel-Tutorial 1", display_scale = 5)
        self.x = 0   # px.width/2
        self.y = 0   # px.height/2
        self.speed = TS

        #Read resource file
        px.load("assets/firststep.pyxres")
        
        px.run(self.update, self.draw)
        
    def update(self):
        if px.btnp(px.KEY_RIGHT):
            if self.x + self.speed >= px.width - TS:
                self.x = px.width - TS
            else:
                self.x += self.speed
        elif px.btnp(px.KEY_LEFT):
            if self.x - self.speed <= 0:
                self.x = 0
            else:
                self.x -= self.speed
        elif px.btnp(px.KEY_UP):
            if self.y - self.speed <= 0:
                self.y = 0
            else:
                self.y -= self.speed
        elif px.btnp(px.KEY_DOWN):
            if self.y + self.speed >= px.height - TS:
                self.y = px.height - TS
            else:
                self.y += self.speed
            

    def draw(self):
        px.cls(3)

        #Image drawing:(x, y, img, u, v, w, h, [colkey])
        # xy:Copy destination coordinates, img:Image bank number
        # uv:Coordinates of copy source, wh:Copy range, colkey:Transparent color
        px.blt(self.x, self.y, 0, 0, 0, 8, 8, 0)

App()

Ich habe – wie empfohlen – die gesamte Applikation in einer eigenen Klasse gekapselt, die ich am Ende mit dem Befehl App() in der letzten Zeile aufrufe. Das funktioniert, weil ich die Methode pyxel.run() im Constructor der Klasse aufrufe. (Ich habe pyxel als px importiert. Ob das eine so gute Idee war, weiß ich noch nicht, eventuell mache ich dies wieder rückgänig.)

Das Programm macht noch nicht viel: In update() werden die Pfeiltasten abgefragt und damit die Spielfigur über den Bildschirm bewegt. Dabei ist sie in dem Fenster gefangen, erreicht oder überschreitet sie die Fenstergrenzen, wird sie auf diese wieder zurückgesetzt. In draw() wird nur der Hintergrund neu gezeichnet und dann mit pyxel.blt() die Spielefigur an die aktuelle Position gesetzt.

Diese ist im Pyxel-Image-Editor in der Ressourcen-Datei assets/firststep.pyxres erstellt worden. Da dies momentan noch die einzige Ressource ist, ist die Banknummer \(0\), die Koordinaten des Bildes stehen an der Position \(0, 0\), das Bild ist \(8\)x\(8\) Pixel weit und die schwarze Hintergrundfarbe des Bildes an der Position \(0\) der Farbpalette wird als transparent gesetzt. Diese vielen Paramter von pyxel.blit() wirken erst einmal etwas verwirrend, machen den Aufruf aber extrem kompakt.

In der nächsten Version möchte ich die Spielewelt mit Hindernissen versehen, auf die der Spieler reagieren muß. Still digging!