Spielewelten mit Raycasting: Frei beweglich

Wir haben uns im letzten Kapitel darauf beschränkt, dass die Spielfigur immer in der Mitte eines Feldes steht. Jetzt erweitern wir die Möglichkeiten dahingehend, dass ein freies Bewegen auch innerhalb der Felder möglich ist. Darüberhinaus werden wir am Ende dieses Kapitels die Wände mit Textur darstellen können.

Freies Gehen auf dem Spielfeld Bearbeiten

In diesem Abschnitt üben wir das freie Gehen auf dem Spielfeld. Die schon bekannten Funktionen werden dabei modifiziert.

Spielfeld.weglaenge ist dabei der Weg, den die Spielfigur auf einmal gehen kann. Im folgenden Code sind dafür 10 Pixel vorgesehen. In der Methode Spielfeld.geheVor() wird daher die Richtung der Spielfigur mit der Weglänge multipliziert. Anschließend wird geschaut, ob wir auf eine Wand treffen würden und dann gegangen.

Die Methoden rund um Spielfeld.findeWand() haben wir ebenfalls auf den neusten Stand gebracht. Ein gefundenes Wandelement hat nun eine Position auf der Spielfeldkarte wie auch eine Schnittpunktposition mit genau dieser gefundenen Wand. zeichneSpielfeld() stellt nun nicht nur die gefundene Wand dar, sondern auch den ermittelten Schnittpunkt.

#!/usr/bin/python
# -*- coding: utf-8 -*-
 
