見出し画像

Processing でマイクラ① マイクラ風の家を建築

Minecraft (JAVA 版) は、Modといわれる拡張機能を追加できます。わたしが一押しのModは「Rapsberry Jam Mod」です。
このModのすごいところは、Pythonプログラムでマイクラ建築を自動化できるのです。マイクラで遊びながら、プログラミングの勉強もできる! かくいう私もこのModのおかげでプログラミングにはまっていったんですね。マイクラ好きな小学生以上のお子様にもお勧めします。
rapsberryjammod

今回は、Processing でマイクラ風の家を建築します。トップの画像は実際に Processing で建築した作品です。光の感じは違いますが、かなりマイクラっぽくないですか?
家を作成するために、まずはテクスチャを貼り付けたブロックを作ります。

テクスチャを準備する

テクスチャとは3次元形状に貼り付ける画像のことです。マイクラのブロックは、正四角柱(サイコロ型)にテクスチャが貼り付けてあります。昔のバージョンでは「terrain.png」という256x256の画像にブロックのテクスチャがまとめられていました。検索すればたくさん出てきますが、今回は
こちらのサイトから拝借いたしました。
Official Download: Terrain PNG Reborn V2.0.0, terrain.png for 1.17+

ダウンロードしたterrain.pngは、サイズ16x16の小さい画像が、16x16(=256)枚並んでいます。ブロックに貼り付けるために、256枚の画像に切り分けなければなりません。画像処理ソフトで手動でできますが、時間がかかるし楽しくありません。

import cv2

size = 256 // 16

# 画像読み込み
img = cv2.imread("data/terrain.png")

# img[top : bottom, left : right]
for i in range(16):
    for j in range(16):
        print(i, j)
        trim_img = img[i * size: (i + 1) * size, j * size: (j + 1) * size]
        cv2.imwrite(f"block_textures/{i}-{j}.png", trim_img)

こういうときこそ、Pythonの出番です。次のプログラムを実行すると、一瞬でトリミングが完了します。dataフォルダに置いた「terrain.png」を block_texturesフォルダに切り分けて、自動で名前をつけて保存することができました。

テクスチャを貼り付けたブロックを作る

texture_cube
tex1 = loadImage("block_textures/9-2.png")
tex2 = loadImage("block_textures/0-3.png")
tex3 = loadImage("block_textures/0-2.png")

beginShape(QUADS)
# +Z "front" face
texture(tex2)
vertex(0, -1, -1, 0, 0)
vertex(1, -1, -1, 1, 0)
vertex(1, 0, -1, 1, 1)
vertex(0, 0, -1, 0, 1)
# -Z "back" face
vertex(0, -1, 0, 0, 0)
vertex(1, -1, 0, 1, 0)
vertex(1, 0, 0, 1, 1)
vertex(0, 0, 0, 0, 1)
# +X "right" face
vertex(1, -1, 0, 0, 0)
vertex(1, -1, -1, 1, 0)
vertex(1, 0, -1, 1, 1)
vertex(1, 0, 0, 0, 1)
# -X "left" face
vertex(0, -1, 0, 0, 0)
vertex(0, -1, -1, 1, 0)
vertex(0, 0, -1, 1, 1)
vertex(0, 0, 0, 0, 1)
endShape()

beginShape(QUADS)
# -Y "top" face
texture(tex1)
vertex(1, -1, 0, 0, 0)
vertex(1, -1, -1, 1, 0)
vertex(0, -1, -1, 1, 1)
vertex(0, -1, 0, 0, 1)
endShape()

beginShape(QUADS)
# +Y "bottom" face
texture(tex3)
vertex(1, 0, 0, 0, 0)
vertex(1, 0, -1, 1, 0)
vertex(0, 0, -1, 1, 1)
vertex(0, 0, 0, 0, 1)
endShape()

Processing でテクスチャ付きのブロックを作るプログラムです。ブロックの6面がすべて同じなら話は簡単ですが、草ブロック(GRASS)などは、上面、側面、底面がそれぞれ別のテクスチャを貼り付けなければならないので、少し手間がかかります。
今回は、vertex を繋いて正方形を作り、その面にテクスチャを貼り付ける方法を採用しました。マイクラで馴染みの「草ブロック」を Processing に持ってくることができました。これで建築の準備ができました。

TextureCubeクラスを作成する

