Help us understand the problem. What is going on with this article?

【#1】PythonでMinecraftを作る。~下調べと設計~

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

minecraft.png

概要

世界的に超有名なサンドボックスゲーム「Minecraft」をプログラミング言語「Python」で再現するプロジェクトです。

次の記事: 【#2】PythonでMinecraftを作る。~モデル描画とプレイヤー実装~

きっかけ

僕がプログラミングに初めて触れたのは、「Minecraft」がきっかけでした。

4年以上前の事です。
Minecraftにおける「MOD」と呼ばれる所謂改造コンテンツに興味があり、「自分でも作ってみたい!」とプログラミングの世界に足を踏み入れました。
総プレイ時間は数万時間を超えており、色々と思い出深いゲームの一つです。

小学校低学年の頃からウェブサイトを制作したりと、多少はクリエイティブな活動をしておりました。
プログラミング言語を使用して実際に"モノ"をプログラミングし、それが実際に動いた時の感動は凄まじいものです。

また、僕の"プログラミング"に対する物事の考え方や組み立て方、それを実現するにあたっての知識
全てにおいて相性抜群でした。

そういった経緯もあり、4年経った今、"MOD"ではなく「Minecraft」そのものを実際に再現してみよう!
と立ち上がりました。

心構え

しかしながら、あの規模のゲームの再現は簡単なものではありません。

今でこそ、「Unity」や「Unreal Engine」といったゲームエンジンと呼ばれるもので、
コーディングを一切せずとも簡単にハイクオリティなゲームが作れてしまう時代です。

コーディングでさえも、ブループリントと呼ばれるものでビジュアライズし、GUIで組み立てる。
ということが可能であるほどです。これは驚くべきことです。

しかしながら、当プロジェクトではそういったゲームエンジンは使用しません。
本当の意味で"イチから"作り上げます。

下調べ

実際に制作する上で、先ずは敵を知らねばなりません。

Minecraftにおける描画

コンピュータグラフィックスライブラリと呼ばれる所謂「描画系」のライブラリは有名なものであれば
「DirectX」「OpenGL」があります。

本題です。MinecraftはJavaでどうやって描画しているのでしょうか?
Minecraftは、Javaにおけるゲームライブラリの一つである「LWJGL(Lightweight Java Game Library)」と呼ばれるものを使用しています。
OpenGLのWrapperの様です。

Pythonにこの様なライブラリは存在するのでしょうか。
最悪無くてもいいんです。無ければ作りゃいい。足りなくなったら足すだけや。

LWJGLに触れてみる

lwjgl.png

統合開発環境「Eclipse」を使用して、少しだけLWJGLに触れてみます。

公式サイトよりダウンロード。

image.png

懐かしい。この画面。
とりあえず、目が疲れるのでダークテーマに。
WindowPreferencesAppearance

image.png

適当にプロジェクトを作ります。
62bd73c3c6c553538f9cd37d5cd19263.png

適当にクラスを作成。

main.java
public class main 
{
    //コンストラクタ
    public main() {}

    public static void main(String[] args)
    {
        System.out.println("Hello LWJGL!");
    }
}

とりあえず動作確認完了。

image.png

LWJGLの環境構築

LWJGLを使うには、「JDK(Java Development Kit)」が必要です。

参考記事: WindowsへのJDKインストール方法

javac -versionで通ればOK。
image.png

LWJGL環境構築にあたって、Mavenをインストール。

参考記事: WindowsへのMavenインストール方法

mvn -vで通ればOK。
image.png

今回はMaven 3をインストールしました。


次に、
mvn archetype:generate -DgroupId=test -DartifactId=test
で適当にプロジェクトの作成
今回はEclipseのワーキングスペースに作成しました。

参考記事: Maven3のメモ

Choose org.apache.maven.archetypes:maven-archetype-quickstart version:
1: 1.0-alpha-1
2: 1.0-alpha-2
3: 1.0-alpha-3
4: 1.0-alpha-4
5: 1.0
6: 1.1
7: 1.3
8: 1.4
Choose a number: 8: 8

[INFO] Using property: groupId = test
[INFO] Using property: artifactId = test
Define value for property 'version' 1.0-SNAPSHOT: : 1.0-SNAPSHOT
[INFO] Using property: package = test
Confirm properties configuration:
groupId: test
artifactId: test
version: 1.0-SNAPSHOT
package: test
 Y: : Y

