image image


image

Spieleprogrammierung: Ein Roguelike in Pygame Zero

Auch wenn das Buch »Code the Classics Volume 1« nach mehr als drei Wochen immer noch nicht bei mir eingetroffen ist, habe ich mein Interesse an Pygame Zero immer noch nicht verloren. Um die Wartezeit zu überbrücken, habe ich mit der Programmierung eines Roguelikes begonnen. Es soll kein vollständiges Spiel werden, sondern nur die grundlegenden Mechaniken zeigen und prüfen, ob so etwas in Pygame Zero leicht zu implementieren ist.

Pygame Zero hält eine gedrückte Taste für eine gedrückte Taste und führt daher die damit ausgelösten Bewegungen immer wieder aus, sechzig Mal in der Sekunde. Daher waren die boolschen Variablen

hero_walking = True
enemy_walking = False

notwendig, die nach einer Bewegung des Helden und seines Gegners (seiner Gegner) sofort auf False gesetzt werden müssen. Denn Roguelikes sind rundenbasierte Spiele, sie funktionieren fast so wie ein Schachspiel. Der Held setzt einen Zug, danach sind die Gegner an der Reihe und dann darf der Held wieder ziehen. Pygame Zero bietet hierfür die Funktion on_key_up() an:

def on_key_up():
    global hero_walking, enemy_walking
    hero_walking = True
    enemy_walking = True

Hier werden erst wenn die Taste wieder losgelassen wird die beiden boolschen Variablen zurück auf True gesetzt.

Wo ich ich schon einmal dabei war, habe ich auch gleich die Funktion on_key_down() genutzt,

def on_key_down():
    if keyboard.escape:
        sys.exit()

denn ich beende ein Spiel lieber mit der Escape-Taste als mit einem Mausklick in den Schließknopf des Fensters. Außerdem bin ich dieses Verhalten von Pygame gewohnt und es ist mir unverständlcih, warum die Macher von Pygame Zero es nicht als Default-Verhalten implementiert haben.

Die anderen Reaktionen auf Tastendruck habe ich aus Symmetriegründen in die Funktion hero_move() gesteckt, weil ich auch eine Funktion enemy_move() implementiert hatte:

def hero_move():
    global hero_walking
    old_hero_x = hero.x
    old_hero_y = hero.y
    if keyboard.left:
        if hero_walking:
            hero.x -= TILESIZE
            hero_walking = False
    if keyboard.right:
        if hero_walking:
            hero.x += TILESIZE
            hero_walking = False
    if keyboard.up:
        if hero_walking:
            hero.y -= TILESIZE
            hero_walking = False
    if keyboard.down:
        if hero_walking:
            hero.y += TILESIZE
            hero_walking = False

Der Gegner ist der zappelige Hulk, weil seine Bewegungen etwas hektisch und zappelig wirken und weil er ein grünes Monster ist. Wenn der Mindestabstand zwischen dem Helden und dem Monster eine gewisse Distanz unterschritten hat, versucht der zappelige Hulk dem Helden zu folgen, andernfalls zappelt er hin und her.

def enemy_move():
    global enemy_walking
    old_enemy_x = enemy.x
    old_enemy_y = enemy.y
    if enemy.distance_to(hero) < MAXDIST:
        if enemy.x > hero.x and enemy_walking:
            enemy.x -= TILESIZE
            enemy_walking = False
        elif enemy.x < hero.x and enemy_walking:
            enemy.x += TILESIZE
            enemy_walking = False
        if enemy.y > hero.y and enemy_walking:
            enemy.y -= TILESIZE
            enemy_walking = False
        elif enemy.y < hero.y and enemy_walking:
            enemy.y += TILESIZE
            enemy_walking = False
    elif enemy_walking:
        if randint(0, 10) > 8:
            enemy.y = choice([enemy.y + TILESIZE, enemy.y - TILESIZE])
        else:
            enemy.x = choice([enemy.x + TILESIZE, enemy.x - TILESIZE])
        enemy_walking = False

Das sind die Hauptbestandteile meines kleinen Experiments. Das vollständige Programm sieht so aus:

import pgzrun
from random import choice, randint
import sys

WIDTH = 640
HEIGHT = 480
TITLE = "Rogue 1"
TILESIZE = 32
HTILES = 20
VTILES = 15
MAXDIST = 5*TILESIZE

LEVEL_1 = [
    "####################",
    "#@ #               #",
    "#  #   ##   ####   #",
    "#       #   #      #",
    "#       #   ####   #",
    "#####   #   #      #",
    "#       #   ####   #",
    "#   #####          #",
    "#                 E#",
    "#   ################",
    "#                  #",
    "#########    ###   #",
    "#            #     #",
    "#            #     #",
    "####################"
]

hero_walking = True
enemy_walking = False

