BLOG

2016.10.14Minecraftでプログラミング

Python プログラムとマインクラフトでゲームを作ろう

whac a block

はじめに


今回は、マインクラフトを使ったちょっとしたゲームを作ってみます。お子様と一緒に作っていただいてもよし、自分で作ってみて遊んでいただいてもよしです。まずは試してみていただければと思います。

元ネタは、Raspberry Pi のドキュメントにある、”WHAC-A-BLOCK” です。

https://www.raspberrypi.org/learning/minecraft-whac-a-block-game/worksheet/

CC-BY-SAとのことですので、この解説記事も同条件でご利用ください。

WHAC-A-BLOCK について


Whac a Mole がもともとのゲームで、いわゆる「もぐらたたき」です。これをマインクラフト上で遊ぶために、モグラの変わりにブロックにしているのが、Whac-a-block です。3×3の9マスのブロックがあり、そのブロックがランダムに違う色のブロックに置き換わっていきます。置き換わったブロックをモグラと見立てて、剣で叩くと得点が入るというゲームです。剣で叩くことでブロックがもとの色に戻ります。すべてのブロックの色が変わってしまったら負けです。

whac a block

アルゴリズム


では、具体的にどのように実現するか見ていきましょう。
1.プレイヤの場所を取得する 
2.ブレイヤの場所の付近にブロックのカベを置く。
3.変化したブロックの数が9よりも大きくなるまで繰り返す
  4.変化したブロックの数を1増やす
  5.ブロックの色をランダムにひとつかえる
  6.叩いたブロックを取得。
  7.叩いたブロックが変化したブロックであったら
    8.ブロックを元に戻す
    9.変化したブロックの数を1減らす
    10.ポイントを1増やす
11.すべてのブロックが変化してしまった時点で得点を表示して終了

 

1,2のプレイヤの場所をとる、ブロックのカベを置くのは今までの解説記事でも扱っているのですぐわかりそうです。ここで不明なのが、叩いたブロックを取得する方法とブロックの種類を調べる方法です。

叩いたブロックを取得する方法


コマンドで次のコマンドが用意されています。
mc.events.pollBlockHits()

これを使うと、剣で叩いたブロックの集合を取得することができます。(剣で叩かないとイベントが取得できません。)取得したevent の中に、位置情報があり、x, y, z の各座標が格納されています。これを使って叩いたブロックの位置を取ることができます。ちなみに、event に含まれている情報は、次のとおりです。

  • type イベントの種類

  • pos 位置。Pos.x, pos.y, pos.z で取得できる

  • face ブロックの面の位置

  • entityId イベントを起こしたプレイヤのID


ブロックの種類を調べる方法


7の部分で叩いたブロックが変頭ブロックかどうかを調べます。このブロックの種類を調べる方法もコマンドが用意されています。

mc.getBlock(x, y, z)

getBlockを用いることで座標(x, y, z) にあるブロックの種類を取得することができます。また、getBlockWithData を用いた場合は、blockのオブジェクトを取得できます。オブジェクトには種類と属性(羊毛ならその色など)を取得することができます。

初期設定


では、最初の部分から見ていきましょう。
import mcpi.minecraft as minecraft
import mcpi.block as block
import random
import time

最初の部分では、使用するライブラリを宣言しています。mcpi がpython プログラムからマインクラフトに通信をするためのライブラリです。random ライブラリは、ランダムな値を出力するために使います。また、timeライブラリは時間を取得したり、待ち時間をいれたりするときに使用します。
mc = minecraft.Minecraft.create()
mc.postToChat("Minecraft Whac-a-Block")
pos = mc.player.getTilePos()

最初の文は、Minecraftと接続するためのクラスのインスタンスを生成しています。そして、プレイヤの現在位置を取得します。
mc.setBlocks(pos.x - 1, pos.y, pos.z + 3,
pos.x + 1, pos.y + 2, pos.z + 3,
block.STONE.id)
mc.postToChat("Get ready ...")
time.sleep(2)
mc.postToChat("Go")
blocksLit = 0
points = 0

そして、3×3のブロックを設置します。setBlocksは前回までの記事でも取り扱っているので説明は省略します。その後、メッセージを表示して、変数の初期化をしています。blocksLit は、変化したブロックの数、points は得点になります。

判定部分


while blocksLit < 9:
time.sleep(0.2)
blocksLit = blocksLit + 1

ここは、変化したブロックの数が9よりも小さいかぎり繰り返すための while 文が使われています。その後、sleep 処理で 0.2秒待ちます。そして、変化したブロックの数を一つ増やします。
	lightCreated = False
while not lightCreated:
xPos = pos.x + random.randint(-1,1)
yPos = pos.y + random.randint(0,2)
zPos = pos.z + 3
if mc.getBlock(xPos, yPos, zPos) == block.STONE.id:
mc.setBlock(xPos, yPos, zPos, block.GLOWSTONE_BLOCK.id)
lightCreated = True

次に、ブロックを一つランダムに選び、変更する処理を行います。ランダムに選ぶために、random.randint というメソッドを使ってランダムな値を取得します。下限と上限を指定することで、その間の値の中でランダムな整数を生成することができます。if mc.getBlock(xPos, yPos, zPos) == block.STONE.id の部分は、ランダムに選択したブロックが変更されていないブロックかどうかを調べています。変更されていなければ、そのブロックを変更し、次の処理に移ります。変更されていた場合は、再度ランダムにブロックを選択して、変更できるまで同じ処理をくりかえします。
	for hitBlock in mc.events.pollBlockHits():