[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  06:21 min
[INFO] Finished at: 2020-01-21T08:58:05+09:00
[INFO] ------------------------------------------------------------------------

参考: LWJGL で Hello world してみる(環境構築編)

次に、
作成したプロジェクトの中のpom.xmlにLWJGLをリンク

mvn nativedependencies:copy

でダイナミックリンクライブラリをダウンロード

mvn clean eclipse:eclipse -DdownloadSources=true -DdownloadJavadocs=true

でEclipseでプロジェクトを読み込めるようにします。
Eclipseを開いて、Import existing projectsからインポート。

035c913206ba6d283300d9fcacdaaec8.png

ライブラリが構築されています。

image.png

実行

ソースコードは以下の通りです。

Main.java
package test;

import org.lwjgl.LWJGLException;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;

import static org.lwjgl.opengl.GL11.*;

public class Main 
{
    public static final int screen_width = 800;
    public static final int screen_height = 500;

    public static void Initialize()
    {
        try 
        {
            Display.setDisplayMode(new DisplayMode(screen_width, screen_height));
            Display.setTitle("Hello LWJGL!");
            Display.create();
        } 
        catch(LWJGLException e) 
        {
            e.printStackTrace();
            return;
        }

        try 
        {
            glOrtho(0, screen_width, 0, screen_height, 0, depth);
            glMatrixMode(GL_MODELVIEW);

            while (!Display.isCloseRequested()) 
            {
                glClear(GL_COLOR_BUFFER_BIT);

                Render();

                Display.update();
            }
        } 
        catch(Exception e) 
        {
            e.printStackTrace();
        } 
        finally 
        {
            Display.destroy();
        }
    }

    public static void Render()
    {
        glBegin(GL_QUADS);

        glEnd();
    }

    public static void main(String[] args) 
    {
        Initialize();
    }  
}

とりあえず何も描画されていませんが、いけました。
image.png


線を描画

画面右上から右下まで赤い線を描画します。

Main.java
public static void Render()
{
    glBegin(GL_LINES);

    glColor3f(1.0f, 0f, 0f);
    glVertex2f(0, 0);
    glVertex2f(screen_width, screen_height);

    glEnd();
}

線が描画されていることが確認できます。
image.png

Pythonでの描画

ここまでで、MinecraftにはOpenGLが使われていることがわかりました。
PythonでもOpenGLは使えるようです。

環境構築

「PyCharm」と呼ばれるIDEを使用します。

FileSettingProject:<プロジェクト名>Project Interpreter
より、必要なライブラリを追加します。
参考サイトにある画像読込のPillowはPython3.8サポート外らしいので、インストールできませんでした。

843d79107f26b0a03a748c8b80ca4686.png

PythonでOpenGLに触れてみる

main.pyを作成して実行。
ソースコードは以下の通りです。
このPythonのスカスカ記法、慣れるのに時間がかかりそうです。

main.py
from OpenGL.GL import *
import glfw


def main():

    if not glfw.init():
        return


    window = glfw.create_window(640, 480, 'Hello World', None, None)
    if not window:
        glfw.terminate()
        print('Failed to create window')
        return


    glfw.make_context_current(window)


    print('Vendor :', glGetString(GL_VENDOR))
    print('GPU :', glGetString(GL_RENDERER))
    print('OpenGL version :', glGetString(GL_VERSION))


    glfw.destroy_window(window)
    glfw.terminate()


if __name__ == "__main__":
    main()

出力結果:

Vendor : b'NVIDIA Corporation'
GPU : b'GeForce GTX 1080/PCIe/SSE2'
OpenGL version : b'4.6.0 NVIDIA 441.08'

Process finished with exit code 0

順調です。

ウィンドウに実際に描画してみる

ソースコードは以下の通りです。

glClearColorの引数はRGBAです。
それぞれfloatで指定します。
(r / 255.f, g / 255.f, b / 255.f, a / 255.f)
※Red Green Blue Alpha(透明度)

main.py
glfw.make_context_current(window)

glfw.window_hint(glfw.CONTEXT_VERSION_MAJOR, 4)
glfw.window_hint(glfw.CONTEXT_VERSION_MINOR, 0)
glfw.window_hint(glfw.OPENGL_PROFILE, glfw.OPENGL_CORE_PROFILE)

while not glfw.window_should_close(window):

    glClearColor(0, 1, 0, 1)
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

    glfw.swap_buffers(window)
    glfw.poll_events()

▼この様な感じで描画されます。
Python凄いですね。こんなに簡単にできてしまうのか。
C# .NET WinFormでやっていたら、今頃地獄を見ていたことでしょう。
image.png

豆知識:フロントバッファとバックバッファ

フロントバッファ1枚だと、更新する度にチカチカしてしまったり、問題が生じます。
そこでバックバッファと呼ばれるものを用意し、更新はバックバッファに対して行います。
SwapBuffer()でバックバッファをフロントバッファに送ることで、画面に表示します。

renderbuffer.png

設計

ここまでで、OpenGLでどの様に描画しているのか踏まえつつ、実際に触れてみました。

ここからが本題です。
ゲームを制作していく上で、あてずっぽうにプログラミングしていては、プログラムの保守性やメンテナンス性に支障をきたします。
そこで、大まかでも良いので、実際にプログラムの設計図を作成します。

ワールドの管理

Minecraftでは、Worldのマネジメント手段のひとつとして、「チャンク」と呼ばれるものがあります。
これは、広大なワールドを16(x)×16(z)×256(y)のチャンクに分割し、必要な範囲のみ読み込み/描画を行うことで負荷を最小限に抑えるものです。

不思議に思ったこと

通常、3Dゲームのワールドにおける3次元ベクトルの概念ではZが高さとして定義されていると認識していますが、MinecraftではYが高さなんです。
理由は分かりません。謎です。。

▼下記画像の3次元におけるZがMinecraftではYと定義されています。
image.png

画像出典: 第8回 基礎数学Ⅰ

ブロックの管理

次に、ブロックについてです。
Minecraftでは、内部的にはブロックには2種類あります。

  • "土"ブロックといった、何の機能も持たない普通のブロックをBlock
  • "かまど"ブロックといった、GUIや機能を持つ特殊なブロックをTileEntity

として定義されています。
"かまど"等の機能を持ったブロックの実体はBlockであり、TileEntityのオブジェクトを持ちます。
これも負荷軽減やリソース節約のワールドマネジメントの一つです。

Block

Blockはインターフェースを継承します。
インターフェースにはWorldのインスタンスも含まれており、ブロックからWorldへ設置された/破壊された...等のイベントを送受信します。

例えば、こんな感じです。

public boolean onBlockActivated(World world, int x, int y, int z, EntityPlayer player)

TileEntity

TileEntityの実体はBlockですから、BlockTileEntityの紐付けが必要です。
そこで、Minecraftではこの様に特定のブロックのインターフェース関数からTileEntityのオブジェクトを生成します。
MyTileEntityClassはインターフェースTileEntityを継承します。

public TileEntity createNewTileEntity(World world)
{
   return new MyTileEntityClass();
}

設計

ここまでの情報を基に、軽く設計図を組み立ててみます。
あまりにも規模が大きいので、とりあえず細かい所は何も考えず、現状でわかっていること・実装したいことを基に設計を行います。

▼この様な感じになりました。
ワールドとの情報のやり取り等のイベントはインターフェースに定義します。
diagram_pycraft.png

悩みどころ

Minecraftのソースコードを実際に見たわけではないので、これまでMOD開発で培った経験や知識を基に大まかな設計はできましたが、細かい所までは自分で考えて実装しなければなりません。
なかなか厳しい戦いになりそうです。

特に、悩みどころは描画です。
Worldからの描画を実際にどうやってレンダリングしようか。と。
特に3次元空間なので、2次元空間と違ってややこしそうです。
テクスチャの読み込み..etc問題は山積みです。

とりあえず、メニュー画面の制作でしょうか。

次回に続く

規模が大きいので、プロジェクトはパート毎に分けることにしました。
プロジェクト名は...「PyCraft」でいいかな?
それっぽいロゴを制作して下さる方募集中です。宜しくお願い致します。

次回はメニュー画面の制作と、レンダリング問題の解決を目標とします。

最後までご覧頂きありがとうございました。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした