Matplotlib Spiel: Pong

PythonPythonBeginner
Jetzt üben

This tutorial is from open-source community. Access the source code

💡 Dieser Artikel wurde von AI-Assistenten übersetzt. Um die englische Version anzuzeigen, können Sie hier klicken

Einführung

In diesem Lab lernst du, wie du ein interaktives Pong-Spiel mit Matplotlib erstellst. Pong ist ein klassisches Arcade-Spiel, bei dem zwei Spieler ein Ball mit Schlägern hin und her über ein virtuelles Spielfeld schlagen. In diesem Lab wird dir der Prozess des Erstellens deines eigenen Pong-Spiels mit Python und Matplotlib erläutert.

Tipps für die VM

Nachdem der VM-Start abgeschlossen ist, klicke in der oberen linken Ecke, um zur Registerkarte Notebook zu wechseln und Jupyter Notebook für die Übung zu nutzen.

Manchmal musst du einige Sekunden warten, bis Jupyter Notebook vollständig geladen ist. Die Validierung von Vorgängen kann aufgrund der Einschränkungen in Jupyter Notebook nicht automatisiert werden.

Wenn du bei der Lernphase Probleme hast, kannst du Labby gerne fragen. Gib nach der Sitzung Feedback, und wir werden das Problem für dich prompt beheben.

Matplotlib installieren

Bevor wir beginnen, müssen wir sicherstellen, dass Matplotlib installiert ist. Du kannst Matplotlib mit pip installieren, indem du folgenden Befehl in deiner Konsole ausführst:

pip install matplotlib

Bibliotheken importieren

Das erste, was wir tun müssen, ist, die erforderlichen Bibliotheken zu importieren. Wir werden time, numpy und matplotlib verwenden.

import time
import matplotlib.pyplot as plt
import numpy as np

Definiere die Pad- und Puck-Klassen

Als nächstes müssen wir die Pad- und Puck-Klassen definieren. Die Pad-Klasse repräsentiert die Schläger, die von den Spielern verwendet werden, während die Puck-Klasse den Ball repräsentiert.

class Pad:
    def __init__(self, disp, x, y, type='l'):
        self.disp = disp
        self.x = x
        self.y = y
        self.w =.3
        self.score = 0
        self.xoffset = 0.3
        self.yoffset = 0.1
        if type == 'r':
            self.xoffset *= -1.0

        if type == 'l' or type == 'r':
            self.signx = -1.0
            self.signy = 1.0
        else:
            self.signx = 1.0
            self.signy = -1.0

    def contains(self, loc):
        return self.disp.get_bbox().contains(loc.x, loc.y)


class Puck:
    def __init__(self, disp, pad, field):
        self.vmax =.2
        self.disp = disp
        self.field = field
        self._reset(pad)

    def _reset(self, pad):
        self.x = pad.x + pad.xoffset
        if pad.y < 0:
            self.y = pad.y + pad.yoffset
        else:
            self.y = pad.y - pad.yoffset
        self.vx = pad.x - self.x
        self.vy = pad.y + pad.w/2 - self.y
        self._speedlimit()
        self._slower()
        self._slower()

    def update(self, pads):
        self.x += self.vx
        self.y += self.vy
        for pad in pads:
            if pad.contains(self):
                self.vx *= 1.2 * pad.signx
                self.vy *= 1.2 * pad.signy
        fudge =.001
        ## wahrscheinlich sauberer mit etwas wie...
        if self.x < fudge:
            pads[1].score += 1
            self._reset(pads[0])
            return True
        if self.x > 7 - fudge:
            pads[0].score += 1
            self._reset(pads[1])
            return True
        if self.y < -1 + fudge or self.y > 1 - fudge:
            self.vy *= -1.0
            ## füge etwas Zufälligkeit hinzu, um es interessanter zu machen
            self.vy -= (randn()/300.0 + 1/300.0) * np.sign(self.vy)
        self._speedlimit()
        return False

    def _slower(self):
        self.vx /= 5.0
        self.vy /= 5.0

    def _faster(self):
        self.vx *= 5.0
        self.vy *= 5.0

    def _speedlimit(self):
        if self.vx > self.vmax:
            self.vx = self.vmax
        if self.vx < -self.vmax:
            self.vx = -self.vmax

        if self.vy > self.vmax:
            self.vy = self.vmax
        if self.vy < -self.vmax:
            self.vy = -self.vmax

