[# 2] Créez Minecraft avec Python. ~ Dessin du modèle et implémentation du lecteur ~

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

minecraft.png

Aperçu

Il s'agit d'un projet visant à reproduire le jeu de bac à sable mondialement connu "Minecraft" dans le langage de programmation "Python".

** Article précédent: "[# 1] Créer Minecraft avec Python.-Recherche préliminaire et conception-" **

** Article suivant: "[# 3] Make Minecraft with Python.-Amélioration du mouvement du joueur (concept d'inertie) et du jugement de collision-" ** **

Préface

Merci beaucoup pour votre patience. C'est la seconde!

Que faire cette fois

--Sélection du moteur de jeu --Exécution du procès

Sélection du moteur de jeu

Je ne pouvais pas créer un moteur de jeu 3D à partir de rien, alors j'ai cherché un moteur de jeu 3D (bibliothèque) qui prend également en charge Python.

Parmi eux, nous avons sélectionné ceux qui sont relativement faciles à utiliser et qui sont susceptibles de produire de beaux dessins.

Panda3D Engine 509ed0ebac43da9c5fa01735640f7ef6.png

C'est un moteur de jeu 3D appelé "Panda3D". Les plates-formes sont Python et C ++.

J'ai essayé d'utiliser Panda3D

Je vais en fait le déplacer.

▼ Installation

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

▼ Code source

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))

Il a été dessiné comme ça.

image.png

Essayez de charger le modèle

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)

Il s'est avéré être quelque chose comme ça. Il semble que vous puissiez l'utiliser avec une souris. image.png Le cube ressemble à ceci. image.png


Essayez de dessiner en 3D

Référence: vidéo de 6 secondes avec gif @ panda3D

0ku48-wnqiw.gif

▼ Code source Puisqu'elle sera longue, seule la partie noyau sera décrite.

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" est l'un des moteurs de jeu qui utilisent l'accélération matérielle. Il semble qu'il prend en charge non seulement C ++ mais aussi Python et .NET. La réalisation de jeux 3D sur .NET est assez difficile, j'aimerais donc utiliser Ogre.NET pour réaliser "J'ai essayé de reproduire Minecraft avec C #!".

Jetez un œil à Showcase.

Cela semble être un jeu comme "X-Morph: Defense". Bien sûr, le moteur Ogre est utilisé. C'est un super graphique.

image.png

image.png

Le résultat de diverses pensées

image.png

En plus des moteurs de jeu autres que ceux ci-dessus, j'ai décidé d'utiliser "Pyglet", qui semble avoir le plus d'informations, étant donné qu'il est multiplateforme. Cela ne dépend pas des autres bibliothèques. ・ Il est parfait qu'il prenne en charge le multi-affichage et le multi-moniteur.

J'ai essayé d'utiliser Pyglet

pyxpyg.png

À partir de là, faisons un dessin simple en utilisant Pyglet.

Environnement

▼ Installation

pip install pyglet

▼ Installation (pour PyCharm) Barre de menus FichierParamètres pygletins.png

Essayez de bouger

▼ Exemple de code

main.py


#Module d'importation
from pyglet.gl import *

#Fenêtre à afficher
#width est la largeur et la hauteur est la hauteur.
#caption est le titre de la fenêtre et redimensionnable indique s'il faut autoriser le redimensionnement.
pyglet.window.Window(width=700, height=400, caption='Hello Pyglet!', resizable=True)
pyglet.app.run()

S'il est affiché comme ça, c'est OK. image.png

Essayez de tracer une ligne

OpenGL est utilisé dans Pyglet.

▼ Exemple de code

main.py


#Module d'importation
from pyglet.gl import *

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

#Événement de dessin de fenêtre
@window.event
def on_draw():
    #Dessin clair
    window.clear()
    glBegin(GL_LINES) #Commencez à dessiner une ligne
    glColor3f(1, 0, 0) # R,G,B ÷ 255
    glVertex2f(0, 0) # x, y(0, 0)De
    glVertex2f(100, 100) # x, y(100, 100)Jusqu'à ce que
    glEnd() #Terminez de dessiner la ligne

pyglet.app.run()

Une fois exécutée, la ligne sera dessinée comme ceci. image.png

Commentaire

Dans le code source ci-dessus, la fonction d'événement de dessin de window est définie et la ligne est dessinée à l'aide d'OpenGL. Il semble qu'OpenGL reconnaît le coin inférieur gauche de l'écran comme des coordonnées nulles.