if mc.getBlock(hitBlock.pos.x, hitBlock.pos.y, hitBlock.pos.z) == block.GLOWSTONE_BLOCK.id:
mc.setBlock(hitBlock.pos.x, hitBlock.pos.y, hitBlock.pos.z, block.STONE.id)
blocksLit = blocksLit - 1
points = points + 1

ここでは、剣でたたいたブロックの集合を取得し、それぞれに対して処理をします。ブロックが変更されていたブロックであった場合に、setBlockでもとに戻して、ブロックのカウントを一つ減らす。そして、ポイントを一つ増やす処理をします。
mc.postToChat("Game Over - points = " + str(points))

while文を抜けたあとにある、最後の1文は、ゲームオーバーのときのメッセージです。取得した得点を表示して終了です。

少し改良


これで以上ですが、このままだと元ネタのままなので、少しアレンジをしてみようかと思います。このゲームに味付けをするとしたら、段階的に難しくしていく方法を考えることが良さそうです。段階的に難しくするアイディアとして次のが浮かびました。

1.スリープの時間を徐々に短くする


これは、いわゆるレベルを設定して、レベルが上がるごとに待ち時間を減らす処理を加えます。level という変数をつくってその変数が points が 10ポイント、20ポイント、30ポイント、40ポイントになるごとに待ち時間を 0.2 秒ずつ減らすようにしてみました。
level = 0
while blocksLit < 9:
time.sleep(1 - 0.2*level)
if points == 10:
level += 1
elif points == 20:
level += 1
elif points == 30:
level += 1
elif points == 40:
level += 1

単純ですが、徐々にスピードが上がっているような感じになります。

2.間違えたらペナルティ


間違って変化していないブロックをたたいた場合は、点数を減点させてみましょう。がむしゃらにたたいた場合点数がでない事になります。これは、ポイントの判定をしている部分で、変化したブロックではない場合に減点する処理を入れます。
	for hitBlock in mc.events.pollBlockHits():
if mc.getBlock(hitBlock.pos.x, hitBlock.pos.y, hitBlock.pos.z) == block.GLOWSTONE_BLOCK.id:
mc.setBlock(hitBlock.pos.x, hitBlock.pos.y, hitBlock.pos.z, block.STONE.id)
blocksLit = blocksLit - 1
points = points + 1
else:
points -= 1

全文を載せると次のようになります。
import mcpi.minecraft as minecraft
import mcpi.block as block
import random
import time
mc = minecraft.Minecraft.create()
mc.postToChat("Minecraft Whac-a-Block")
pos = mc.player.getTilePos()
mc.setBlocks(pos.x - 1, pos.y, pos.z + 3,
pos.x + 1, pos.y + 2, pos.z + 3,
block.STONE.id)
mc.postToChat("Get ready ...")
time.sleep(2)
mc.postToChat("Go")
blocksLit = 0
points = 0
level = 0
while blocksLit < 9:
time.sleep(1 - 0.2*level)
if points == 10:
level += 1
elif points == 20:
level += 1
elif points == 30:
level += 1
elif points == 40:
level += 1
blocksLit = blocksLit + 1
lightCreated = False
while not lightCreated:
xPos = pos.x + random.randint(-1,1)
yPos = pos.y + random.randint(0,2)
zPos = pos.z + 3
if mc.getBlock(xPos, yPos, zPos) == block.STONE.id:
mc.setBlock(xPos, yPos, zPos, block.GLOWSTONE_BLOCK.id)
lightCreated = True
for hitBlock in mc.events.pollBlockHits():
if mc.getBlock(hitBlock.pos.x, hitBlock.pos.y, hitBlock.pos.z) == block.GLOWSTONE_BLOCK.id:
mc.setBlock(hitBlock.pos.x, hitBlock.pos.y, hitBlock.pos.z, block.STONE.id)
blocksLit = blocksLit - 1
points = points + 1
else:
points -= 1
mc.postToChat("Game Over - points = " + str(points))

おわりに


今回は、もぐらたたきゲームをつくってみました。地味なゲームなのですが、以外にやってみるとそこまで高得点を出すのは難しいことがわかるかと思います。ほかのアイディアを考えていただき、(ゲームの中なので)ゲームを作ってみても良いかもしれません。Raspberry Pi で試していただくか、PC版のマインクラフトでぜひお試しください。また、違う内容で書いてみたいと思います。お楽しみに。




東京都文京区小石川で小学生、中学生、高校生を対象としたプログラミング&ロボット教室を開校しています。コース概要のページで説明しております。創造性や協調性などこれからの時代に必要となる素養を育てるコースです。ご興味ありましたらぜひお問い合わせください。

お問い合わせはこちら!


問い合わせをする

INFORMATION無料体験会・ワークショップ・ご案内

     

秋の無料体験会

無料体験会を実施します。ぜひ一度ご体験ください!

プログラミング&ロボット教室 授業運営パッケージ

弊社で実施している授業を取り入れたい方々向けに提供しています。