class TextureCube:
    def __init__(self, t1, t2=None, t3=None):
        self.tex1 = t1
        self.tex2 = t2 if t2 else t1
        self.tex3 = t3 if t3 else t1

    def draw(self):
        beginShape(QUADS)
        # +Z "front" face
        texture(self.tex2)
        vertex(0, -1, -1, 0, 0)
        vertex(1, -1, -1, 1, 0)
        vertex(1, 0, -1, 1, 1)
        vertex(0, 0, -1, 0, 1)
        # -Z "back" face
        vertex(0, -1, 0, 0, 0)
        vertex(1, -1, 0, 1, 0)
        vertex(1, 0, 0, 1, 1)
        vertex(0, 0, 0, 0, 1)
        # +X "right" face
        vertex(1, -1, 0, 0, 0)
        vertex(1, -1, -1, 1, 0)
        vertex(1, 0, -1, 1, 1)
        vertex(1, 0, 0, 0, 1)
        # -X "left" face
        vertex(0, -1, 0, 0, 0)
        vertex(0, -1, -1, 1, 0)
        vertex(0, 0, -1, 1, 1)
        vertex(0, 0, 0, 0, 1)
        endShape()

        beginShape(QUADS)
        # -Y "top" face
        texture(self.tex1)
        vertex(1, -1, 0, 0, 0)
        vertex(1, -1, -1, 1, 0)
        vertex(0, -1, -1, 1, 1)
        vertex(0, -1, 0, 0, 1)
        endShape()

        beginShape(QUADS)
        # +Y "bottom" face
        texture(self.tex3)
        vertex(1, 0, 0, 0, 0)
        vertex(1, 0, -1, 1, 0)
        vertex(0, 0, -1, 1, 1)
        vertex(0, 0, 0, 0, 1)
        endShape()

建築ではたくさんのブロックを使います。草ブロック、土ブロック(DIRT)、石ブロック(STONE)などは、貼り付けてあるテクスチャは違えど、ほとんど同じであることから、プログラムを簡単に描くために、Pythonの「クラス(Class)」という仕組みを使います。
クラスはかなり高度な仕組みであり(私にとってはですが)、理解が難しいかもしれません。しかし使いこなせばコードを簡略化するだけでなく、改造やバグ修正の際に大活躍してくれます。
詳しい説明は本職の方にお任せして、ここでは「クラスは設計図」と考えてください。「def __init__()」で初期化して、クラスを使える状態にします。「def draw()」でブロックの設置を実行します。このようにまとめておくと、建築のプログラムが簡単に書けるようになります。

ブロックのインスタンスを作成