** 1. Déclarez le début du dessin au trait avec glBegin (GL_LINES) **

** 2. Déclarez la couleur avec glColor3f (1, 0, 0) ** La couleur ici est RVB (rouge, vert, bleu). Chacun est généralement compris entre 0 et 255, mais ici il est déclaré avec Float comme il est dit 3f, alors passez le nombre divisé par 255.f comme argument. «3f» signifie passer les nombres «3» dans «f».

** 2. Déclarez le point de départ de la ligne avec glVertex2f (x, y) ** Passez les coordonnées du point de départ de la ligne. «2f» signifie passer les coordonnées de la dimension «2» avec «f» loat.

** 3. Déclarez le point final de la ligne avec glVertex2f (x, y) ** Passez les coordonnées du point final de la ligne. Il est acceptable de passer ici un nombre plus grand que la taille de la fenêtre, mais il ne sera pas dessiné hors de l'écran.

** 4. Déclarez la fin du dessin au trait avec glEnd () **

pygletdem2.png

Essayez de dessiner un modèle de bloc de Minecraft

Enfin le sujet principal.

Préface

image.png C'est une ** grosse erreur ** d'essayer de tout implémenter à la fois pour réaliser le projet.

Supposons que vous souhaitiez implémenter la fonction A, la fonction B et la fonction C. Si ceux-ci sont tous implémentés en même temps et qu'un bogue ou un défaut se produit, il est difficile d'identifier quelle fonction (quelle partie) est à l'origine du bogue ou du défaut, ou il faut plus de temps que d'habitude pour en trouver la cause. Je le ferai.

La procédure correcte est Mise en œuvre de la fonction A ▶ ** Contrôle de fonctionnement ** ▶ Mise en œuvre de la fonction B ▶ ** Contrôle de fonctionnement ** ▶ Mise en œuvre de la fonction C est.

Dans le cas peu probable où un problème surviendrait après l'implémentation de la «fonction C», le problème est survenu entre la «fonction B» et la «fonction C». Peut être considéré comme. C'est plus efficace.

Commençons par vérifier le fonctionnement avec la configuration minimale. L'introduction est longue.

Préparation de la texture

Pour le moment, nous l'utiliserons à titre d'essai, nous avons donc préparé cette texture, familière à Minecraft. ▼ Texture missing16.png

Il est temps de mettre en œuvre

Dessinons réellement en utilisant la texture préparée! Le code source sera long, je l'ai donc posté sur Gist.

▼ Code source Gist: main.py

Une fois exécuté, il sera dessiné comme suit. * Condition normale. </ font>

9a6730ede366fb49dbcb1cdd1a2c17c2.png

Pour le moment, je n'ai pu dessiner que d'un seul côté.

Commentaire

Je vais vous expliquer en extrayant des pièces.

Classe mondiale

Définit ce qu'on appelle un Batch qui contient le Vertex à dessiner.

self.batch = pyglet.graphics.Batch()

Ensuite, définissez les coordonnées tridimensionnelles qui sont la clé. Ici, s'il est réglé sur «0», il sera dessiné hors de l'écran ** et ne peut pas être confirmé ** en raison de l'emplacement de la caméra initiale, il est donc défini avec un léger décalage.

#Définir les coordonnées du monde 3D
x = 0.5
y = 0
z = -2

Une fonction est fournie pour charger les textures. Utilisez le chargeur natif pyglet. Spécifiez le patch d'image pour path.

#Fonction pour charger la texture
def load_texture(self, path):
    texture = pyglet.image.load(path).get_texture()#Utilisez le chargeur de texture de 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)

Classe de fenêtre

Vient ensuite la classe window. Cette classe hérite de la classe pyglet.window.Window.

Qu'est-ce que «l'héritage»?

La fonction d'initialisation initialise instance de la classe «World» définie précédemment.

super est similaire à Java.

Qu'est-ce que "super?"

def __init__(self, *args, **kwargs):
    super().__init__(*args, **kwargs)
    #Initialisation de classe mondiale
    self.world = World()

Ensuite, définissez la fonction qui exécute le dessin essentiel.

def on_draw(self):
    #Effacer l'écran
    self.clear()
    #Puisque le monde est en 3D, réglez-le en mode de dessin 3D
    self.render_mode_3d()
    #Dessine le monde
    self.world.draw()