Definiere die Game-Klasse

Jetzt, nachdem wir die Pad- und Puck-Klassen definiert haben, können wir fortfahren und die Game-Klasse definieren. Diese Klasse wird für die Verwaltung der Spiellogik und das Zeichnen des Spiels auf dem Bildschirm verantwortlich sein.

class Game:
    def __init__(self, ax):
        ## erstelle die Anfangslinie
        self.ax = ax
        ax.xaxis.set_visible(False)
        ax.set_xlim([0, 7])
        ax.yaxis.set_visible(False)
        ax.set_ylim([-1, 1])
        pad_a_x = 0
        pad_b_x =.50
        pad_a_y = pad_b_y =.30
        pad_b_x += 6.3

        ## Schläger
        pA, = self.ax.barh(pad_a_y,.2,
                           height=.3, color='k', alpha=.5, edgecolor='b',
                           lw=2, label="Spieler B",
                           animated=True)
        pB, = self.ax.barh(pad_b_y,.2,
                           height=.3, left=pad_b_x, color='k', alpha=.5,
                           edgecolor='r', lw=2, label="Spieler A",
                           animated=True)

        ## Ablenkungen
        self.x = np.arange(0, 2.22*np.pi, 0.01)
        self.line, = self.ax.plot(self.x, np.sin(self.x), "r",
                                  animated=True, lw=4)
        self.line2, = self.ax.plot(self.x, np.cos(self.x), "g",
                                   animated=True, lw=4)
        self.line3, = self.ax.plot(self.x, np.cos(self.x), "g",
                                   animated=True, lw=4)
        self.line4, = self.ax.plot(self.x, np.cos(self.x), "r",
                                   animated=True, lw=4)

        ## Mittelinie
        self.centerline, = self.ax.plot([3.5, 3.5], [1, -1], 'k',
                                        alpha=.5, animated=True, lw=8)

        ## Puck (s)
        self.puckdisp = self.ax.scatter([1], [1], label='_nolegend_',
                                        s=200, c='g',
                                        alpha=.9, animated=True)

        self.canvas = self.ax.figure.canvas
        self.background = None
        self.cnt = 0
        self.distract = True
        self.res = 100.0
        self.on = False
        self.inst = True    ## zeige die Anweisungen von Anfang an
        self.pads = [Pad(pA, pad_a_x, pad_a_y),
                     Pad(pB, pad_b_x, pad_b_y, 'r')]
        self.pucks = []
        self.i = self.ax.annotate(instructions, (.5, 0.5),
                                  name='monospace',
                                  verticalalignment='center',
                                  horizontalalignment='center',
                                  multialignment='left',
                                  xycoords='axes fraction',
                                  animated=False)
        self.canvas.mpl_connect('key_press_event', self.on_key_press)

    def draw(self):
        draw_artist = self.ax.draw_artist
        if self.background is None:
            self.background = self.canvas.copy_from_bbox(self.ax.bbox)

        ## wiederherstelle den sauberen Hintergrund
        self.canvas.restore_region(self.background)

        ## zeige die Ablenkungen
        if self.distract:
            self.line.set_ydata(np.sin(self.x + self.cnt/self.res))
            self.line2.set_ydata(np.cos(self.x - self.cnt/self.res))
            self.line3.set_ydata(np.tan(self.x + self.cnt/self.res))
            self.line4.set_ydata(np.tan(self.x - self.cnt/self.res))
            draw_artist(self.line)
            draw_artist(self.line2)
            draw_artist(self.line3)
            draw_artist(self.line4)

        ## Pucks und Schläger
        if self.on:
            self.ax.draw_artist(self.centerline)
            for pad in self.pads:
                pad.disp.set_y(pad.y)
                pad.disp.set_x(pad.x)
                self.ax.draw_artist(pad.disp)

            for puck in self.pucks:
                if puck.update(self.pads):
                    ## wir kommen nur hierhin, wenn jemand einen Punkt gemacht hat
                    self.pads[0].disp.set_label(f"   {self.pads[0].score}")
                    self.pads[1].disp.set_label(f"   {self.pads[1].score}")
                    self.ax.legend(loc='center', framealpha=.2,
                                   facecolor='0.5',
                                   prop=FontProperties(size='xx-large',
                                                       weight='bold'))

                    self.background = None
                    self.ax.figure.canvas.draw_idle()
                    return
                puck.disp.set_offsets([[puck.x, puck.y]])
                self.ax.draw_artist(puck.disp)

        ## zeichne nur das Achsenrechteck neu
        self.canvas.blit(self.ax.bbox)
        self.canvas.flush_events()
        if self.cnt == 50000:
            ## damit wir nicht zu weit gehen
            print("...und du spielst schon zu lange!!!")
            plt.close()

        self.cnt += 1

    def on_key_press(self, event):
        if event.key == '3':
            self.res *= 5.0
        if event.key == '4':
            self.res /= 5.0

        if event.key == 'e':
            self.pads[0].y +=.1
            if self.pads[0].y > 1 -.3:
                self.pads[0].y = 1 -.3
        if event.key == 'd':
            self.pads[0].y -=.1
            if self.pads[0].y < -1:
                self.pads[0].y = -1

        if event.key == 'i':
            self.pads[1].y +=.1
            if self.pads[1].y > 1 -.3:
                self.pads[1].y = 1 -.3
        if event.key == 'k':
            self.pads[1].y -=.1
            if self.pads[1].y < -1:
                self.pads[1].y = -1

        if event.key == 'a':
            self.pucks.append(Puck(self.puckdisp,
                                   self.pads[randint(2)],
                                   self.ax.bbox))
        if event.key == 'A' and len(self.pucks):
            self.pucks.pop()
        if event.key == '':
            self.pucks[0]._reset(self.pads[randint(2)])
        if event.key == '1':
            for p in self.pucks:
                p._slower()
        if event.key == '2':
            for p in self.pucks:
                p._faster()

        if event.key == 'n':
            self.distract = not self.distract

        if event.key == 'g':
            self.on = not self.on
        if event.key == 't':
            self.inst = not self.inst
            self.i.set_visible(not self.i.get_visible())
            self.background = None
            self.canvas.draw_idle()
        if event.key == 'q':
            plt.close()