# インスタンスを作成
tex_air = loadImage('block_textures/invisible.png')
tex_stone = loadImage('block_textures/0-1.png')
tex_grass_top = loadImage('block_textures/9-2.png')
tex_grass_side = loadImage('block_textures/0-3.png')
tex_dirt = loadImage('block_textures/0-2.png')
tex_cobblestone = loadImage('block_textures/1-0.png')
tex_planks = loadImage('block_textures/0-4.png')
tex_bedrock = loadImage('block_textures/1-1.png')
tex_sapling = loadImage('block_textures/0-15.png')
tex_water = loadImage('block_textures/9-0.png')
tex_lava = loadImage('block_textures/15-15.png')
tex_sand = loadImage('block_textures/1-2.png')
tex_gravel = loadImage('block_textures/0-0.png')
tex_gold_ore = loadImage('block_textures/2-0.png')
tex_iron_ore = loadImage('block_textures/2-1.png')
tex_coal_ore = loadImage('block_textures/2-2.png')
tex_log = loadImage('block_textures/1-4.png')
tex_leaves = loadImage('block_textures/4-5.png')
tex_sponge = loadImage('block_textures/1-2.png')
tex_glass = loadImage('block_textures/3-1.png')
tex_lapis_ore = loadImage('block_textures/10-0.png')
tex_lapis_block = loadImage('block_textures/9-0.png')
tex_dispenser = loadImage('block_textures/2-14.png')
tex_dispenser_top = loadImage('block_textures/3-14.png')
tex_sandstone = loadImage('block_textures/12-0.png')
tex_noteblock = loadImage('block_textures/4-10.png')
tex_bed = loadImage('block_textures/8-7.png')
tex_golden_rail = loadImage('block_textures/11-3.png')
tex_detector_rail = loadImage('block_textures/12-3.png')
tex_sticky_piston = loadImage('block_textures/6-10.png')
tex_piston_side = loadImage('block_textures/6-12.png')
tex_piston_bottom = loadImage('block_textures/6-13.png')
tex_web = loadImage('block_textures/0-11.png')
tex_tallgrass = loadImage('block_textures/2-7.png')
tex_deadbush = loadImage('block_textures/4-7.png')
tex_piston = loadImage('block_textures/6-12.png')
tex_piston_head = loadImage('block_textures/6-11.png')
tex_wool = loadImage('block_textures/11-0.png')
tex_orange_wool = loadImage('block_textures/13-2.png')
tex_magenta_wool = loadImage('block_textures/12-2.png')
tex_light_blue = loadImage('block_textures/11-2.png')
tex_yellow_wol = loadImage('block_textures/10-2.png')
tex_lime_wool = loadImage('block_textures/9-2.png')
tex_pink_wool = loadImage('block_textures/8-2.png')
tex_gray_wool = loadImage('block_textures/7-2.png')
tex_light_gray = loadImage('block_textures/14-1.png')
tex_cyan_wool = loadImage('block_textures/13-1.png')
tex_purple_wool = loadImage('block_textures/12-1.png')
tex_blue_wool = loadImage('block_textures/11-1.png')
tex_brown_wool = loadImage('block_textures/10-1.png')
tex_green_wool = loadImage('block_textures/9-1.png')
tex_red_wool = loadImage('block_textures/8-1.png')
tex_black_wool = loadImage('block_textures/7-1.png')
tex_piston_extension = loadImage('block_textures/invisible.png')
tex_yellow_flower = loadImage('block_textures/0-13.png')
tex_red_flower = loadImage('block_textures/0-12.png')
tex_brown_mushroom = loadImage('block_textures/1-13.png')
tex_red_mushroom = loadImage('block_textures/1-12.png')
tex_gold = loadImage('block_textures/1-7.png')
tex_iron = loadImage('block_textures/1-6.png')
tex_double_stone_slab = loadImage('block_textures/0-5.png')
tex_stone_slab = loadImage('block_textures/0-5-half.png')  # half
tex_brick_block = loadImage('block_textures/0-7.png')
tex_tnt = loadImage('block_textures/0-8.png')
tex_tnt_top = loadImage('block_textures/0-10.png')
tex_bookshelf = loadImage('block_textures/2-3.png')
tex_mossy_cobblestone = loadImage('block_textures/2-4.png')
tex_obsidian = loadImage('block_textures/2-5.png')
tex_torch = loadImage('block_textures/5-0.png')
# マイクラのBlock IDに合わせて、インスタンスを配置
blocks = [
    TextureCube(tex_air),  # 0
    TextureCube(tex_stone),  # 1
    TextureCube(tex_grass_top, tex_grass_side, tex_dirt),  # 2
    TextureCube(tex_dirt),  # 3
    TextureCube(tex_cobblestone),  # 4
    TextureCube(tex_planks),  # 5
    TextureCube(tex_sapling),  # 6
    TextureCube(tex_bedrock),  # 7
    TextureCube(tex_water),  # 8
    TextureCube(tex_water),  # 9
    TextureCube(tex_lava),  # 10
    TextureCube(tex_lava),  # 11
    TextureCube(tex_sand),  # 12
    TextureCube(tex_gravel),  # 13
    TextureCube(tex_gold_ore),  # 14
    TextureCube(tex_iron_ore),  # 15
    TextureCube(tex_coal_ore),  # 16
    TextureCube(tex_log),  # 17
    TextureCube(tex_leaves),  # 18
    TextureCube(tex_sponge),  # 19
    TextureCube(tex_glass),  # 20
    TextureCube(tex_lapis_ore),  # 21
    TextureCube(tex_lapis_block),  # 22
    TextureCube(tex_dispenser, tex_dispenser_top, tex_dispenser_top),  # 23
    TextureCube(tex_sandstone),  # 24
    TextureCube(tex_noteblock),  # 25
    TextureCube(tex_bed),  # 26
    TextureCube(tex_golden_rail),  # 27
    TextureCube(tex_detector_rail),  # 28
    TextureCube(tex_sticky_piston, tex_piston_side, tex_piston_bottom),  # 29
    TextureCube(tex_web),  # 30
    TextureCube(tex_tallgrass),  # 31
    TextureCube(tex_deadbush),  # 32
    TextureCube(tex_piston, tex_piston_side, tex_piston_bottom),  # 33
    TextureCube(tex_piston_head, tex_piston_side, tex_piston_bottom),  # 34
    [
        TextureCube(tex_wool),  # 35-0
        TextureCube(tex_orange_wool),  # 35-1
        TextureCube(tex_magenta_wool),  # 35-2
        TextureCube(tex_light_blue),  # 35-3
        TextureCube(tex_yellow_wol),  # 35-4
        TextureCube(tex_lime_wool),  # 35-5
        TextureCube(tex_pink_wool),  # 35-6
        TextureCube(tex_gray_wool),  # 35-7
        TextureCube(tex_light_gray),  # 35-8
        TextureCube(tex_cyan_wool),  # 35-9
        TextureCube(tex_purple_wool),  # 35-10
        TextureCube(tex_blue_wool),  # 35-11
        TextureCube(tex_brown_wool),  # 35-12
        TextureCube(tex_green_wool),  # 35-13
        TextureCube(tex_red_wool),  # 35-14
        TextureCube(tex_black_wool),  # 35-15
     ],
    TextureCube(tex_piston_extension),  # 36
    TextureCube(tex_yellow_flower),  # 37
    TextureCube(tex_red_flower),  # 38
    TextureCube(tex_brown_mushroom),  # 39
    TextureCube(tex_red_mushroom),  # 40
    TextureCube(tex_gold),  # 41
    TextureCube(tex_iron),  # 42
    TextureCube(tex_double_stone_slab),  # 43
    TextureCube(tex_stone_slab),  # 44
    TextureCube(tex_brick_block),  # 45
    TextureCube(tex_tnt, tex_tnt_top, tex_tnt_top),  # 46
    TextureCube(tex_bookshelf),  # 47
    TextureCube(tex_mossy_cobblestone),  # 48
    TextureCube(tex_obsidian),  # 49
    TextureCube(tex_torch),  # 50
]