def legal_move(walls, actor):
    for wall in walls:
        if wall.colliderect(actor):
            return(True)

## Der Held
hero = Actor("hero")

def hero_move():
    global hero_walking
    old_hero_x = hero.x
    old_hero_y = hero.y
    if keyboard.left:
        if hero_walking:
            hero.x -= TILESIZE
            hero_walking = False
    if keyboard.right:
        if hero_walking:
            hero.x += TILESIZE
            hero_walking = False
    if keyboard.up:
        if hero_walking:
            hero.y -= TILESIZE
            hero_walking = False
    if keyboard.down:
        if hero_walking:
            hero.y += TILESIZE
            hero_walking = False
    
    ## Kollision mit Wall
    if legal_move(walls, hero):
        hero.x = old_hero_x
        hero.y = old_hero_y

def reset_hero(x, y):
    hero.topleft = (x*TILESIZE, y*TILESIZE)

## Der Feind
enemy = Actor("enemy")

def enemy_move():
    global enemy_walking
    old_enemy_x = enemy.x
    old_enemy_y = enemy.y
    if enemy.distance_to(hero) < MAXDIST:
        if enemy.x > hero.x and enemy_walking:
            enemy.x -= TILESIZE
            enemy_walking = False
        elif enemy.x < hero.x and enemy_walking:
            enemy.x += TILESIZE
            enemy_walking = False
        if enemy.y > hero.y and enemy_walking:
            enemy.y -= TILESIZE
            enemy_walking = False
        elif enemy.y < hero.y and enemy_walking:
            enemy.y += TILESIZE
            enemy_walking = False
    elif enemy_walking:
        if randint(0, 10) > 8:
            enemy.y = choice([enemy.y + TILESIZE, enemy.y - TILESIZE])
        else:
            enemy.x = choice([enemy.x + TILESIZE, enemy.x - TILESIZE])
        enemy_walking = False

    ## Kollision mit Wall
    if legal_move(walls, enemy):
        enemy.x = old_enemy_x
        enemy.y = old_enemy_y

## User Input
def on_key_down():
    ## Spielende mit ESC
    if keyboard.escape:
        sys.exit()

def on_key_up():
    global hero_walking, enemy_walking
    hero_walking = True
    enemy_walking = True

## Level
walls = []

def setup_maze(level):
    for y in range (len(level)):
        for x in range (len(level[y])):
            sprite = level[y][x]
            if sprite == "#":
                wall = Actor("wall")
                wall.topleft = (x*TILESIZE, y*TILESIZE)
                walls.append(wall)
            if sprite == "@":
                hero.topleft = (x*TILESIZE, y*TILESIZE)
            if sprite == "E":
                enemy.topleft = (x*TILESIZE, y*TILESIZE)

## Das Spiel
setup_maze(LEVEL_1)
    
def draw():
    screen.fill((128, 128, 130))
    for wall in walls:
        wall.draw()
    enemy.draw()
    hero.draw()

def update():
    hero_move()
    enemy_move()
    if enemy.colliderect(hero):
        reset_hero(1, 1)

pgzrun.go()

Natürlich findet Ihr den kompletten Sourcecode und die verwendeten Assets in meinem GitHub-Repositorium. Die Sprites und die Mauerteile habe ich dem freien (CC BY 3.0) Tilesets von TomeTik entnommen.

Jetzt müßt Ihr mir nur noch die Daumen drücken, daß »Code the Classics Volume 1« bald bei mir eintrifft. Denn das PDF, das man kostenlos herunterladen kann, ist doch recht mühselig zu lesen (ich müßte es ausdrucken) und ich habe große Lust, mit Pygame Zero noch mehr anzustellen.

image


(Kommentieren) 

image image



Über …

Der Schockwellenreiter ist seit dem 24. April 2000 das Weblog digitale Kritzelheft von Jörg Kantel (Neuköllner, EDV-Leiter Rentner, Autor, Netzaktivist und Hundesportler — Reihenfolge rein zufällig). Hier steht, was mir gefällt. Wem es nicht gefällt, der braucht ja nicht mitzulesen. Wer aber mitliest, ist herzlich willkommen und eingeladen, mitzudiskutieren!

Alle eigenen Inhalte des Schockwellenreiters stehen unter einer Creative-Commons-Lizenz, jedoch können fremde Inhalte (speziell Videos, Photos und sonstige Bilder) unter einer anderen Lizenz stehen.

Der Besuch dieser Webseite wird aktuell von der Piwik Webanalyse erfaßt. Hier können Sie der Erfassung widersprechen.

Diese Seite verwendet keine Cookies. Warum auch? Was allerdings die iframes von Amazon, YouTube und Co. machen, entzieht sich meiner Kenntnis.


Werbung


Werbung


image  image  image
image  image  image


image