Pygbag objektorientiert – ein Template

Python
Pygame
Pygbag
Spieleprogrammierung
OOP
Autor:in

Jörg Kantel

Veröffentlichungsdatum

8. Juni 2023

Immer noch läßt mich Pygbag, das kleine Tool, das Pygame-Spiele nach WebAssembly (WASM) kompiliert, so daß diese auch im Browser laufen können, nicht los. Und da mein Ziel momentan sowohl in Python und Pygame, wie auch in P5.js und P5.play (JavaScript) auf einer sauberen, objektorientierten Implementierung ausgerichtet ist, hatte ich mich gestern hingesetzt und ein Template erstellt, daß meiner Vorstellung von Onjektorientierung in Python entspricht und das als Basis für weitere Programme gedacht ist.

Ich habe mich dabei weitestgehend an mein an Greenfoot angelehntes Pygame Framework gehalten, das ich im Januar schon einmal vorgestellt hatte. Lediglich die Hauptschleife verlangte eine Abweichung von der reinen Lehre, da Pygbag nicht mitspielte. Denn ursprünglich hatte ich – analog zu der Pure-Pygame-Implementierung – in der Klasse GameWorld eine Methode run() vorgesehen, die die Hauptschleife wie folgt implementierte,

    def run(self):
        # Hauptschleife des Spiels
        while self.keep_going:
            self.clock.tick(FPS)
            self.events()
            self.update()
            self.draw()
            await asyncio.sleep(0)  # Very important, and keep it 0

die ich im Programm (nachdem ich mit world = GameWorld eine Instanz der Spielewelt initalisiert hatte) wie folgt aufrufen wollte:

async def main():
    world.run()

asyncio.run(main())

Das funktionierte seltsamerweise zwar im Python-Interpreter, aber nicht mehr im Browser. Also habe ich die run()-Methode wieder gelöscht und die einzelnen GameWorld-Methoden hintereinander in der main()-Schleife aufgerufen:

async def main():
    while world.keep_going:
        world.clock.tick(FPS)
        world.events()
        world.update()
        world.draw()
        await asyncio.sleep(0)  # Very important, and keep it 0

asyncio.run(main())

Das funktionierte dann sowohl im Python-Interpreter wie auch im Browser, wie Ihr an meinem auf Itch.io hochgeladenen Template ausprobieren könnt.

Es passiert noch nicht viel – es ist ja nur ein Template, aber mit den Tasten a (= Links), d (= rechts), w (= hoch) und s (= runter) könnt Ihr die kleine Spielekonsole durch das Fenster steuern. Damit habe ich (hoffentlich!) die Grundlagen gelegt, um mit den (teilweisen noch leeren) implementierten Methoden ein vollständiges Spiel zu entwickeln:

import pygame as pg
import asyncio
from pygame.locals import *
import os, sys

## Settings
WIDTH, HEIGHT = 640, 480   # 16x16: 40, 30; 32x32: 20, 15
TITLE = "Pygame OOP Template"
FPS = 60                   # Frame per second
PLAYER_WIDTH = 30
PLAYER_HEIGHT = 45
PLAYER_SPEED = 5

## Hier wird der Pfad zum Verzeichnis der Assets gesetzt
DATAPATH = os.path.join(os.getcwd(), "data")

## Farben
BG_COLOR = (40, 40, 40)

# Klassen

## Class GameWorld
class GameWorld:

    def __init__(self):
        # Pygame und das Fenster initialisieren
        pg.init()
        self.screen = pg.display.set_mode((WIDTH, HEIGHT))
        pg.display.set_caption(TITLE)

        self.clock = pg.time.Clock()
        self.keep_going = True

    def reset(self):
        # Neustart oder Status zurücksetzen
        # Hier werden alle Elemente der GameWorld initialisiert
        self.all_sprites = pg.sprite.Group()
        self.player = Player()
        self.all_sprites.add(self.player)
  
    def events(self):
        for event in pg.event.get():
            if ((event.type == pg.QUIT)
                or (event.type == pg.KEYDOWN
                and event.key == pg.K_ESCAPE)):
                self.keep_going = False
                self.game_over()

    def update(self):
        self.all_sprites.update()

    def draw(self):
        self.screen.fill(BG_COLOR)
        self.all_sprites.draw(self.screen)
        pg.display.flip()

    def start_screen(self):
        pass
    
    def win_screen(self):
        pass
    
    def loose_screen(self):
        pass

    def game_over(self):
        # print("Bye, Bye, Baby!")
        pg.quit()
        sys.exit()
## Ende Class GameWorld

## Class Player
class Player(pg.sprite.Sprite):

    def __init__(self):
        pg.sprite.Sprite.__init__(self)
        # Load Image
        img = pg.image.load(os.path.join(DATAPATH, "platformchar_idle.png")).convert_alpha()
        self.image = img
        self.rect = self.image.get_rect()
        self.x, self.y = WIDTH/2, HEIGHT/2
        self.speed = PLAYER_SPEED
    
    def update(self):
        keys = pg.key.get_pressed()
        if keys[pg.K_w]:     # UP
            if self.y > PLAYER_HEIGHT - 20:
                self.y -= self.speed
        elif keys[pg.K_s]:   # DOWN
            if self.y < HEIGHT - PLAYER_HEIGHT:
                self.y += self.speed
        elif keys[pg.K_a]:   # LEFT
            if self.x > PLAYER_WIDTH:
                self.x -= self.speed
        elif keys[pg.K_d]:   # RIGHT
            if self.x < WIDTH - PLAYER_WIDTH:
                self.x += self.speed
        else:
            self.x += 0
            self.y += 0
        self.rect.center = (self.x, self.y)
## End Class Player

# Hauptprgramm
world = GameWorld()
world.start_screen()
world.reset()

# Hauptschleife
async def main():
    while world.keep_going:
        world.clock.tick(FPS)
        world.events()
        world.update()
        world.draw()
        await asyncio.sleep(0)  # Very important, and keep it 0

asyncio.run(main())

Denn Quellcode für dieses Template (mit den Assets und dem build-Folder) habe ich auch wieder auf mein GitHub-Repositorium hochgeladen und das fertige Skript könnt Ihr – wie ich oben schon erwähnte – auch auf Itch.io spielen.

Die kleine gelbe Spielekonsole entstammt dem gestern schon erwähnten freien (CC0) »Simplified Platformer Pack« von Kenney.nl.

Jetzt habe ich die Grundlagen für weitere Experimente mit Pygame im Browser gelegt. Schaun wir mal, was sich daraus noch entwickeln wird. Still digging!