マイクラの[Block ID]と同じ並びで、TextureCubeクラスから作ったインスタンス(Instance)を、変数blocks に収納しています。「インスタンスはクラスから作成される製品のようなもの」と考えてください。
今回作った「ブロックのインスタンス」は draw()を実行すると、ブロックを置いてくれる便利な製造機です。
(このコード書くの大変でした。テクスチャの指定が間違っているところがあるかもしれません。コメントで教えてくださると助かります。)

クラフトデータを作成

# クラフトデータを作成
heights = [0, 4, 5, 6, 7, 8, 8, 7, 6, 5, 4, 0]
roof_id = 2
body_id = 5
pillar_id = 12
for i in range(12):
    for j in range(10):
        roof_height = heights[i]
        if (i == 1 and j == 0) or (i == 1 and j == 9) or (i == 10 and j == 0) or (i == 10 and j == 9):
            block_id = pillar_id
        else:
            block_id = body_id
        if roof_height > 0:
            for k in range(roof_height):
                block_positions.append((i, k, j, block_id))
            else:
                block_positions.append((i, k + 1, j, roof_id))
        else:
            block_positions.append((i, 3, j, roof_id))

建築用のデータ作成の部分です。
for文を使って「マイクラ風の家」の3次元データを作成しました。豆腐ハウスは簡単ですが、頑張って「三角屋根の家」作りに挑戦しました。家の四隅は柱として、別のブッロクを選んで装飾しています。変数block_positions に「ブロックの位置と種類」のデータをリストとしてまとめました。

マイクラ風の家を建築する

# クラフト
noFill()
for bp in block_positions:
    pushMatrix()
    translate(bp[0] + offset_x, -(bp[1] + offset_y), -(bp[2] + offset_z))
    if bp[4:]:
        blocks[bp[3]][bp[4]].draw()
    else:
        blocks[bp[3]].draw()
    popMatrix()
popMatrix()

実際に建築している部分です。この部分がこんなに短くなっているのは、先ほど述べたクラスの仕組みを使ったからです。クラスを使っていれば、今後ブロックの種類を増やしたり、ブロックの機能を増やしたりするのも簡単にできるようになります。

Minecraftic House

プログラムを実行すると、マイクラ風の家が出来上がりました。なかなかの出来で自己満足しています(笑)
「クラフトデータを作成」の部分を書き換えると、様々な建築を行うことができます。手動では難しい円形や幾何学模様も思いのままです。
Processing x Minecraft = Processincraft の世界にようこそ!


前の記事
Processing でボクセルアート② 変形させて遊んでみよう!
次の記事
Processing でマイクラ② マイクラ建築を自動化してみよう

その他のタイトルはこちら


この記事が気に入ったらサポートをしてみませんか?