MicroStudio und Python: Tutorial 1

microStudio
Spieleprogrammierung
Python
Brython
Autor:in

Jörg Kantel

Veröffentlichungsdatum

22. März 2024

Bevor ich mich in das vorgestern vorgestellte Abenteuer Pyxel stürze, möchte ich meine begonnene, kleine Tutorialreihe zu microStudio wenigstens zu einem vorläufigen Abschluß bringen. Und da Pyxel in Python programmiert wird, dachte ich, daß es an der Zeit ist, auch die Programmierung von microStudio mit Python zu erkunden.

Die Python-Implementierung in microStudio basiert auf Brython, einer Python 3-Version für den Browser, die JavaScript als Skriptsprache ersetzen will. Das führt zu einigen Einschränkungen, unter anderem die, daß C-basierte Python-Module nicht unterstützt werden. Das betrifft insbesondere den Scientific Stack mit NumPy, SciPy und Pandas1. Dafür kann man in microStudio ähnliche, JavaScript basierte Bibliotheken einsetzen2.

Eine Folge ist, daß in Brython die Keyboard-Abfrage unter Umständen beim ersten Aufruf ein undefined zurückgibt. Um den Fehler zu umgehen, wird der Einsatz dieser Helper-Funktion

def check_input(obj, val):
  if hasattr(obj, val):
    return obj[val] != 0
  return 0

empfohlen. Damit kann dann die Abfrage mit check_input(keyboard, "<taste>") entschärft werden.

Nun aber zu meinem ersten microStudio/Python-Experiment: Es ist eine Abwandlung meines ersten microScript-Tutorials, nur daß ich hier zur Abwechslung mal wieder auf die beliebten Gameboy-Sprites von Kenneys freiem (CC0) Simplified Platformer Pack zurückgegriffen habe, die ich – wie im ersten Tutorial Beschrieben – mit der Bildverarbeitung meines Vertrauens zu microStudio-kompatiblen, animationstauglichen Bildstreifen zerlegt habe.

Der Code selber birgt keine großen Überraschungen. Ich habe erst eine Klasse Player angelegt,

class Player:
  
  def __init__(self):
    self.x = 0
    self.y = 0
    self.w = 50
    self.h = 50
    self.im = "idle_down"
    self.dir = "down"
    self.speed = 1
    
  def display(self):
    screen.drawSprite(self.im, self.x, self.y, self.w, self.h)

die neben dem Konstrutor nur noch die Methode display() besitzt. Das eigentliche Update des Players wird in der update()-Funktion des Hauptprogramms erledigt:

gboy = Player()

def init():
  # global gboy
  # gboy = Player()
  pass

def update():
  if check_input(keyboard, "LEFT"):
    gboy.dir = "left"
    gboy.im = "walking_left"
    gboy.x -= gboy.speed
  elif check_input(keyboard, "RIGHT"):
    gboy.dir = "right"
    gboy.im = "walking_right"
    gboy.x += gboy.speed
  elif check_input(keyboard, "UP"):
    gboy.dir = "up"
    gboy.im = "walking_up"
    gboy.y += gboy.speed
  elif check_input(keyboard, "DOWN"):
    gboy.dir = "down"
    gboy.im = "walking_right"
    gboy.y -= gboy.speed
  else:
    if gboy.dir == "right":
      gboy.im = "idle_right"
    elif gboy.dir == "left":
      gboy.im = "idle_left"
    elif gboy.dir == "up":
      gboy.im = "idle_up"
    elif gboy.dir == "down":
      gboy.im = "idle_down"
  wrap(gboy)

def draw():
  screen.clear()
  screen.fillRect(0, 0, screen.width, screen.height, "rgb(41, 54, 111")
  gboy.display()

In größeren Programmen sollte dies sicher der besseren Übersichtlichkeit wegen ebenfalls in einer Methode von Player gekapselt werden.

Eine weitere Besonderheit war mir noch aufgefallen: Wenn eine Instanz einer Klasse (in diesem Fall gboy = Player()) in der Funktion init() initialisiert wird, muß diese Variable dort als global deklariert werden (vergleiche die auskommentierten Zeilen). Da ich in solchen Fällen immer sofort Namensraumverschmutzung wittere, habe ich die Initialisierung außerhalb auf der obersten Ebene des Skriptes vorgenommen3. Damit ist sie automatisch und für alle sichtbar global.

Zu guter Letzt habe ich dann auch noch MrLmans wrap()-Funktion aus seiner games-prog library v.02 nach Python portiert und in dem Skript eingesetzt:

def wrap(obj, leeway = 0):
  if obj.x + leeway < -screen.width/2:
    obj.x = screen.width/2 + leeway
  elif obj.x - leeway > screen.width/2:
    obj.x = -screen.width/2 - leeway
  if obj.y + leeway < -screen.height/2:
    obj.y = screen.height/2 + leeway
  elif obj.y - leeway > screen.height/2:
    obj.y = -screen.height/2 - leeway

Hier ist das Ergebnis dieser wenigen Zeilen in seiner vollen Pracht:

Der kleine Gameboy kann mit den Pfeiltasten (oder (für die Traditionalisten unter Euch) mit den Tasten w, a, s und d) über den Bildschirm gescheucht werden.

Wie immer habe ich das Tutorial auch auf meinen microStudio-Account hochgeladen. Ihr könnt dort den Code studieren, ihn verändern und auch sonst alles damit anstellen, was Euch Spaß macht und/oder Euch weiterbringt.

Der Schritt von microScript nach Python verlief (bisher) überraschend problemlos. Ich werde die Reihe daher fortsetzen. Still digging!

Fußnoten

  1. Hier ist Pyxel weiter: Diese App basiert auf Pyodide und dieses Projekt kann mit großen Teilen des Scientific Stack umgehen (Numpy, SciPy, Pandas, aber auch Pillow werden in der Dokumentation expizit aufgeführt).↩︎

  2. Allerdings scheint die Integration von JavaScript in microStudio-Python/Bryton-Projekte nicht immer ganz schmerzfrei zu verlaufen. Hier ist neben dem (immer empfohlenen) Blick in die Dokumentation auch viel Experimentierfreude angesagt.↩︎

  3. Ob das wirklich eine gute Idee ist oder ob man doch besser in den sauren, globalen Apfel beißt, werden zukünftige Experimente zeigen. Ich bin da selber noch unsicher …↩︎