Erstelle die Spielanimation

Jetzt, nachdem wir die Game-Klasse definiert haben, können wir die Spielanimation erstellen, indem wir ein Game-Objekt instanziieren und in einer Schleife seine draw()-Methode aufrufen.

fig, ax = plt.subplots()
canvas = ax.figure.canvas
animation = Game(ax)

## deaktiviere die standardmäßigen Tastatureingaben
if fig.canvas.manager.key_press_handler_id is not None:
    canvas.mpl_disconnect(fig.canvas.manager.key_press_handler_id)


## Setze den Blitting-Hintergrund beim Neuziehen zurück
def on_redraw(event):
    animation.background = None


## Starte die Animation nach der ersten Zeichnung
def start_anim(event):
    canvas.mpl_disconnect(start_anim.cid)

    start_anim.timer.add_callback(animation.draw)
    start_anim.timer.start()
    canvas.mpl_connect('draw_event', on_redraw)


start_anim.cid = canvas.mpl_connect('draw_event', start_anim)
start_anim.timer = animation.canvas.new_timer(interval=1)

tstart = time.time()

plt.show()
print('FPS: %f' % (animation.cnt/(time.time() - tstart)))

Zusammenfassung

In diesem Lab haben Sie gelernt, wie Sie ein interaktives Pong-Spiel mit Matplotlib erstellen. Sie haben gelernt, wie Sie die Pad- und Puck-Klassen sowie die Game-Klasse definieren, die für die Verwaltung der Spiellogik und das Zeichnen des Spiels auf dem Bildschirm verantwortlich ist. Sie haben auch gelernt, wie Sie die Spielanimation erstellen, indem Sie ein Game-Objekt instanziieren und in einer Schleife seine draw()-Methode aufrufen. Mit diesen Kenntnissen können Sie jetzt Ihre eigenen interaktiven Spiele mit Python und Matplotlib erstellen.