2016.09.20Minecraftでプログラミング
改良版 Python プログラミングでマインクラフトの家を作る
はじめに
みなさま、こんにちは。前回、python プログラムから、マインクラフトの家を作る記事を書いたのですが、どうも出来がイマイチで反省点が多かったため、作りを見直してみることにしました。同じ家ですが、プログラムの作り方を変えて、今一度挑戦してみようと思います。
前回製作時の反省点
前回のプログラムの反省点をまずは書いていきます。最初の記事ではそもそも記述が少し間違っていたのもあり、ご迷惑をおかけしたと反省しておりますが、それに加えて、座標の扱いが場当たり的で、可読性が悪いところが、問題点だと考えています。要するに、パッと関数を見たときに、どの地点の処理をしているのかがわかりにくいため、書いていくとわからなくなってしまいやすい、ということです。
今回の改良点
そこで、今回の制作では次の改良を加えることにします。
- ポイントをクラスとして定義。ポイントをひとつの値として扱うことで、どの地点の処理をしているかを明確化します。
- ポイントのネーミングルールを準備。ポイントは、3次元で扱っていくと多くの点が存在することになるため、数がたくさん必要になってくることから、どのあたりのポイントかが名前から類推できるようにします。
クラスとは?
先ほどの改良点のところで、「クラスを定義」とさらっと書きましたが、クラスとは、ある物事や事象を抽象化した型のことです。プログラミング言語では、あらかじめ用意された型があります。例えば、整数や小数といった数値を表す型や、文字列を表す型などがあります。これらの型はあらかじめ用意されているので、特に定義なしに使用できますが、今回の場合のように、建物を建てる地点(ポイント)を表現する型は存在しません。そのため、それを定義してあげることで、プログラムの中で値として使うことができるようにします。
ポイントのクラス
ポイントのクラスを準備することにします。ポイントで重要なのは、x, y, z の3つの座標をひとまとめに扱うことです。そのため、クラスの定義では、ポイントのx, y, z の座標を表すための変数を入れることにします。そして、初期化時の処理として、座標の初期値を与えることにします。最初にある、def __init__ という関数が初期化時の関数です。Python プログラミングにおいては、クラスを定義する場合に、必ず __init__ 関数を用意する必要があります。クラスを実際使えるように変数として宣言する場合に、この関数で変数を作る処理を規定します。今回の例では、座標の値を書き込んでいます。それに加えて、基本演算である、= (__eq__ : 等しいかどうかを調べる関数)、!= (__ne__ :等しくおないかどうかを調べる関数)を定義します。これで、例えば、p1というポイントと、p2というポイントがあったときに、p1 とp2が等しいことを調べるためには、次のような形で記述することができます。
if p1 = p2:
# …
さらに、地点p と同じ座標を持つ変数を作るための関数 create、地点を移動させるための関数 move を用意します。実際のコードは次の通りになります。
#----------------------------------------
# point class
#----------------------------------------
class Point:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
def __eq__(self, p):
if (self.x == p.x and self.y == p.y and self.z == p.z):
return True
else:
return False
def __ne__(self, p):
if (self.x == p.x and self.y == p.y and self.z == p.z):
return False
else:
return True
def create(self):
result = Point(self.x, self.y, self.z)
return result
def move(self, x, y, z):
self.x += x
self.y += y
self.z += z
ポイントを指定してブロックを置くための関数
次に、ポイントを指定して、ブロックを置くための関数を用意します。ライブラリに元々用意されている関数の場合、ポイントではなく、3点の座標を指定する方法でした。今回は、可読性を上げるためにポイントを指定することでブロックを置くことができる関数を用意します。1ブロックだけ置く場合と、2地点を指定し、その地点で囲まれた中を埋める関数を用意します。
#----------------------------------------
# set block using a point
#----------------------------------------
def setBlockUsingPoint(p1, blockId, blockType=0):
mc.setBlock(p1.x, p1.y, p1.z, blockId, blockType)
#----------------------------------------
# set blocks using points
#----------------------------------------
def setBlocksUsingPoints(p1, p2, blockId, blockType=0):
mc.setBlocks(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, blockId, blockType)
ポイントの名前付けルール
それでは、実際にポイントの名前付けルールを考えます。図に示すように、それぞれのポイントに名前をつけます。柱を置くポイントそれぞれに、p1からp6までの名前をつけています。ただ、この名前のつけ方の場合、1階、2階、屋根と高さが違うポイントでその都度名前を変えないといけないことから、名前のルールとしては次のようにします。
- xとz座標が同じポイントは、下記の図にあるように最初の2文字はpの後に数字で表す。
- 続いてy軸方向の階数をつける。1階ならば1、2階ならば2、屋根ならばr とする。
- 最後にyが小さい順に数字をつける
このルールによって完全に場所がわかるわけではないですが、名前を見るとおおよその位置が想像できるようになるはずです。今回は、この方針で進めていきます。
1階部分の記述
では、1階部分の記述からスタートします。前回の記事の内容を見ていただければ分かりますが、次のような流れで準備していきました。
- 最初に基礎部分のブロックを作る
- 柱、梁を作る
- 壁を作る
最初の基礎ブロックを作る部分は、それぞれのポイント間にブロックを置くために、下記のような記述をしていました。
mc.setBlocks(x, y, z, x+width, y+height, z, block.STONE.id) # line 1
それを、ポイントを使ってブロックを置くコードに置き換えます。
setBlocksUsingPoints(p111, p211, block.STONE.id) # p11 to p21
ぱっと見でどのポイント間でのブロックの配置かがよくわかるようになったと思います。次に柱と梁です。柱と梁も先ほどの基礎のブロックと考え方は同じです。2地点をそれぞれ定義して、2地点感を木のブロックで埋めることでできます。
エントランスに関しても、同じようにポイントを指定する形に修正をしました。ドアとゲートの位置をそれぞれ、p5, p6 の位置から作成して、ドアとゲートを作成しています。
#----------------------------------------
# Make entrance
#----------------------------------------
def makeEntrance(p511, p611):
# set door
door11 = p611.create()
door11.move(0, 0, 1)
door12 = p611.create()
door12.move(0, 1, 1)
door21 = p511.create()
door21.move(0, 0, -1)
door22 = p511.create()
door22.move(0, 1, -1)
setBlockUsingPoint(door11, block.DOOR_WOOD.id, 0x10)
setBlockUsingPoint(door12, block.DOOR_WOOD.id, 0x18)
setBlockUsingPoint(door21, block.DOOR_WOOD.id, 0x10)
setBlockUsingPoint(door22, block.DOOR_WOOD.id, 0x19)
# set entrance gate
gate11 = p611.create()
gate11.move(-1, 0, 0)
gate12 = p611.create()
gate12.move(-1, 2, 0)
gate21 = p511.create()
gate21.move(-1, 0, 0)
gate22 = p511.create()
gate22.move(-1, 2, 0)
setBlocksUsingPoints(gate11, gate12, block.STONE.id)
setBlocksUsingPoints(gate21, gate22, block.STONE.id)
setBlocksUsingPoints(gate12, gate22, block.STONE.id)
次に壁です。壁は、2本の柱の位置の間を指定された高さの分だけ全て埋める関数を追加します。指定した2地点がp1, p2 とした場合に、p1とp2を結ぶ線がx 軸方向か、z軸方向かで壁を立てる向きが変わるので、最初にp1, p2のx、zの値をそれぞれ比べて、どちらの方向かを見極めます。そして、柱の間に壁が来るようにsetBlocksを使ってブロックを埋めます。
#----------------------------------------
# Make wall
# p1.y = p2.y
#----------------------------------------
def makeWall(p1, p2, height):
height -= 1
if p1.x < p2.x:
# x axis
mc.setBlocks(p1.x+1, p1.y, p1.z, p2.x-1, p2.y+height, p2.z, block.WOOD_PLANKS.id, block.WOOD_PLANKS_BIRCH.data)
elif p1.x > p2.x:
# x axis
mc.setBlocks(p2.x+1, p2.y, p2.z, p1.x-1, p1.y+height, p1.z, block.WOOD_PLANKS.id, block.WOOD_PLANKS_BIRCH.data)
elif p1.z < p2.z:
# z axis
mc.setBlocks(p1.x, p1.y, p1.z+1, p2.x, p2.y+height, p2.z-1, block.WOOD_PLANKS.id, block.WOOD_PLANKS_BIRCH.data)
elif p1.z > p2.z:
# z axis
mc.setBlocks(p2.x, p2.y, p2.z+1, p1.x, p1.y+height, p1.z-1, block.WOOD_PLANKS.id, block.WOOD_PLANKS_BIRCH.data)
else:
# error
print("error")
そして、天井を入れます。2点を指定して、その間を埋める形になります。また、1階部分には以前は床を張っていなかったのですが、床をはるコードを追加しました。基本的には、天井と同じです。
#----------------------------------------
# Make ceiling
#----------------------------------------
def makeCeiling(p1, p2):
p1.move(1, 0, 1)
p2.move(-1, 0, -1)
setBlocksUsingPoints(p1, p2, block.WOOD_PLANKS.id, block.WOOD_PLANKS_BIRCH.data)
2階のコードに関しては、1階部分と基本的に同じです。また、屋根の部分は、複雑な形状のため、ポイントを指定する場合とそうでない場合で差が出ないので、基本的にはそのままとしました。
最後に、部屋の中の明かりを追加します。これは、それぞれの柱があるポイントにて、高さを2ブロック床から上げたところに、設置していきます。例では、1階と2階の部分に置くようにしました。明かりを入れたため、夜でも明るくなりました。
改良版の家のコードは以下のようになります。
import mcpi.minecraft as minecraft
import mcpi.block as block
import server
import sys
#----------------------------------------
# point class
#----------------------------------------
class Point:
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
def __eq__(self, p):
if (self.x == p.x and self.y == p.y and self.z == p.z):
return True
else:
return False
def __ne__(self, p):
if (self.x == p.x and self.y == p.y and self.z == p.z):
return False
else:
return True
def create(self):
result = Point(self.x, self.y, self.z)
return result
def move(self, x, y, z):
self.x += x
self.y += y
self.z += z
#----------------------------------------
# set block using a point
#----------------------------------------
def setBlockUsingPoint(p1, blockId, blockType=0):
mc.setBlock(p1.x, p1.y, p1.z, blockId, blockType)
#----------------------------------------
# set blocks using points
#----------------------------------------
def setBlocksUsingPoints(p1, p2, blockId, blockType=0):
mc.setBlocks(p1.x, p1.y, p1.z, p2.x, p2.y, p2.z, blockId, blockType)
#----------------------------------------
# Flat space
#----------------------------------------
def flatspace(x, y, z, width, height, depth):
width -= 1
height -= 1
depth -= 1
# BlockId 0 is a space (no block).
mc.setBlocks(x, y, z, x+width, y+height, z+depth, block.AIR.id)
#----------------------------------------
# Make wall
# p1.y = p2.y
#----------------------------------------
def makeWall(p1, p2, height):
height -= 1
if p1.x < p2.x:
# x axis
mc.setBlocks(p1.x+1, p1.y, p1.z, p2.x-1, p2.y+height, p2.z, block.WOOD_PLANKS.id, block.WOOD_PLANKS_BIRCH.data)
elif p1.x > p2.x:
# x axis
mc.setBlocks(p2.x+1, p2.y, p2.z, p1.x-1, p1.y+height, p1.z, block.WOOD_PLANKS.id, block.WOOD_PLANKS_BIRCH.data)
elif p1.z < p2.z:
# z axis
mc.setBlocks(p1.x, p1.y, p1.z+1, p2.x, p2.y+height, p2.z-1, block.WOOD_PLANKS.id, block.WOOD_PLANKS_BIRCH.data)
elif p1.z > p2.z:
# z axis
mc.setBlocks(p2.x, p2.y, p2.z+1, p1.x, p1.y+height, p1.z-1, block.WOOD_PLANKS.id, block.WOOD_PLANKS_BIRCH.data)
else:
# error
print("error")
#----------------------------------------
# Make a base for house
#----------------------------------------
def makeBase(p11, p21, p31, p41):
setBlocksUsingPoints(p11, p21, block.STONE.id) # p11 to p21
setBlocksUsingPoints(p21, p31, block.STONE.id) # p21 to p31
setBlocksUsingPoints(p31, p41, block.STONE.id) # p31 to p41
setBlocksUsingPoints(p41, p11, block.STONE.id) # p41 to p11
#----------------------------------------
# Make Joinst, Purlin and walls
#----------------------------------------
def makeJoistAndPurlinAndWalls(p112, p212, p312, p412, p512, p612, height):
#height -= 1
#
p121 = p112.create()
p121.move(0, height, 0)
p221 = p212.create()
p221.move(0, height, 0)
p321 = p312.create()
p321.move(0, height, 0)
p421 = p412.create()
p421.move(0, height, 0)
p521 = p512.create()
p521.move(0, height, 0)
p621 = p612.create()
p621.move(0, height, 0)
# Make purlin
setBlocksUsingPoints(p112, p121, block.WOOD.id)
setBlocksUsingPoints(p212, p221, block.WOOD.id)
setBlocksUsingPoints(p312, p321, block.WOOD.id)
setBlocksUsingPoints(p412, p421, block.WOOD.id)
setBlocksUsingPoints(Point(p512.x, p512.y-1, p512.z), p521, block.WOOD.id)
setBlocksUsingPoints(Point(p612.x, p612.y-1, p612.z), p621, block.WOOD.id)
# Make joist
setBlocksUsingPoints(p121, p221, block.WOOD.id)
setBlocksUsingPoints(p221, p321, block.WOOD.id)
setBlocksUsingPoints(p321, p421, block.WOOD.id)
setBlocksUsingPoints(p421, p121, block.WOOD.id)
# Make walls
wallheight = height
makeWall(p112, p212, wallheight)
makeWall(p212, p312, wallheight)
makeWall(p312, p412, wallheight)
makeWall(p412, p512, wallheight)
makeWall(p612, p112, wallheight)
#----------------------------------------
# Make entrance
#----------------------------------------
def makeEntrance(p511, p611):
# set door
door11 = p611.create()
door11.move(0, 0, 1)
door12 = p611.create()
door12.move(0, 1, 1)
door21 = p511.create()
door21.move(0, 0, -1)
door22 = p511.create()
door22.move(0, 1, -1)
setBlockUsingPoint(door11, block.DOOR_WOOD.id, 0x10)
setBlockUsingPoint(door12, block.DOOR_WOOD.id, 0x18)
setBlockUsingPoint(door21, block.DOOR_WOOD.id, 0x10)
setBlockUsingPoint(door22, block.DOOR_WOOD.id, 0x19)
# set entrance gate
gate11 = p611.create()
gate11.move(-1, 0, 0)
gate12 = p611.create()
gate12.move(-1, 2, 0)
gate21 = p511.create()
gate21.move(-1, 0, 0)
gate22 = p511.create()
gate22.move(-1, 2, 0)
setBlocksUsingPoints(gate11, gate12, block.STONE.id)
setBlocksUsingPoints(gate21, gate22, block.STONE.id)
setBlocksUsingPoints(gate12, gate22, block.STONE.id)
#----------------------------------------
# Make window
#----------------------------------------
def makeWindow(p, height):
height -= 1
mc.setBlocks(p.x, p.y, p.z, p.x, p.y+height, p.z, block.WOOD.id)
mc.setBlocks(p.x, p.y, p.z+1, p.x, p.y+height, p.z+2, block.GLASS_PANE.id)
mc.setBlocks(p.x, p.y, p.z+3, p.x, p.y+height, p.z+3, block.WOOD.id)
#----------------------------------------
# Make ceiling
#----------------------------------------
def makeCeiling(p1, p2):
p1.move(1, 0, 1)
p2.move(-1, 0, -1)
setBlocksUsingPoints(p1, p2, block.WOOD_PLANKS.id, block.WOOD_PLANKS_BIRCH.data)
#----------------------------------------
# Make floor
#----------------------------------------
def makefloor(p1, p2):
p1.move(1, 0, 1)
p2.move(-1, 0, -1)
setBlocksUsingPoints(p1, p2, block.WOOD_PLANKS.id)
#----------------------------------------
# Make torch
#----------------------------------------
def makeTorch(p1, p2, p3, p4, p5, p6):
# Attached torch
for i in range(2):
mc.setBlock(p1.x+1, p1.y+2+i*4, p1.z+1, block.TORCH.id)
mc.setBlock(p2.x-1, p2.y+2+i*4, p2.z+1, block.TORCH.id)
mc.setBlock(p3.x-1, p3.y+2+i*4, p3.z-1, block.TORCH.id)
mc.setBlock(p4.x+1, p4.y+2+i*4, p4.z-1, block.TORCH.id)
mc.setBlock(p5.x+1, p5.y+2+i*4, p5.z, block.TORCH.id)
mc.setBlock(p6.x+1, p6.y+2+i*4, p6.z, block.TORCH.id)
#----------------------------------------
# Make first floor
#----------------------------------------
def makeFirstFloor(p111, p211, p311, p411, p511, p611):
height = 2
mc.postToChat("Make base.")
makeBase(p111, p211, p311, p411)
mc.postToChat("Make Joinst, Purlin and Walls.")
# Make points for joinst, purlin
# points on the base
p112 = p111.create()
p112.move(0, 1, 0)
p212 = p211.create()
p212.move(0, 1, 0)
p312 = p311.create()
p312.move(0, 1, 0)
p412 = p411.create()
p412.move(0, 1, 0)
p512 = p511.create()
p512.move(0, 1, 0)
p612 = p611.create()
p612.move(0, 1, 0)
makeJoistAndPurlinAndWalls(p112, p212, p312, p412, p512, p612, height)
# Make entrance
mc.postToChat("Make entrance.")
makeEntrance(p511, p611)
# Make window
mc.postToChat("Make window.")
pwindow = p112.create()
pwindow.move(0, 0, 3)
makeWindow(pwindow, 2)
pwindow.move(0, 0, 10)
makeWindow(pwindow, 2)
# Make ceiling
mc.postToChat("Make ceiling.")
makeCeiling(Point(p112.x, p112.y+height, p112.z), Point(p312.x, p312.y+height, p312.z))
# Make floor
makefloor(Point(p111.x, p111.y-1, p111.z), Point(p311.x, p311.y-1, p311.z))
#----------------------------------------
# Make second floor
#----------------------------------------
def makeSecondFloor(p122, p222, p322, p422, p522, p622):
height = 3
mc.postToChat("Make Joinst, Purlin and Walls.")
makeJoistAndPurlinAndWalls(p122, p222, p322, p422, p522, p622, height)
# Make window
mc.postToChat("Make window.")
pwindow = p122.create()
pwindow.move(0, 1, 3)
makeWindow(pwindow, height-1)
pwindow.move(0, -1, 5)
makeWindow(pwindow, height)
pwindow.move(0, 1, 5)
makeWindow(pwindow, height-1)
# Make ceiling
mc.postToChat("Make ceiling.")
p1r1 = p122.create()
p1r1.move(0, height, 0)
p3r1 = p322.create()
p3r1.move(0, height, 0)
makeCeiling(p1r1, p3r1)
#----------------------------------------
# Make roof
#----------------------------------------
def makeRoof(x, y, z):
# Walls for roof
mc.postToChat("Make walls.")
for i in range(6):
mc.setBlocks(x, y+i, z+1+i, x, y+i, z+19-i, block.WOOD_PLANKS.id, block.WOOD_PLANKS_BIRCH.data)
mc.setBlocks(x+13, y+i, z+1+i, x+13, y+i, z+19-i, block.WOOD_PLANKS.id, block.WOOD_PLANKS_BIRCH.data)
# Left side of roof bricks
mc.postToChat("Make roofs.")
for i in range(7):
mc.setBlocks(x-1, y+i, z+i, x+14, y+i, z+i, 108, 2)
# Right side of roof bricks
for i in range(7):
mc.setBlocks(x-1, y+i, z+19-i, x+14, y+i, z+19-i, 108, 3)
# Top side of roof bricks
mc.setBlocks(x-1, y+6, z+7, x+14, y+6, z+12, block.BRICK_BLOCK.id)
# Window
mc.postToChat("Make a window.")
makeWindow(Point(x, y, z+8), 3)
#----------------------------------------
# Main function
#----------------------------------------
if __name__ == "__main__":
mc = minecraft.Minecraft()
# Get current position
x, y, z = mc.player.getPos()
fieldpoint1 = Point(x, y-10, z)
fieldpoint2 = Point(x+30, y, z+30)
setBlocksUsingPoints(fieldpoint1, fieldpoint2, block.GRASS.id)
# Make a flat space
mc.postToChat("Make a flat space.")
flatspace(x, y, z, 30, 20, 30)
# define points
width = 14
depth = 20
baseHeight = 4
p111 = Point(x+4, y, z+4)
p211 = Point(p111.x+width-1, p111.y, p111.z)
p311 = Point(p111.x+width-1, p111.y, p111.z+depth-1)
p411 = Point(p111.x, p111.y, p111.z+depth-1)
p511 = p111.create()
p511.move(0, 0, 11)
p611 = p111.create()
p611.move(0, 0, 8)
# Make first floor
makeFirstFloor(p111, p211, p311, p411, p511, p611)
# Make second floor
p122 = p111.create()
p122.move(0, baseHeight, 0)
p222 = p211.create()
p222.move(0, baseHeight, 0)
p322 = p311.create()
p322.move(0, baseHeight, 0)
p422 = p411.create()
p422.move(0, baseHeight, 0)
p522 = p511.create()
p522.move(0, baseHeight, 0)
p622 = p611.create()
p622.move(0, baseHeight, 0)
makeSecondFloor(p122, p222, p322, p422, p522, p622)
# Make roof
makeRoof(p111.x, p111.y+baseHeight*2, p111.z)
makeTorch(p111, p211, p311, p411, p511, p611)
まとめ
いかがでしたでしょうか。少しは見た目がすっきりしたかと思います。前回の記事がエラーが多い内容でしたので、改めて書きなおしてみました。ご意見いただければ幸いです。
東京都文京区小石川で小学生、中学生、高校生を対象としたプログラミング&ロボット教室を開校しています。コース概要のページで説明しております。創造性や協調性などこれからの時代に必要となる素養を育てるコースです。ご興味ありましたらぜひお問い合わせください。