[# 2] Mach Minecraft mit Python. ~ Modellzeichnung und Player-Implementierung ~

Part of pictures, articles, images on this page are copyrighted by Mojang AB.

minecraft.png

Überblick

Dies ist ein Projekt zur Reproduktion des weltberühmten Sandbox-Spiels "Minecraft" in der Programmiersprache "Python".

** Vorheriger Artikel: "[# 1] Minecraft mit Python erstellen. Vorläufige Forschung und Design-" **

** Nächster Artikel: "[# 3] Minecraft mit Python erstellen. - Spielerbewegung (Konzept der Trägheit) und Kollisionsbeurteilung verbessern-" ** ** **

Vorwort

Vielen Dank für Ihre Geduld. Dies ist der zweite!

Was ist diesmal zu tun?

Auswahl der Spiel-Engine

Ich konnte keine 3D-Game-Engine von Grund auf neu erstellen und suchte nach einer 3D-Game-Engine (Bibliothek), die auch Python unterstützt.

Unter diesen haben wir diejenigen ausgewählt, die relativ einfach zu bedienen sind und wahrscheinlich schöne Zeichnungen produzieren.

Panda3D Engine 509ed0ebac43da9c5fa01735640f7ef6.png

Es ist eine 3D-Spiel-Engine namens "Panda3D". Plattformen sind Python und C ++.

Ich habe versucht, Panda3D zu verwenden

Ich werde es tatsächlich bewegen.

▼ Installation

pip install --pre --extra-index-url https://archive.panda3d.org/ panda3d

▼ Quellcode

main.py


from Renderer import engine

def main():
    _render = engine.Renderer()
    _render.run()

if __name__ == "__main__":
    main()

engine.py


from direct.showbase.ShowBase import ShowBase
from direct.gui.OnscreenText import OnscreenText
from panda3d.core import TextNode
from pandac.PandaModules import WindowProperties

class Renderer(ShowBase):

    def __init__(self):
        ShowBase.__init__(self)

        props = WindowProperties()
        props.setTitle('PyCraft')
        props.setSize(1280, 720)
        self.win.requestProperties(props)

        OnscreenText(text="PyCraft ScreenText",
                     parent=None, align=TextNode.ARight,
                     fg=(1, 1, 1, 1), pos=(-0.1, 0.1), scale=.08,
                     shadow=(0, 0, 0, 0.5))

Es wurde so gezeichnet.

image.png

Versuchen Sie, das Modell zu laden

engine.py


from direct.showbase.ShowBase import ShowBase
from direct.gui.OnscreenText import OnscreenText
from panda3d.core import TextNode
from pandac.PandaModules import WindowProperties
from direct.showbase.Loader import Loader

class Renderer(ShowBase):

    def __init__(self):
        ShowBase.__init__(self)

        props = WindowProperties()
        props.setTitle('PyCraft')
        props.setSize(1280, 720)
        self.win.requestProperties(props)

        OnscreenText(text="PyCraft ScreenText",
                     parent=None, align=TextNode.ARight,
                     fg=(1, 1, 1, 1), pos=(-0.1, 0.1), scale=.08,
                     shadow=(0, 0, 0, 0.5))

        self.scene = self.loader.loadModel("models/environment")
        self.scene.reparentTo(self.render)
        self.scene.setScale(1, 1, 1)
        self.scene.setPos(0, 0, 0)

        self.cube = self.loader.loadModel("models/misc/rgbCube")
        self.cube.reparentTo(self.render)
        self.cube.setScale(1, 1, 1)
        self.cube.setPos(0, 20, 0)

Es stellte sich heraus, dass es so etwas war. Es scheint, dass Sie es mit einer Maus bedienen können. image.png Der Würfel sieht so aus. image.png


Versuchen Sie, in 3D zu zeichnen

Referenz: 6 Sekunden Video mit gif @ panda3D

0ku48-wnqiw.gif

▼ Quellcode Da es lang sein wird, wird nur der Kernteil beschrieben.

engine.py


        self.world = BulletWorld()
        self.world.setGravity(Vec3(0, 0, -9.81))
        self.worldPath = self.render.attachNewNode("")

        debugNode = BulletDebugNode()
        nodePath = self.worldPath.attachNewNode(debugNode)
        nodePath.show()
        self.world.setDebugNode(debugNode)

        bodyGround = BulletRigidBodyNode()
        bodyGround.addShape(BulletBoxShape((3, 3, 0.001)))
        nodePath = self.worldPath.attachNewNode(bodyGround)
        nodePath.setPos(0, 0, -2)
        nodePath.setHpr(0, 12, 0)
        self.world.attachRigidBody(bodyGround)

        self.boxes = []
        for i in range(30):
            bodyBox = BulletRigidBodyNode()
            bodyBox.setMass(1.0)
            bodyBox.addShape(BulletBoxShape((0.5, 0.5, 0.5)))
            nodePath = self.worldPath.attachNewNode(bodyBox)
            nodePath.setPos(0, 0, 2 + i * 2)
            self.boxes.append(nodePath)
            self.world.attachRigidBody(bodyBox)

Ogre3D "Ogre3D" ist eine der Spiel-Engines, die Hardwarebeschleunigung verwenden. Es scheint, dass es nicht nur C ++, sondern auch Python und .NET unterstützt. Die Realisierung von 3D-Spielen in .NET ist ziemlich schwierig, daher möchte ich Ogre.NET verwenden, um zu erkennen, dass "ich versucht habe, Minecraft mit C # zu reproduzieren!".

Schauen Sie sich [Showcase] an (https://www.ogre3d.org/showcase).

Dies scheint ein Spiel zu sein "X-Morph: Defense". Natürlich wird der Ogre-Motor verwendet. Das ist eine großartige Grafik.

image.png

image.png

Das Ergebnis verschiedener Gedanken

image.png

Zusätzlich zu anderen als den oben genannten Spiel-Engines habe ich mich für "Pyglet" entschieden, das die meisten Informationen zu enthalten scheint, da es plattformübergreifend ist. Es hängt nicht von anderen Bibliotheken ab. ・ Es ist perfekt, dass es Multi-Display und Multi-Monitor unterstützt.

Ich habe versucht, Pyglet zu verwenden

pyxpyg.png

Von hier aus machen wir eine einfache Zeichnung mit Pyglet.

Umgebung

▼ Installation

pip install pyglet

▼ Installation (für PyCharm) Menüleiste DateiEinstellungen pygletins.png

Versuche dich zu bewegen

▼ Beispielcode

main.py


#Modul importieren
from pyglet.gl import *

#Fenster zum Anzeigen
#Breite ist die Breite und Höhe ist die Höhe.
#Die Beschriftung ist der Fenstertitel und die Größe ändert, ob die Größe geändert werden soll.
pyglet.window.Window(width=700, height=400, caption='Hello Pyglet!', resizable=True)
pyglet.app.run()

Wenn es so angezeigt wird, ist es OK. image.png

Versuchen Sie, eine Linie zu zeichnen

OpenGL wird in Pyglet verwendet.

▼ Beispielcode

main.py


#Modul importieren
from pyglet.gl import *

#Fenster
window = pyglet.window.Window(width=700, height=400, caption='Hello Pyglet!', resizable=True)

#Fensterzeichnungsereignis
@window.event
def on_draw():
    #Klare Zeichnung
    window.clear()
    glBegin(GL_LINES) #Beginnen Sie mit dem Zeichnen einer Linie
    glColor3f(1, 0, 0) # R,G,B ÷ 255
    glVertex2f(0, 0) # x, y(0, 0)Von
    glVertex2f(100, 100) # x, y(100, 100)Bis
    glEnd() #Beenden Sie das Zeichnen der Linie

pyglet.app.run()

Bei der Ausführung wird die Linie wie folgt gezeichnet. image.png

Kommentar

Im obigen Quellcode wird die Zeichenereignisfunktion von "Fenster" definiert und die Linie wird mit OpenGL gezeichnet. Es scheint, dass OpenGL die untere linke Ecke des Bildschirms als Nullkoordinaten erkennt.

** 1. Deklarieren Sie den Beginn der Strichzeichnung mit glBegin (GL_LINES) **

** 2. Deklariere Farbe mit glColor3f (1, 0, 0) ** Die Farbe hier ist RGB (Rot, Grün, Blau). Jedes ist normalerweise 0 bis 255, aber hier wird es mit "Float" deklariert, wie es "3f" sagt. Übergeben Sie also die durch "255.f" geteilte Zahl als Argument. 3f bedeutet , 3-Nummern in float zu übergeben.

** 2. Deklarieren Sie den Startpunkt der Linie mit glVertex2f (x, y) ** Übergeben Sie die Koordinaten des Startpunkts der Linie. "2f" bedeutet, die Koordinaten der "2" -Dimension mit "f" loat zu übergeben.

** 3. Deklarieren Sie den Endpunkt der Linie mit glVertex2f (x, y) ** Übergeben Sie die Koordinaten des Endpunkts der Linie. Es ist in Ordnung, hier eine Zahl zu übergeben, die größer als das Fenster ist, aber sie wird nicht vom Bildschirm gezeichnet.

** 4. Deklarieren Sie das Ende der Strichzeichnung mit glEnd () **

pygletdem2.png

Versuchen Sie, ein Blockmodell von Minecraft zu zeichnen

Endlich das Hauptthema.

Vorwort

image.png Es ist ein ** großer Fehler **, zu versuchen, alles auf einmal zu implementieren, um das Projekt zu realisieren.

Angenommen, Sie möchten "Funktion A", "Funktion B" und "Funktion C" implementieren. Wenn diese auf einmal implementiert werden und ein Fehler oder Defekt auftritt, ist es schwierig zu identifizieren, welche Funktion (welcher Teil) den Fehler oder Defekt verursacht, oder es dauert länger als gewöhnlich, die Ursache zu finden. Ich werde das machen.

Das richtige Verfahren ist Implementierung von Funktion A ▶ ** Funktionsprüfung ** ▶ Implementierung von Funktion B ▶ ** Funktionsprüfung ** ▶ Implementierung von Funktion C ist.

In dem unwahrscheinlichen Fall, dass ein Problem auftritt, nachdem die "Funktion C" implementiert wurde, besteht ein Problem zwischen der "Funktion B" und der "Funktion C". Kann als gedacht werden. Es ist effizienter.

Lassen Sie uns zunächst den Vorgang mit der Mindestkonfiguration überprüfen. Die Einführung ist lang.

Textur vorbereiten

Vorerst werden wir es probeweise verwenden, daher haben wir diese Textur vorbereitet, die Minecraft vertraut ist. ▼ Textur missing16.png

Es ist Zeit zu implementieren

Lassen Sie uns tatsächlich mit der vorbereiteten Textur zeichnen! Der Quellcode wird lang sein, also habe ich ihn auf Gist gepostet.

▼ Quellcode Gist: main.py

Bei der Ausführung wird es wie folgt gezeichnet. * Normaler Zustand. </ font>

9a6730ede366fb49dbcb1cdd1a2c17c2.png

Vorerst konnte ich nur auf einer Seite zeichnen.

Kommentar

Ich werde beim Extrahieren von Teilen erklären.

Weltklasse

Definiert einen sogenannten Stapel, der den zu zeichnenden Scheitelpunkt enthält.

self.batch = pyglet.graphics.Batch()

Definieren Sie dann die dreidimensionalen Koordinaten, die der Schlüssel sind. Wenn hier "0" eingestellt ist, wird es vom Bildschirm gezeichnet ** und kann aufgrund der Position der ursprünglichen Kamera nicht bestätigt werden **, sodass es mit einer leichten Verschiebung definiert wird.

#Definieren Sie 3D-Weltkoordinaten
x = 0.5
y = 0
z = -2

Zum Laden von Texturen steht eine Funktion zur Verfügung. Verwenden Sie den nativen Pyglet-Lader. Geben Sie den Image-Patch für "Pfad" an.

#Funktion zum Laden der Textur
def load_texture(self, path):
    texture = pyglet.image.load(path).get_texture()#Verwenden Sie den Texturlader von pyglet
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
    glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
    return pyglet.graphics.TextureGroup(texture)

Fensterklasse

Als nächstes kommt die Fensterklasse. Diese Klasse erbt von der Klasse pyglet.window.Window.

Was ist "Vererbung"?

Die Initialisierungsfunktion initialisiert die Instanz der zuvor definierten Klasse "World".

super ähnelt Java.

Was ist "super?"

def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    #Weltklasse-Initialisierung
    self.world = World()

Definieren Sie dann die Funktion, die die wesentliche Zeichnung ausführt.

def on_draw(self):
    #Löschen Sie den Bildschirm
    self.clear()
    #Da die Welt 3D ist, stellen Sie den 3D-Zeichenmodus ein
    self.render_mode_3d()
    #Zeichne die Welt
    self.world.draw()

Hier ist die Funktion render_mode_3d (), Beim Ausführen von 3D-Zeichnungen und 2D-Zeichnungen muss der Matrixmodus eingestellt werden. Es verfügt über ** Projektion ** und Feldkonvertierungsmodus (Modellansicht), der standardmäßig den Feldkonvertierungsmodus verwendet. Wir werden diese Matrixmodi später diskutieren.

Hinweis: Verwenden Sie glLoadIdentity (), um den Konvertierungsprozess für den kumulativen Modus zu löschen (zu initialisieren). </ font>


def render_mode_3d(self):
    self.render_mode_projection()
    #Sichtfeld einstellen
    gluPerspective(70, self.width / self.height, 0.05, 1000)
    self.render_mode_modelview()

def render_mode_2d(self):
    self.render_mode_projection()
    #Zeichenbereich 0 zum Fenster_Breite, 0 zum Fenster_height
    gluOrtho2D(0, self.width, 0, self.height)
    self.render_mode_modelview()

def render_mode_projection(self):
    glMatrixMode(GL_PROJECTION)#Projektionskonvertierungsmodus
    glLoadIdentity()#Beseitigen Sie den kumulativen Konvertierungsprozess

def render_mode_modelview(self):
    glMatrixMode(GL_MODELVIEW)#Modellierungskonvertierungsmodus(Feldkonvertierung)
    glLoadIdentity()#Beseitigen Sie den kumulativen Konvertierungsprozess

gluOrtho2D führt eine Projektionskonvertierung durch, bei der es sich um eine Parallelprojektion in 2D handelt. gluOrtho2D(left, right, top, bottom)

gl2d.png

Dann legt gluPerspective () das Sichtfeld fest. gluPerspective(fovY, aspect, zNear, zFar) Der Betrachtungswinkel der Y-Achse, der Aspekt (horizontaler Betrachtungswinkel), der Z-kürzeste Abstand bzw. der Z-Längste Abstand.

glsiya.png

Außerdem ändern zNear und zFar das Erscheinungsbild auf dem Bildschirm nicht. Dies kann als ** angesehen werden, unabhängig davon, ob Sie 1 m näher am Bildschirm oder 1 m näher am Bildschirm sind **.

Sonderedition

Ab hier wird der Inhalt spezialisiert. Wenn Sie nicht interessiert sind, können Sie ihn überspringen.

Matrix-Modus

Zu den Matrixmodi gehören ** visuelle Transformation (GL_PROJECTION) ** und ** Modellierungstransformation (GL_MODELVIEW) **. * Um genau zu sein, gibt es auch GL_TEXTURE, aber es wird in dieser Quelle nicht behandelt, daher wird es übersprungen. </ font>

Auch 3D ▶ 2D-Konvertierung ist "Geometriekonvertierung" "Affin-Konvertierung" zur Konvertierung beim Skalieren / Verschieben Wird genannt.

Es wird super lang und ich denke, einige Leute sind interessiert, also werde ich es in einen externen Artikel werfen. Es tut uns leid.

"Affin-Konvertierung vollständig verstehen"

Ich konnte keinen japanischen Artikel finden, der die Geometriekonvertierung ausführlich erklärt.

Player-Implementierung

Ich möchte den gesamten Block zeichnen, aber es ist unpraktisch, weil ich den Ansichtspunkt nicht so verschieben kann, wie er ist. Daher werde ich die Ansichtspunktbewegung vorerst genauso wie den Spieler implementieren. Ich habe auch "PyImGui" verwendet, um die Debug-Informationen anzuzeigen.

▼ Quellcode Gist: main.py

▼ So. ddiol-p2pjh.gif

Zeichnen wir den gesamten Block

Nachdem Sie den Ansichtspunkt und die Koordinaten frei bewegen können, zeichnen wir den gesamten Block. Der Quellcode lautet wie folgt. Es tut mir leid, wenn ich einen Fehler in der Richtung gemacht habe. ~~ Ich habe Angst, also entschuldige ich mich im Voraus. ~~

▼ Quellcode Gist: main.py

#Es tut mir leid, wenn ich einen Fehler mache
#Vorderseite
self.batch.add(4, GL_QUADS, self.loaded_texture, ('v3f', (x, y, z, x, y, z+1, x, y+1, z+1, x, y+1, z,)), texture_coordinates)
#Rückseite
self.batch.add(4, GL_QUADS, self.loaded_texture, ('v3f', (x+1, y, z+1, x+1, y, z, x+1, y+1, z, x+1, y+1, z+1,)), texture_coordinates)
#Bodenfläche
self.batch.add(4, GL_QUADS, self.loaded_texture, ('v3f', (x, y, z, x+1, y, z, x+1, y, z+1, x, y, z+1,)), texture_coordinates)
#Obere Oberfläche
self.batch.add(4, GL_QUADS, self.loaded_texture, ('v3f', (x, y+1, z+1, x+1, y+1, z+1, x+1, y+1, z, x, y+1, z,)), texture_coordinates)
#Rechte Seite
self.batch.add(4, GL_QUADS, self.loaded_texture, ('v3f', (x+1, y, z, x, y, z, x, y+1, z, x+1, y+1, z,)), texture_coordinates)
#links
self.batch.add(4, GL_QUADS, self.loaded_texture, ('v3f', (x, y, z+1, x+1, y, z+1, x+1, y+1, z+1, x, y+1, z+1,)), texture_coordinates)

▼ Es sieht so aus 9cu9p-1169w.gif Wie Sie dem Video entnehmen können, weist die Zeichnung übrigens einige Auffälligkeiten auf. Dies liegt daran, dass * zusätzliche Teile * wie der normalerweise unsichtbare Teil (Rückseite) gezeichnet werden. Um dies zu verhindern, setzen Sie glEnable (GL_DEPTH_TEST). Dies ist eine bequeme Möglichkeit, unnötige Oberflächenzeichnungen auf der OpenGL-Seite auszuwählen / zu eliminieren.

▼ Nach dem Einstellen wurde es wunderschön gezeichnet! sq6py-82p4x.gif

Schließlich

Dieses Mal haben wir die Spiel-Engine ausgewählt, das Modell gezeichnet und den Spieler implementiert. Das nächste Mal möchte ich eine Welt aufbauen und ein Hit-Urteil umsetzen.

Danke, dass du bis zum Ende zugesehen hast.

Recommended Posts