Voici la fonction render_mode_3d (), Il est nécessaire de définir le mode matrice lors de l'exécution d'un dessin 3D et d'un dessin 2D. Il a ** Projection ** et Mode de conversion de champ (Modelview), qui par défaut en mode de conversion de champ. Nous discuterons de ces modes matriciels plus tard.

Remarque: Utilisez glLoadIdentity () pour effacer (initialiser) le processus de conversion en mode cumulatif. </ font>


def render_mode_3d(self):
    self.render_mode_projection()
    #Définir le champ de vision
    gluPerspective(70, self.width / self.height, 0.05, 1000)
    self.render_mode_modelview()

def render_mode_2d(self):
    self.render_mode_projection()
    #Zone de dessin 0 à fenêtre_largeur, 0 à la fenêtre_height
    gluOrtho2D(0, self.width, 0, self.height)
    self.render_mode_modelview()

def render_mode_projection(self):
    glMatrixMode(GL_PROJECTION)#Mode de conversion de projection
    glLoadIdentity()#Éliminez le processus de conversion cumulatif

def render_mode_modelview(self):
    glMatrixMode(GL_MODELVIEW)#Modélisation du mode de conversion(Conversion de champ)
    glLoadIdentity()#Éliminez le processus de conversion cumulatif

gluOrtho2D effectue une conversion de projection, qui est une projection parallèle en 2D. gluOrtho2D(left, right, top, bottom)

gl2d.png

Ensuite, gluPerspective () définit le champ de vision. gluPerspective(fovY, aspect, zNear, zFar) L'angle de vision de l'axe Y, l'aspect (angle de vision horizontal), la distance la plus courte Z et la distance la plus longue Z, respectivement.

glsiya.png

De plus, «zNear» et «zFar» ne modifient pas l'apparence à l'écran. Cela peut être considéré comme ** si vous êtes 1 m plus près de l'écran ou 1 m plus près de l'écran **.

Edition supplémentaire

À partir de là, le contenu sera spécialisé, donc si vous n'êtes pas intéressé, vous pouvez l'ignorer.

Mode matriciel

Les modes matriciels incluent ** transformation visuelle (GL_PROJECTION) ** et ** transformation de modélisation (GL_MODELVIEW) **. * Pour être exact, il y a aussi GL_TEXTURE, mais il n'est pas géré dans cette source, alors sautez-le. </ font>

De plus, la conversion 3D ▶ 2D est une «conversion de géométrie» ʻAffin conversion` pour la conversion lors de la mise à l'échelle / du déplacement Est appelé.

Ça va être super long et je pense que certaines personnes sont intéressées, alors je vais le jeter dans un article externe. Pardon.

"Comprendre complètement la conversion Affin"

Je n'ai pas trouvé d'article japonais expliquant en détail la conversion de géométrie.

Implémentation du joueur

Je voudrais dessiner le bloc entier, mais c'est gênant car je ne peux pas déplacer le point de vue tel quel, donc pour le moment, je vais implémenter le mouvement du point de vue comme le joueur. J'ai également utilisé "PyImGui" pour afficher les informations de débogage.

▼ Code source Gist: main.py

▼ Comme ça ddiol-p2pjh.gif

Dessinons tout le bloc

Maintenant que vous pouvez déplacer librement le point de vue et les coordonnées, dessinons le bloc entier. Le code source est le suivant. Je suis désolé si j'ai fait une erreur dans la direction. ~~ J'ai peur, alors je m'excuse à l'avance. ~~

▼ Code source Gist: main.py

#Je suis désolé si je fais une erreur
#De face
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)
#Arrière
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)
#Surface inférieure
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)
#Surface supérieure
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)
#Côté droit
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)
#la gauche
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)

▼ Ça ressemble à ça 9cu9p-1169w.gif Au fait, comme vous pouvez le voir sur la vidéo, il y a des anomalies dans le dessin. Ceci est dû au fait que * des pièces supplémentaires * telles que la partie normalement invisible (surface arrière) sont dessinées. Pour éviter cela, définissez glEnable (GL_DEPTH_TEST). C'est un moyen pratique de sélectionner / d'éliminer le dessin de surface inutile du côté OpenGL.

▼ Après le réglage, il était magnifiquement dessiné! sq6py-82p4x.gif

finalement

Cette fois, nous avons sélectionné le moteur de jeu, dessiné le modèle et implémenté le joueur. La prochaine fois, j'aimerais construire un monde et mettre en œuvre le jugement de frappe.

Merci d'avoir regardé jusqu'à la fin.

Recommended Posts