import math
import pygame
import sys
 
 
class Spielfeld(object):
 
    def __init__(self, dateiname, feldgroesse, weglaenge):
        """ Initialisiert das Spielfeld
          dateiname - Dateiname der Spielfeld-Datei
          feldgroesse - die Pixelgrösse eines quadratischen Feldes
          weglaenge - dieses Stück Pixelweg legt der Spieler bei einem Tastendruck zurück"""
        self.felder = []
        self.feldgroesse = feldgroesse
        self.feldgroesseD2 = feldgroesse // 2
        self.weglaenge = weglaenge
        self.blickrichtung = 160
        datei = open(dateiname, 'r')
        for zeile in datei:
            self.felder.append(zeile[:-1])
        datei.close()
        self.breite = len(self.felder[0])
        self.hoehe = len(self.felder)
        for num, zeile in enumerate(self.felder):
            idx = zeile.find('#')
            if idx >= 0:
                # Pixel-Position!!!
                self.position = (idx * self.feldgroesse + self.feldgroesseD2, num * self.feldgroesse + self.feldgroesseD2)
                self.felder[num] = self.felder[num].replace('#', '.')
                break
        self.WEITWEG = self.breite * self.hoehe * self.feldgroesse

 
    def zeichen(self, x, y):
        """Liefert das Zeichen an der Position x, y """
        assert(0 <= x < self.breite)
        assert(0 <= y < self.hoehe)
        return self.felder[y][x]
 
 
    def istWand(self, zeichen):
        """True, wenn das Zeichen ein Wandzeichen ist """
        return zeichen in ('N', 'W', 'S', 'O')
 
 
    def geheVor(self):
        """gehe in Richtung der Blickrichtung """
        px, py = self.position
        qx = int(round(px + self.weglaenge * math.cos(math.radians(self.blickrichtung))))
        qy = int(round(py - self.weglaenge * math.sin(math.radians(self.blickrichtung))))
        zeichen = self.zeichen(qx // self.feldgroesse, qy // self.feldgroesse)
        if not self.istWand(zeichen): 
            self.position = (qx, qy)  
 
 
    def geheZurueck(self):
        """gehe entgegen der Blickrichtung """
        self.blickrichtung += 180
        self.geheVor()
        self.blickrichtung -= 180
 
 
    def dreheLinks(self):
        self.blickrichtung = (self.blickrichtung + 5) % 360
 
 
    def dreheRechts(self):
        self.blickrichtung = (self.blickrichtung - 5) % 360


    def findeWandHV(self, winkel, px, py, fx, fy):
        """findet Wände in ausschließlich horizontaler/vertikaler Richtung"""
        rDict = {0 : (1, 0), 90 : (0, -1), 180 : (-1, 0), 270 : (0, 1)}
        rx, ry = rDict[winkel]
        feldX = fx + rx
        feldY = fy + ry
        feldZeichen = self.zeichen(feldX, feldY)
        while not self.istWand(feldZeichen):
            feldX = feldX + rx
            feldY = feldY + ry
            feldZeichen = self.zeichen(feldX, feldY)
        if winkel == 0:
            sx = feldX * self.feldgroesse 
            sy = py
            entfernung = sx - px
        elif winkel == 90:
            sx = px
            sy = (feldY + 1) * self.feldgroesse
            entfernung = py - sy
        elif winkel == 180:
            sx = (feldX + 1) * self.feldgroesse 
            sy = py
            entfernung = px - sx
        else: # winkel == 270
            sx = px
            sy = feldY * self.feldgroesse
            entfernung = sy - py
        return (feldX, feldY, feldZeichen, sx, sy, entfernung)


    def findeWandDiagonal(self, px, py, dx1, dy1, dx2, dy2, (kx, ky)):
        # Findet Wände in diagonaler Richtung
        sx = px + dx1
        sy = py + dy1
        feldX = int(sx / self.feldgroesse) + kx
        feldY = int(sy / self.feldgroesse) + ky
        if (0 <= feldX < self.breite) and (0 <= feldY < self.hoehe):
            feldZeichen = self.zeichen(feldX, feldY)
        else:
            return (0, 0, '0', 0, 0, self.WEITWEG)
        while not self.istWand(feldZeichen):
            sx = sx + dx2
            sy = sy + dy2
            feldX = int(sx / self.feldgroesse) + kx
            feldY = int(sy / self.feldgroesse) + ky
            if (0 <= feldX < self.breite) and (0 <= feldY < self.hoehe):
                feldZeichen = self.zeichen(feldX, feldY)
            else:
                return (0, 0, '0', 0, 0, self.WEITWEG)
        # wir haben die Wand gefunden
        ex = sx - px
        ey = sy - py
        entfernung = math.sqrt(ex * ex + ey * ey)
        return (feldX, feldY, feldZeichen, sx, sy, entfernung)


    def findeWand(self, winkel):
        winkel = winkel % 360
        px, py = self.position
        fx = px // self.feldgroesse
        fy = py // self.feldgroesse 
        # Spezialfall wegen Tangens
        if winkel % 90 == 0:
            return self.findeWandHV(winkel, px, py, fx, fy)
        else: # alle anderen Winkel
            rdict = {0 : ((0, -1), (0, 0)), 
                     1 : ((0, -1), (-1, 0)), 
                     2 : ((0, 0), (-1, 0)), 
                     3 : ((0, 0), (0, 0))}
            quadrant = int(winkel) // 90
            tangens = math.tan(math.radians(winkel))
            korr1, korr2 = rdict[quadrant]

            if quadrant == 0:
                # - Schnittpunkte mit horiz. Linien
                dy1 = py % self.feldgroesse
                dx1 = dy1 / tangens
                dy2 = self.feldgroesse
                dx2 = dy2 / tangens
                rx = 1.0
                ry = -1.0
                dx1 = rx * dx1
                dy1 = ry * dy1
                dx2 = rx * dx2
                dy2 = ry * dy2
                wand1 = self.findeWandDiagonal(px, py, dx1, dy1, dx2, dy2, korr1)

                # - Schnittpunkte mit vert. Linien
                dx1 = self.feldgroesse - px % self.feldgroesse
                dy1 = tangens * dx1
                dx2 = self.feldgroesse
                dy2 = tangens * dx2
                rx = 1.0
                ry = -1.0
                dx1 = rx * dx1
                dy1 = ry * dy1
                dx2 = rx * dx2
                dy2 = ry * dy2
                wand2 = self.findeWandDiagonal(px, py, dx1, dy1, dx2, dy2, korr2)
            elif quadrant == 1:
                # - Schnittpunkte mit horiz. Linien
                dy1 = py % self.feldgroesse
                dx1 = dy1 / tangens
                dy2 = self.feldgroesse
                dx2 = dy2 / tangens
                rx = 1.0
                ry = -1.0
                dx1 = rx * dx1
                dy1 = ry * dy1
                dx2 = rx * dx2
                dy2 = ry * dy2
                wand1 = self.findeWandDiagonal(px, py, dx1, dy1, dx2, dy2, korr1)

                # - Schnittpunkte mit vert. Linien
                dx1 = px % self.feldgroesse
                dy1 = tangens * dx1
                dx2 = self.feldgroesse
                dy2 = tangens * dx2
                rx = -1.0
                ry = 1.0
                dx1 = rx * dx1
                dy1 = ry * dy1
                dx2 = rx * dx2
                dy2 = ry * dy2
                wand2 = self.findeWandDiagonal(px, py, dx1, dy1, dx2, dy2, korr2)
            elif quadrant == 2:
                # - Schnittpunkte mit horiz. Linien
                dy1 = self.feldgroesse - py % self.feldgroesse
                dx1 = dy1 / tangens
                dy2 = self.feldgroesse
                dx2 = dy2 / tangens
                rx = -1.0
                ry = 1.0
                dx1 = rx * dx1
                dy1 = ry * dy1
                dx2 = rx * dx2
                dy2 = ry * dy2
                wand1 = self.findeWandDiagonal(px, py, dx1, dy1, dx2, dy2, korr1)

                # - Schnittpunkte mit vert. Linien
                dx1 = px % self.feldgroesse
                dy1 = tangens * dx1
                dx2 = self.feldgroesse
                dy2 = tangens * dx2
                rx = -1.0
                ry = 1.0
                dx1 = rx * dx1
                dy1 = ry * dy1
                dx2 = rx * dx2
                dy2 = ry * dy2
                wand2 = self.findeWandDiagonal(px, py, dx1, dy1, dx2, dy2, korr2)
            elif quadrant == 3:
                # - Schnittpunkte mit horiz. Linien
                dy1 = self.feldgroesse - py % self.feldgroesse
                dx1 = dy1 / tangens
                dy2 = self.feldgroesse
                dx2 = dy2 / tangens
                rx = -1.0
                ry = 1.0
                dx1 = rx * dx1
                dy1 = ry * dy1
                dx2 = rx * dx2
                dy2 = ry * dy2
                wand1 = self.findeWandDiagonal(px, py, dx1, dy1, dx2, dy2, korr1)

                # - Schnittpunkte mit vert. Linien
                dx1 = self.feldgroesse - px % self.feldgroesse
                dy1 = tangens * dx1
                dx2 = self.feldgroesse
                dy2 = tangens * dx2
                rx = 1.0
                ry = -1.0
                dx1 = rx * dx1
                dy1 = ry * dy1
                dx2 = rx * dx2
                dy2 = ry * dy2
                wand2 = self.findeWandDiagonal(px, py, dx1, dy1, dx2, dy2, korr2)
            if wand1[5] < wand2[5]:
                return wand1
            else:
                return wand2
        



def init(breite, hoehe):
    screen = pygame.display.set_mode((breite, hoehe))
    screen.fill((200, 200, 200))
    pygame.display.update()
    return screen
 
 
def zeichneSpielfeld(screen, breite, hoehe, spielfeld, wandpos):
    feldbreite = breite // spielfeld.breite
    feldhoehe = hoehe // spielfeld.hoehe
    # Spielfeld zeichnen
    for y in xrange(spielfeld.hoehe):
        for x in xrange(spielfeld.breite):
            zeichen = spielfeld.zeichen(x, y)
            rechteck = (x * feldbreite, y * feldhoehe, feldbreite, feldhoehe)
            if zeichen == '.':
                pygame.draw.rect(screen, (0, 0, 0), rechteck)
            elif zeichen == 'N':
                pygame.draw.rect(screen, (128, 0, 0), rechteck)
            elif zeichen == 'W':
                pygame.draw.rect(screen, (160, 43, 43), rechteck)
            elif zeichen == 'S':
                pygame.draw.rect(screen, (128, 128, 0), rechteck)
            elif zeichen == 'O':
                pygame.draw.rect(screen, (213, 85, 0), rechteck)
    # Spielfigur zeichnen
    pos = (int (1.0 * spielfeld.position[0] / spielfeld.feldgroesse * feldbreite), int(1.0 * spielfeld.position[1] / spielfeld.feldgroesse * feldhoehe))
    pygame.draw.circle(screen, (255, 255, 255), pos, 10)
    # grid zeichnen
    for x in xrange(1, spielfeld.breite):
        start = (x * feldbreite, 0)
        ende = (start[0], hoehe - 1)
        pygame.draw.line(screen, (128, 128, 128), start, ende)
    for y in xrange(1, spielfeld.hoehe):
        start = (0, y * feldhoehe)
        ende = (breite - 1, start[1])
        pygame.draw.line(screen, (128, 128, 128), start, ende)
    # Blickrichtung zeichnen
    qx = int(pos[0] + 2 * feldbreite * math.cos(math.radians(spielfeld.blickrichtung)))
    qy = int(pos[1] - 2 * feldhoehe * math.sin(math.radians(spielfeld.blickrichtung)))
    pygame.draw.line(screen, (0, 255, 0), pos, (qx, qy))
    # gefundene Wand zeichnen
    wx, wy, zeichen, sx, sy, entfernung = wandpos
    wx = wx * feldbreite + feldbreite // 2
    wy = wy * feldhoehe + feldhoehe // 2
    spos = (int (1.0 * sx / spielfeld.feldgroesse * feldbreite), int(1.0 * sy / spielfeld.feldgroesse * feldhoehe))
    pygame.draw.line(screen, (0, 0, 255), pos, spos)
    pygame.draw.circle(screen, (0, 0, 255), spos, 5)
    pygame.draw.circle(screen, (0, 0, 255), (wx, wy), min(feldhoehe, feldbreite) // 5)
    pygame.display.update()
 
 
def hauptschleife(spielfeld, screen, breite, hoehe):
    wand = spielfeld.findeWand(spielfeld.blickrichtung)
    zeichneSpielfeld(screen, breite, hoehe, spielfeld, wand)
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_UP:
                    spielfeld.geheVor()
                elif event.key == pygame.K_DOWN:
                    spielfeld.geheZurueck()
                elif event.key == pygame.K_LEFT:
                    spielfeld.dreheLinks()
                elif event.key == pygame.K_RIGHT:
                    spielfeld.dreheRechts()
                wand = spielfeld.findeWand(spielfeld.blickrichtung)
                zeichneSpielfeld(screen, breite, hoehe, spielfeld, wand)
 
 
if __name__ == '__main__':
    spielfeld = Spielfeld('spielfeld.txt', 64, 10)
    screen = init(500, 500)
    hauptschleife(spielfeld, screen, 500, 500)


Wandsuche Bearbeiten

Die auffälligste Neuerung an diesem Code ist Spielfeld.findeWand(). Wir bedienen uns hier kleinen Tricks, um die Wandsuche im Vergleich zum letzten Kapitel qualitativ zu verbessern.

Wandsuche horizontal/vertikal Bearbeiten

 
Suche nach der Wand bei 0°
 
Suche nach der Wand bei 180°

Für die Spezialfälle, in denen der aktuell zu untersuchende Winkel ein vielfaches von 90° ist, wird Spielfeld.findeWandHV() aufgerufen. Die Parameter sind die tatsächliche Pixelposition und die aktuelle Feldposition, auf der sich die Spielfigur befindet. In Abhängigkeit vom Winkel wird im Dictionary rDict eine Richtung vorgegeben, in der die zu suchende Wand liegt. Bei einem Winkel von 0° beispielsweise wird in X-Richtung gesucht und die Y-Richtung beibehalten. Die Notation ist dann (1, 0). Für 180° müssen wir allerdings nach links blicken und deswegen jeweils -1 in X-Richtung suchen, die Y-Richtung bleibt erhalten, also (-1, 0). So kommen die Einträge innerhalb dieses Dictionaries zustande. In der Schleife schauen wir uns also immer das nächste Feld an und testen darauf, ob es ein Wandzeichen ist. Ist es so, dann haben wir die Wand gefunden.
Im Fall von 0° ist die X-Position, an der das Wandelement gefunden wurde, einfach feldX * feldgroesse. Im Fall von 180° jedoch finden wir beispielsweise die Wand an der X-Position 0. Der Abstand zu dieser Wand ist dann allerdings zu korrigieren, da wir die Wand ja nicht an der linken, sondern an der rechten Seite schneiden. Finden wir also eine Wand in der Spalte 0, so müssen wir feldgroesse hinzuzählen. Allgemein also (feldX + 1) * self.feldgroesse.

Wandsuche Diagonal Bearbeiten

Alle anderen Fälle werden von Spielfeld.findeWandDiagonal(), verarbeitet, die ihrerseits sehr viel Vorbereitung bedarf. Wir übergeben der Methode Spielfeld.findeWandDiagonal() die augenblickliche Pixel-Position, den Abstand, wo diese Methode zuerst suchen soll, also die erste Schrittweite (dx1, dy1), den Abstand, wo danach gesucht werden soll (dx2, dy2) und einen Korrekturwert, auf den wir gleich noch zu sprechen kommen.

Innerhalb von Spielfeld.findeWandDiagonal() wird zuerst die erste Schrittweite (dx1, dy1) gegangen, dann wird geschaut, ob dort eine Wand liegt. Korrekturwerte werden jeweils hinzugezählt. Falls an dieser Stelle die zu untersuchende Wand außerhalb des Spielfeldes liegt, wird einfach angenommen, die Wand ist sehr weit entfernt und ein entsprechender Wert (Spielfeld.WEITWEG) zurückgeliefert. Dann wird unter berücksichtigung der Korrekturwerte das zweite Paar Schrittweiten ((dx1, dy1)) genommen, um Felder zu untersuchen.

Die Vorbereitung von Spielfeld.findeWandDiagonal() innerhalb von Spielfeld.findeWand() nimmt sehr viel Raum ein. Wir rufen Spielfeld.findeWandDiagonal() auch zweimal auf, einmal für die Schnittpunkte mit horizontalen und einmal für die Schnittpunkte mit vertikalen Linien. Dies kennen sie schon aus dem letzten Kapitel. Neu ist, dass wir mit quadrant das Spielfeld von der Spielfigur aus in vier Teile teilen. Der Quadrant 0 enthält alle Winkel, die größer als 0° und kleiner als 90° sind.

Die erste Schrittweite ist bestimmt durch den Abstand zum nächsten Feld. Es wird also der erste mögliche Schnittpunkt mit einer waagerechten und senkrechten Feldbegrenzung (im Anwendungsbild eine Gitterline) gesucht. Die nächsten Schrittweiten werden bestimmt durch die Feldgröße, da Feldbegrenzungen immer Feldgrößen voneinander entfernt sind. dy1 = py % self.feldgroesse hat hier die Bedeutung, die wir im mathematischen Kapitel erläutert haben, nämlich den Abstand der Spielfigur vom oberen Rand zu bestimmen.

Der Tangens ist im Quadrant 0 positiv, die Y-Richtung soll aber negativ sein - wir erinnern uns, dass der Wert (0 ; 0) in der oberen linken Ecke liegt, man also nach oben in den negativen Bereich schaut. Folglich müssen wir alle gefundenen Werte für dy1 und dy2 mit -1.0 multiplizieren. Dafür dient die etwas längliche aber immer gleiche Rechnung, die rx und ry enthält.

Die Korrekturwerte im rdict kommen daher zustande, dass man manchmal das falsche Feld untersucht. Sucht man beispielsweise im Quadrant 0 einen Schnittpunkt mit horizontalen Linien, so rechnet man immer ein Feld zu tief. Die Korrektur von (0, -1) für diesen Fall berichtigt das. Da meistens alles gut läuft, man also nur wenige solcher quadrantabhängigen Korrekturen braucht, ist rdict zumeist mit Nullen besetzt. Übrigens könnte man auch die oben genannte rx- und ry-Werte dort mit eintragen.

Spielfeld zeichnen Bearbeiten

Das Spielfeld wird mit der Funktion zeichneSpielfeld() neu gezeichnet. Diese Funktion wird mit der echten Fenstergröße und einigen weiteren Parametern aufgerufen. Da diese echte Fenstergröße nicht unbedingt genau so groß sein muss, wie das Spielfeld, benutzen wir die aus dem mathematischen Kapitel bekannte Methode, um das Spielfeld maßstabsgetreu zu verkleinern. Wenn also die echte Spielfeldbreite in Feldern gemessen spielfeld.breite ist und die echte Feldbreite in Pixeln gemessen spielfeld.feldgroesse ist, dann ist das gesamte echte Spielfeld in Pixeln gemessen gerade spielfeld.breite * spielfeld.feldgroesse breit. Diese Breite muss aber nun in das Programmfenster mit der Breite breite gequetscht werden. Hierbei hilft uns die Variable feldbreite, die die Größe eines einzelnen Feldes auf dem Programmfenster berechnet.

Hat man dann eine Pixel-Position auf dem Spielfeld, kann man mit spielfeld.position[0] / spielfeld.feldgroesse * feldbreite die X-Position im Programmfenster bestimmen. Der Quotient feldbreite / spielfeld.feldgroesse, der in all diesen Berechnungen steckt, ist dann genau das Maß, um das wir alle X-Positionsangaben verkleinern müssen, um eine maßstabsgetreue Positionierung auf dem Fenster zu bekommen.

Freies Gehen im Raum Bearbeiten

Das nun folgende Beispiel benutzt die jetzt bekannte Spielfeldklasse, um eine dreidimensionale Raumdarstellung zu erzeugen.

Anmerkung:

Die Klasse Spielfeld ist hierbei die gleiche wie oben schon beschrieben, wir haben sie weggelassen, um den Quelltext nicht unnötig lang werden zu lassen. Probieren Sie dieses Beispiel aus, dann kopieren Sie sich bitte die oben angeführte Klasse einfach in den Quelltext.

Im Vergleich zu obigem Programm fällt auf, dass wir lediglich die Raumklasse hinzugefügt haben und die Funktion zeichneSpielfeld() deutlich kleiner ausfällt:

#!/usr/bin/python
# -*- coding: utf-8 -*-
 
import math
import pygame
import sys
 
 
class Spielfeld(object):
    # Aus Gründen der Übersichtlichkeit weggelassen, Wie oben
    pass

class Raum(object):

    def __init__(self, blickfeldwinkel, pBreite, pHoehe, wandhoehe):
        """Initialisiert den Raum
          blickfeldwinkel - gesamter Winkelbereich, den Spielfigur sehen kann
          pBreite, pHoehe - Ausmaße des Fensters, auf das projiziert werden soll"""
        self.blickfeldWinkel = blickfeldwinkel
        self.blickfeldWinkelD2 = blickfeldwinkel / 2.0
        self.pHoehe = pHoehe
        self.mittelLinie = self.pHoehe // 2
        self.spaltenZahl = pBreite
        self.projektionsAbstand = (self.spaltenZahl / 2.0) /  math.tan(math.radians(self.blickfeldWinkelD2))
        self.spaltenWinkel = 1.0 * blickfeldwinkel / self.spaltenZahl
        self.wandHoeheEcht = 1.0 * wandhoehe


    def berechneHoehe(self, entfernung):
        """berechnet aus der Entfernung die scheinbare Wandhöhe.
          Da Entfernung auch 0 sein kann, erfolgt hier Anpassung"""
        if entfernung > 0.0:
            h = self.wandHoeheEcht * self.projektionsAbstand / entfernung
        else:
            h = self.pHoehe
        return int(h)


    def raycasting(self, spielfeld):
        anfangsWinkel = spielfeld.blickrichtung + self.blickfeldWinkelD2
        hoehenListe = []
        for spalte in xrange(self.spaltenZahl):
            winkel = anfangsWinkel - spalte * self.spaltenWinkel
            winkel = winkel % 360
            feldXpos, feldYpos, feldZeichen, schnittpunktX, schnittpunktY, entfernung = spielfeld.findeWand(winkel)
            hoehe = self.berechneHoehe(entfernung)
            hoehenListe.append((spalte, feldZeichen, hoehe))
        return hoehenListe


 
def init(breite, hoehe):
    screen = pygame.display.set_mode((breite, hoehe))
    screen.fill((200, 200, 200))
    pygame.display.update()
    return screen


def zeichneSpielfeld(screen, mittellinie, hoehen):
    screen.fill((200, 200, 200))
    for spalte, zeichen, hoehe in hoehen:
        x0, x1 = spalte, spalte
        # Zeichne in der Mitte 
        y0 = mittellinie - hoehe // 2
        y1 = y0 + hoehe
        if zeichen == 'N':
            farbe = (128, 0, 0)
        elif zeichen == 'S':
            farbe = (128, 128, 0)
        elif zeichen == 'O':
            farbe = (213, 85, 0)
        elif zeichen == 'W':
            farbe = (160, 43, 43)
        pygame.draw.line(screen, farbe, (x0, y0), (x1, y1), 1)
    pygame.display.update()



def hauptschleife(raum, spielfeld, screen, breite, hoehe):
    mussZeichnen = False
    hoehen = raum.raycasting(spielfeld)
    zeichneSpielfeld(screen, raum.mittelLinie, hoehen)
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                mussZeichnen = True
                if event.key == pygame.K_UP:
                    spielfeld.geheVor()
                elif event.key == pygame.K_DOWN:
                    spielfeld.geheZurueck()
                elif event.key == pygame.K_LEFT:
                    spielfeld.dreheLinks()
                elif event.key == pygame.K_RIGHT:
                    spielfeld.dreheRechts()
            if mussZeichnen:
                hoehen = raum.raycasting(spielfeld)
                zeichneSpielfeld(screen, raum.mittelLinie, hoehen)
                mussZeichnen = False


if __name__ == '__main__':
    spielfeld = Spielfeld('spielfeld.txt', 64, 10)
    screen = init(500, 500)
    raum = Raum(60, 500, 500, 64)
    hauptschleife(raum, spielfeld, screen, 500, 500)


Die Methode berechneHoehe() berechnet wieder die Höhe mit Hilfe des Strahlensatzes, allerdings haben wir es hier mit Pixel-Entferungen zu tun, und müssen deswegen wandHoeheEcht miteinbeziehen. Den Fall, dass der Abstand 0 wird, berücksichtigen wir in der Methode ebenfalls.

Die Modulo-Operation beim Winkel haben wir statt in spielfeld.findeWand() nun direkt in der Methode raycasting() vorgenommen. In den späteren Beispielen werden wir sie dort gebrauchen.

Ansonsten ist die Ausgabe wie im Beispielprogramm im Kapitel "Darstellung des Raumes", allerdings kommen Sie den Wänden wesentlich näher.

Wand mit Textur Bearbeiten

 
Die erste Wand bekommt eine Tapete

Nachdem wir uns nun frei auf dem Spielfeld bewegen können, werden wir uns dem Thema Texturen widmen. Auf der nebenstehenden Abbildung sehen Sie schon, worum es geht: Die Nordwand wird mit einer Textur versehen. Hierzu benötigen wir eine Grafikdatei mit dem Namen wand2N.png, diese sollte so groß sein wie die Feldgröße, also 64 Pixel in diesen Beispielprogrammen.

Das Beispielprogramm enthält einige neue Funktionen, nämlich ladeBild() und bildspalteZoom(). Den Kern dieser Funktionen haben wir Ihnen im Kapitel Grundlagen der Programmierung vorgestellt.

#!/usr/bin/python
# -*- coding: utf-8 -*-
 
import math
import pygame
import sys
 
 
class Spielfeld(object):
    # Wie im ersten Beispielprogramm
    pass


class Raum(object):

    def __init__(self, blickfeldwinkel, pBreite, pHoehe, wandhoehe):
        """Initialisiert den Raum
          blickfeldwinkel - gesamter Winkelbereich, den Spielfigur sehen kann
          pBreite, pHoehe - Ausmaße des Fensters, auf das projiziert werden soll"""
        self.blickfeldWinkel = blickfeldwinkel
        self.blickfeldWinkelD2 = blickfeldwinkel / 2.0
        self.pHoehe = pHoehe
        self.mittelLinie = self.pHoehe // 2
        self.spaltenZahl = pBreite
        self.projektionsAbstand = (self.spaltenZahl / 2.0) /  math.tan(math.radians(self.blickfeldWinkelD2))
        self.spaltenWinkel = 1.0 * blickfeldwinkel / self.spaltenZahl
        self.wandHoeheEcht = 1.0 * wandhoehe


    def berechneHoehe(self, entfernung):
        """berechnet aus der Entfernung die scheinbare Wandhöhe.
          Da Entfernung auch 0 sein kann, erfolgt hier Anpassung"""
        if entfernung > 0.0:
            h = self.wandHoeheEcht * self.projektionsAbstand / entfernung
        else:
            h = self.pHoehe
        return int(h)


    def raycasting(self, spielfeld):
        anfangsWinkel = spielfeld.blickrichtung + self.blickfeldWinkelD2
        hoehenListe = []
        for spalte in xrange(self.spaltenZahl):
            winkel = anfangsWinkel - spalte * self.spaltenWinkel
            winkel = winkel % 360
            feldXpos, feldYpos, feldZeichen, schnittpunktX, schnittpunktY, entfernung = spielfeld.findeWand(winkel)
            # Abstand, an dem der momentane Winkel eine Wand trifft vom linken Rand der Wand
            wandort = int( (schnittpunktX +  schnittpunktY) % spielfeld.feldgroesse )
            hoehe = self.berechneHoehe(entfernung)
            hoehenListe.append((spalte, feldZeichen, hoehe, wandort))
        return hoehenListe


def init(breite, hoehe):
    screen = pygame.display.set_mode((breite, hoehe))
    screen.fill((200, 200, 200))
    pygame.display.update()
    return screen


def ladeBild(dateiname):
    tmp = pygame.image.load(dateiname)
    surface = tmp.convert()
    return surface    


def bildspalteZoom(spalte, bild, ziel, (x, y), neueHoehe):
    assert(spalte >= 0 and spalte < bild.get_width() and neueHoehe > 0)
    height = bild.get_height()
    einspaltig = pygame.Surface((1, height), 0, bild)
    einspaltig.blit(bild, (0, 0), (spalte, 0, 1, height))
    gestretcht = pygame.transform.scale(einspaltig, (1, neueHoehe))
    ziel.blit(gestretcht, (x, y), (0, 0, 1, neueHoehe))
    

def zeichneSpielfeld(screen, mittellinie, hoehen, bild):
    screen.fill((200, 200, 200))
    for spalte, zeichen, hoehe, abstand in hoehen:
        x0, x1 = spalte, spalte
        # Zeichne in der Mitte 
        y0 = mittellinie - hoehe // 2
        y1 = y0 + hoehe
        if zeichen == 'N':
            bildspalteZoom(abstand, bild, screen, (x0, y0), hoehe)
        elif zeichen == 'S':
            farbe = (128, 128, 0)
            pygame.draw.line(screen, farbe, (x0, y0), (x1, y1), 1)
        elif zeichen == 'O':
            farbe = (213, 85, 0)
            pygame.draw.line(screen, farbe, (x0, y0), (x1, y1), 1)
        elif zeichen == 'W':
            farbe = (160, 43, 43)
            pygame.draw.line(screen, farbe, (x0, y0), (x1, y1), 1)
    pygame.display.update()



def hauptschleife(raum, spielfeld, screen, breite, hoehe):
    mussZeichnen = False
    wandbild = ladeBild('wand2N.png')
    hoehen = raum.raycasting(spielfeld)
    zeichneSpielfeld(screen, raum.mittelLinie, hoehen, wandbild)
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                mussZeichnen = True
                if event.key == pygame.K_UP:
                    spielfeld.geheVor()
                elif event.key == pygame.K_DOWN:
                    spielfeld.geheZurueck()
                elif event.key == pygame.K_LEFT:
                    spielfeld.dreheLinks()
                elif event.key == pygame.K_RIGHT:
                    spielfeld.dreheRechts()
            if mussZeichnen:
                hoehen = raum.raycasting(spielfeld)
                zeichneSpielfeld(screen, raum.mittelLinie, hoehen, wandbild)
                mussZeichnen = False


if __name__ == '__main__':
    spielfeld = Spielfeld('spielfeld.txt', 64, 10)
    screen = init(500, 500)
    raum = Raum(60, 500, 500, 64)
    hauptschleife(raum, spielfeld, screen, 500, 500)

Korrekte Spalte Bearbeiten

Hat man in der Methode raycasting() den Schnittpunkt eines Strahles mit einer Wand bestimmt (schnittpunktX, schnittpunktY), dann ist ein Teil dieses Schnittpunktes genau auf einer Linie zwischen zwei Feldern. An dieser Stelle ist dann entweder schnittpunktX oder schnittpunktY ein Vielfaches von der Feldbreite. Da man gar nicht so genau wissen möchte, an welcher Seite einer Mauer man schneidet, sondern nur, wie groß der Abstand zur Wand ist, reicht (schnittpunktX + schnittpunktY) % spielfeld.feldgroesse aus, um diesen Abstand zu bestimmen. Man weiss dann also den Abstand des Schnittpunktes zu einer Seite der Mauer. Diesen Abstand nennen wir wandort, er ist gleich der Spalte in unserer Textur.

Innerhalb von zeichneSpielfeld() benutzen wir diesen Wandort, um bei Nordwänden mit bildspalteZoom() die passende Spalte aus der Textur auszuwählen und auf die richtige Größe zu zoomen.

Probleme Bearbeiten

Leider ist das beschriebene Verfahren, um den Wandort zu bestimmen, nicht gut genug, was man an folgenden Bildern sieht. Hier wird eine Situation auf dem Spielfeld wie auch im Raum gezeigt, bei der die falschen Spalten aus der Textur ausgewählt werden.

Ansicht Spielfeld Ansicht 3D
 
Spieler blickt nach rechts-unten...
 
...und sieht die Textur falsch

Blickt man nämlich nach unten auf eine Textur, wird der falsche Wandort bestimmt. Wir müssen die Texturspalte beim Blick nach unten von rechts auswählen, die oben beschriebene Modulo-Operation sorgt allerdings dafür, dass die Texturspalten von links ausgewählt werden. Wenn Sie einmal mit dem Programm durch den Raum wandern, werden Sie weitere Stellen sehen können, bei denen das nicht passt.

Um dieses Problem kümmern wir uns im nächsten Abschnitt und zeigen darüberhinaus, wie man alle Wände mit Textur belegt.

Alle Wände mit Textur Bearbeiten

 
Alle Texturen werden richtig dargestellt

Das folgende Beispielprogramm ist eine löst das oben beschriebene Problem innerhalb von Raum.raycasting(), in dem alle Fälle, bei denen die Wand falsch gezeichnet würde abfängt und die Spalten korrekt von der anderen Seite aus bestimmt:

wandort = int( (schnittpunktX +  schnittpunktY) % spielfeld.feldgroesse )
if (270 > winkel > 90 and schnittpunktX % spielfeld.feldgroesse == 0) or \
 (360 > winkel > 180 and schnittpunktY % spielfeld.feldgroesse == 0):
    wandort = spielfeld.feldgroesse - wandort - 1

Es wird also bei Winkeln zwischen 90° und 270° geschaut, ob man einen Schnittpunkt mit vertikalen Feldbegrenzungen hat und bei Winkeln zwischen 180° und 360°, ob man hier einen Schnittpunkt mit horizontalen Feldbegrenzungen vorfindet. In diesen Fällen muss von der anderen Seite der Wand die Texturspalte bestimmt werden.

Ebenfalls zeigen wir eine Möglichkeit, wie man mit mehreren Texturen umgeht. Hierzu benötigen wir Texturdateien wand2N.png, wand2S.png, wand2O.png und wand2W.png, die wir in der Hauptschleife laden. Diese Bilddateien müssen wieder so groß sein wie die Feldgröße, in unseren Beispielen 64 Pixel.

#!/usr/bin/python
# -*- coding: utf-8 -*-
 
import math
import pygame
import sys
 
 
class Spielfeld(object):
 
    def __init__(self, dateiname, feldgroesse, weglaenge):
        """ Initialisiert das Spielfeld
          dateiname - Dateiname der Spielfeld-Datei
          feldgroesse - die Pixelgrösse eines quadratischen Feldes
          weglaenge - dieses Stück Pixelweg legt der Spieler bei einem Tastendruck zurück"""
        self.felder = []
        self.feldgroesse = feldgroesse
        self.feldgroesseD2 = feldgroesse // 2
        self.weglaenge = weglaenge
        self.blickrichtung = 160
        datei = open(dateiname, 'r')
        for zeile in datei:
            self.felder.append(zeile[:-1])
        datei.close()
        self.breite = len(self.felder[0])
        self.hoehe = len(self.felder)
        for num, zeile in enumerate(self.felder):
            idx = zeile.find('#')
            if idx >= 0:
                # Pixel-Position!!!
                self.position = (idx * self.feldgroesse + self.feldgroesseD2, num * self.feldgroesse + self.feldgroesseD2)
                self.felder[num] = self.felder[num].replace('#', '.')
                break
        self.WEITWEG = self.breite * self.hoehe * self.feldgroesse

 
    def zeichen(self, x, y):
        """Liefert das Zeichen an der Position x, y """
        assert(0 <= x < self.breite)
        assert(0 <= y < self.hoehe)
        return self.felder[y][x]
 
 
    def istWand(self, zeichen):
        """True, wenn das Zeichen ein Wandzeichen ist """
        return zeichen in ('N', 'W', 'S', 'O')
 
 
    def geheVor(self):
        """gehe in Richtung der Blickrichtung """
        px, py = self.position
        qx = int(round(px + self.weglaenge * math.cos(math.radians(self.blickrichtung))))
        qy = int(round(py - self.weglaenge * math.sin(math.radians(self.blickrichtung))))
        zeichen = self.zeichen(qx // self.feldgroesse, qy // self.feldgroesse)
        if not self.istWand(zeichen): 
            self.position = (qx, qy)  
 
 
    def geheZurueck(self):
        """gehe entgegen der Blickrichtung """
        self.blickrichtung += 180
        self.geheVor()
        self.blickrichtung -= 180
 
 
    def dreheLinks(self):
        self.blickrichtung = (self.blickrichtung + 5) % 360
 
 
    def dreheRechts(self):
        self.blickrichtung = (self.blickrichtung - 5) % 360


    def findeWandHV(self, winkel, px, py, fx, fy):
        """findet Wände in ausschließlich horizontaler/vertikaler Richtung"""
        rDict = {0 : (1, 0), 90 : (0, -1), 180 : (-1, 0), 270 : (0, 1)}
        rx, ry = rDict[winkel]
        feldX = fx + rx
        feldY = fy + ry
        feldZeichen = self.zeichen(feldX, feldY)
        while not self.istWand(feldZeichen):
            feldX = feldX + rx
            feldY = feldY + ry
            feldZeichen = self.zeichen(feldX, feldY)
        if winkel == 0:
            sx = feldX * self.feldgroesse 
            sy = py
            entfernung = sx - px
        elif winkel == 90:
            sx = px
            sy = (feldY + 1) * self.feldgroesse
            entfernung = py - sy
        elif winkel == 180:
            sx = (feldX + 1) * self.feldgroesse 
            sy = py
            entfernung = px - sx
        else: # winkel == 270
            sx = px
            sy = feldY * self.feldgroesse
            entfernung = sy - py
        return (feldX, feldY, feldZeichen, sx, sy, entfernung)


    def findeWandDiagonal(self, px, py, dx1, dy1, dx2, dy2, (kx, ky)):
        # Findet Wände in diagonaler Richtung
        sx = px + dx1
        sy = py + dy1
        feldX = int(sx / self.feldgroesse) + kx
        feldY = int(sy / self.feldgroesse) + ky
        if (0 <= feldX < self.breite) and (0 <= feldY < self.hoehe):
            feldZeichen = self.zeichen(feldX, feldY)
        else:
            return (0, 0, '0', 0, 0, self.WEITWEG)
        while not self.istWand(feldZeichen):
            sx = sx + dx2
            sy = sy + dy2
            feldX = int(sx / self.feldgroesse) + kx
            feldY = int(sy / self.feldgroesse) + ky
            if (0 <= feldX < self.breite) and (0 <= feldY < self.hoehe):
                feldZeichen = self.zeichen(feldX, feldY)
            else:
                return (0, 0, '0', 0, 0, self.WEITWEG)
        # wir haben die Wand gefunden
        ex = sx - px
        ey = sy - py
        entfernung = math.sqrt(ex * ex + ey * ey)
        return (feldX, feldY, feldZeichen, sx, sy, entfernung)


    def findeWand(self, winkel):
        px, py = self.position
        fx = px // self.feldgroesse
        fy = py // self.feldgroesse 
        # Spezialfall wegen Tangens
        if winkel % 90 == 0:
            return self.findeWandHV(winkel, px, py, fx, fy)
        else: # alle anderen Winkel
            rdict = {0 : ((0, -1), (0, 0)), 
                     1 : ((0, -1), (-1, 0)), 
                     2 : ((0, 0), (-1, 0)), 
                     3 : ((0, 0), (0, 0))}
            quadrant = int(winkel) // 90
            tangens = math.tan(math.radians(winkel))
            korr1, korr2 = rdict[quadrant]

            if quadrant == 0:
                # - Schnittpunkte mit horiz. Linien
                dy1 = py % self.feldgroesse
                dx1 = dy1 / tangens
                dy2 = self.feldgroesse
                dx2 = dy2 / tangens
                rx = 1.0
                ry = -1.0
                dx1 = rx * dx1
                dy1 = ry * dy1
                dx2 = rx * dx2
                dy2 = ry * dy2
                wand1 = self.findeWandDiagonal(px, py, dx1, dy1, dx2, dy2, korr1)

                # - Schnittpunkte mit vert. Linien
                dx1 = self.feldgroesse - px % self.feldgroesse
                dy1 = tangens * dx1
                dx2 = self.feldgroesse
                dy2 = tangens * dx2
                rx = 1.0
                ry = -1.0
                dx1 = rx * dx1
                dy1 = ry * dy1
                dx2 = rx * dx2
                dy2 = ry * dy2
                wand2 = self.findeWandDiagonal(px, py, dx1, dy1, dx2, dy2, korr2)
            elif quadrant == 1:
                # - Schnittpunkte mit horiz. Linien
                dy1 = py % self.feldgroesse
                dx1 = dy1 / tangens
                dy2 = self.feldgroesse
                dx2 = dy2 / tangens
                rx = 1.0
                ry = -1.0
                dx1 = rx * dx1
                dy1 = ry * dy1
                dx2 = rx * dx2
                dy2 = ry * dy2
                wand1 = self.findeWandDiagonal(px, py, dx1, dy1, dx2, dy2, korr1)

                # - Schnittpunkte mit vert. Linien
                dx1 = px % self.feldgroesse
                dy1 = tangens * dx1
                dx2 = self.feldgroesse
                dy2 = tangens * dx2
                rx = -1.0
                ry = 1.0
                dx1 = rx * dx1
                dy1 = ry * dy1
                dx2 = rx * dx2
                dy2 = ry * dy2
                wand2 = self.findeWandDiagonal(px, py, dx1, dy1, dx2, dy2, korr2)
            elif quadrant == 2:
                # - Schnittpunkte mit horiz. Linien
                dy1 = self.feldgroesse - py % self.feldgroesse
                dx1 = dy1 / tangens
                dy2 = self.feldgroesse
                dx2 = dy2 / tangens
                rx = -1.0
                ry = 1.0
                dx1 = rx * dx1
                dy1 = ry * dy1
                dx2 = rx * dx2
                dy2 = ry * dy2
                wand1 = self.findeWandDiagonal(px, py, dx1, dy1, dx2, dy2, korr1)

                # - Schnittpunkte mit vert. Linien
                dx1 = px % self.feldgroesse
                dy1 = tangens * dx1
                dx2 = self.feldgroesse
                dy2 = tangens * dx2
                rx = -1.0
                ry = 1.0
                dx1 = rx * dx1
                dy1 = ry * dy1
                dx2 = rx * dx2
                dy2 = ry * dy2
                wand2 = self.findeWandDiagonal(px, py, dx1, dy1, dx2, dy2, korr2)
            elif quadrant == 3:
                # - Schnittpunkte mit horiz. Linien
                dy1 = self.feldgroesse - py % self.feldgroesse
                dx1 = dy1 / tangens
                dy2 = self.feldgroesse
                dx2 = dy2 / tangens
                rx = -1.0
                ry = 1.0
                dx1 = rx * dx1
                dy1 = ry * dy1
                dx2 = rx * dx2
                dy2 = ry * dy2
                wand1 = self.findeWandDiagonal(px, py, dx1, dy1, dx2, dy2, korr1)

                # - Schnittpunkte mit vert. Linien
                dx1 = self.feldgroesse - px % self.feldgroesse
                dy1 = tangens * dx1
                dx2 = self.feldgroesse
                dy2 = tangens * dx2
                rx = 1.0
                ry = -1.0
                dx1 = rx * dx1
                dy1 = ry * dy1
                dx2 = rx * dx2
                dy2 = ry * dy2
                wand2 = self.findeWandDiagonal(px, py, dx1, dy1, dx2, dy2, korr2)
            if wand1[5] < wand2[5]:
                return wand1
            else:
                return wand2
        


class Raum(object):

    def __init__(self, blickfeldwinkel, pBreite, pHoehe, wandhoehe):
        """Initialisiert den Raum
          blickfeldwinkel - gesamter Winkelbereich, den Spielfigur sehen kann
          pBreite, pHoehe - Ausmaße des Fensters, auf das projiziert werden soll"""
        self.blickfeldWinkel = blickfeldwinkel
        self.blickfeldWinkelD2 = blickfeldwinkel / 2.0
        self.pHoehe = pHoehe
        self.mittelLinie = self.pHoehe // 2
        self.spaltenZahl = pBreite
        self.projektionsAbstand = (self.spaltenZahl / 2.0) /  math.tan(math.radians(self.blickfeldWinkelD2))
        self.spaltenWinkel = 1.0 * blickfeldwinkel / self.spaltenZahl
        self.wandHoeheEcht = 1.0 * wandhoehe


    def berechneHoehe(self, entfernung):
        """berechnet aus der Entfernung die scheinbare Wandhöhe.
          Da Entfernung auch 0 sein kann, erfolgt hier Anpassung"""
        if entfernung > 0.0:
            h = self.wandHoeheEcht * self.projektionsAbstand / entfernung
        else:
            h = self.pHoehe
        return int(h)


    def raycasting(self, spielfeld):
        anfangsWinkel = spielfeld.blickrichtung + self.blickfeldWinkelD2
        hoehenListe = []
        for spalte in xrange(self.spaltenZahl):
            winkel = anfangsWinkel - spalte * self.spaltenWinkel
            winkel = winkel % 360
            feldXpos, feldYpos, feldZeichen, schnittpunktX, schnittpunktY, entfernung = spielfeld.findeWand(winkel)
            # Abstand, an dem der momentane Winkel eine Wand trifft vom linken Rand der Wand
            wandort = int( (schnittpunktX +  schnittpunktY) % spielfeld.feldgroesse )
            if (270 > winkel > 90 and schnittpunktX % spielfeld.feldgroesse == 0) or \
             (360 > winkel > 180 and schnittpunktY % spielfeld.feldgroesse == 0):
                wandort = spielfeld.feldgroesse - wandort - 1
            hoehe = self.berechneHoehe(entfernung)
            hoehenListe.append((spalte, feldZeichen, hoehe, wandort))
        return hoehenListe


def init(breite, hoehe):
    screen = pygame.display.set_mode((breite, hoehe))
    screen.fill((200, 200, 200))
    pygame.display.update()
    return screen


def ladeBild(dateiname):
    tmp = pygame.image.load(dateiname)
    surface = tmp.convert()
    return surface    


def bildspalteZoom(spalte, bild, ziel, (x, y), neueHoehe):
    assert(spalte >= 0 and spalte < bild.get_width() and neueHoehe > 0)
    height = bild.get_height()
    einspaltig = pygame.Surface((1, height), 0, bild)
    einspaltig.blit(bild, (0, 0), (spalte, 0, 1, height))
    gestretcht = pygame.transform.scale(einspaltig, (1, neueHoehe))
    ziel.blit(gestretcht, (x, y), (0, 0, 1, neueHoehe))
    

def zeichneSpielfeld(screen, mittellinie, hoehen, bildN, bildS, bildO, bildW):
    screen.fill((200, 200, 200))
    for spalte, zeichen, hoehe, abstand in hoehen:
        x0, x1 = spalte, spalte
        # Zeichne in der Mitte 
        y0 = mittellinie - hoehe // 2
        y1 = y0 + hoehe
        if zeichen == 'N':
            bildspalteZoom(abstand, bildN, screen, (x0, y0), hoehe)
        elif zeichen == 'S':
            bildspalteZoom(abstand, bildS, screen, (x0, y0), hoehe)
        elif zeichen == 'O':
            bildspalteZoom(abstand, bildO, screen, (x0, y0), hoehe)
        elif zeichen == 'W':
            bildspalteZoom(abstand, bildW, screen, (x0, y0), hoehe)
    pygame.display.update()



def hauptschleife(raum, spielfeld, screen, breite, hoehe):
    mussZeichnen = False
    wandbildN = ladeBild('wand2N.png')
    wandbildS = ladeBild('wand2S.png')
    wandbildO = ladeBild('wand2O.png')
    wandbildW = ladeBild('wand2W.png')
    hoehen = raum.raycasting(spielfeld)
    zeichneSpielfeld(screen, raum.mittelLinie, hoehen, wandbildN, wandbildS, wandbildO, wandbildW)
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                mussZeichnen = True
                if event.key == pygame.K_UP:
                    spielfeld.geheVor()
                elif event.key == pygame.K_DOWN:
                    spielfeld.geheZurueck()
                elif event.key == pygame.K_LEFT:
                    spielfeld.dreheLinks()
                elif event.key == pygame.K_RIGHT:
                    spielfeld.dreheRechts()
            if mussZeichnen:
                hoehen = raum.raycasting(spielfeld)
                zeichneSpielfeld(screen, raum.mittelLinie, hoehen, wandbildN, wandbildS, wandbildO, wandbildW)
                mussZeichnen = False


if __name__ == '__main__':
    spielfeld = Spielfeld('spielfeld.txt', 64, 10)
    screen = init(500, 500)
    raum = Raum(60, 500, 500, 64)
    hauptschleife(raum, spielfeld, screen, 500, 500)

Anregungen Bearbeiten