【Kivy】Pythonで落ちもの系ゲーム開発(テトリス風)Part7
こんにちは、にわこまです。
今回は、落ちてくるミノの描画更新を行う関数と盤面のミノを描画する関数を作成します。FieldWidgetにおいて描画を更新する関数はこれで最後になります。
誤字脱字など何かございましたらご連絡お願いいたします。
スポンサードサーチ
落ちもの系ゲーム開発Part7
Part1で紹介した、今回作成するゲームの概要を改めて説明します。
今回開発するゲームは、テトリスに似ています。テトリスのルールは、以下のような7つのミノと呼ばれるブロックを積み重ねていき、横一列をミノで満たしたらその行が消され得点します。
作るゲームの概要
テトリスではミノが消される条件は、「横一列にミノがいっぱいになったら」ですが、今回開発するゲームでは「隣り合うブロックが10になったら」消されるというものです。
ゆえに、ブロック1つひとつには1~9までの数字が代入されます。
以下のような場合、オレンジ色の線で囲まれたブロックが消されます。
さらに、ブロックが消されたら、その列上で消されたブロックの座標より高い位置にあるブロックは消されたブロックの数だけ下がります。(左図から右図のようになります。)
開発環境
OS : Windows10
Python Version : 3.7
Kivy Version : 1.11.1
Numpy Version : 1.18.1
開発手順
1.全てのミノに共通する関数を作成します。(共通の初期値、動作、関数)
2.7種類のミノ(Iミノ、JミノLミノ、Oミノ、Sミノ、Tミノ、Zミノ)を作成します。
3.ブロックの削除関係
4.kivyを使ってアプリケーションを開発します。
1、2、3は4のための準備であるため、簡単です。4は1番ボリュームがあります。
今回は、
落ちてくるミノの描画の更新を行う関数(updateCurrentMino)と盤面のミノを描画する関数(updateBlocks)を作成します。
ブロックを表現するクラス
updateBlocks関数とupdateCurrentMino関数はどちらともミノを描画します。そのミノを表現するクラス(ColorWidget)を作成します。また、7種類のミノからランダムに1つ選ぶ関数(getRandomMino)を作成します。
ColorWidgetは、サイズと表示位置、色、数字を指定できるようにします。色を変えられることによって、盤面のミノと落ちてくるミノの色を分けることができます。
以下は、ColorWidgetとgetRandomMinoのソースコードです。tenplusapp.pyに追記します。
# 上記省略
class ColorWidget(Widget):
def __init__(self, color, number, **kwargs):
super(ColorWidget, self).__init__(**kwargs)
self.color = color
self.number = number
self.size_hint = [None, None]
self.bind(pos=self.update)
self.bind(size=self.update)
self.update()
def update(self, *args):
self.canvas.clear()
self.canvas.add(Color(rgba=self.color))
self.canvas.add(Rectangle(pos=self.pos, size=self.size))
self.add_widget(Label(text=str(self.number), pos=self.pos, size=self.size))
pass
def getRandomMino():
minoList = [Imino(), Jmino(), Lmino(), Omino(), Smino(), Tmino(), Zmino()]
r = random.randrange(0, len(minoList))
return minoList[r]
class FieldWidget(Widget):
# 省略
# 下記省略
コードの解説
3行目から17行目の「ColorWidget」は、ブロックやミノを表現するためのクラスです。Widgetクラスを継承しています。
4行目の「__init__関数」は、初期値などを設定する関数です。引きスには、**kwargsに加えてcolorとnumberを追加しています。
5行目の「super()」は、kivyライブラリのクラスを継承した必要なコードです。Widgetkクラスの__init__関数を実行しています。
6、7行目はそれぞれ、引数の色と数字をself.color、self.numberに代入しています。
8行目の「self.size_hint = [None, None]」は、このクラスのサイズは絶対サイズにするため、相対サイズをNoneにしています。
9、10行目はそれぞれ、サイズと表示位置が変更された時、後述するupdate関数を実行するように設定しています。
11行目の「self.update()」は、後述するupdate関数を実行しています。
12行目から16行目の「update」は、サイズと表示位置が変更されたときに実行する関数です。
13行目の「self.canvas.clear()」は、canvasに追加したウィジェットを全て削除しています。
14行目の「self.canvas.add(Color(rgba=self.color))」は、16行目のRectangleの色を指定しています。
15行目の「self.canvas.add(Rectangle(pos=self.pos, size=self.size))」は、特定のサイズの四角形を特定の位置に追加しています。
16行目は、数字を表示するためのラベルを追加しています。
19行目から22行目の「getRandomMino()」は、7種類のミノからランダムに1つ取得する関数です。
20行目の「minoList」は、7種類のミノを生成し、リスト化しています。
21行目の「r = random.randrange(0, len(minoList))」は、0から6の範囲でランダムアな数字を取得します。
22行目の「return minoList[r]」は、21行目で取得した数字のインデックスに代入されているミノを返却しています。
動作確認
ColorWidgetクラス単体だけでは、動作確認できないため、後述するupdateBlocks関数とupdateCurrentMino関数で動作確認を行います。
スポンサードサーチ
盤面のミノの描画を更新する関数
盤面のミノを描画する関数を作成します。具体的にはtenplus.pyのfieldにおいて、0と10以外の数字の部分を表示する関数です。
以下は、updateBlocks関数のソースコードです。tenplusapp.pyに追記します。
# 上記省略
class ColorWidget(Widget):
# 省略
def getRandomMino():
# 省略
class FieldWidget(Widget):
nextMino = ObjectProperty(None)
score = NumericProperty(0)
def __init__(self, **kwargs):
# 省略
def updateGrid(self, *args):
# 省略
def updateWalls(self, *args):
# 省略
def updateBlocks(self, *args):
for cw in self.blocksList:
# currentMino以外のミノを全て削除する
self.remove_widget(cw)
posList = list(zip(*np.where((0 < tenplus.field) & (tenplus.field < 10))))
for pos in posList:
if(pos[0] > 4): # field上部を表示させないため
num = tenplus.field[pos]
cw = ColorWidget(pos=[self.x + int(self.wsize*pos[1]), self.y + int(self.hsize*(25-pos[0]))],
size=[self.wsize, self.hsize],
color=[0.23, 0.08, 0.86, 0.5],
number=num)
self.blocksList.append(cw)
self.add_widget(cw)
pass
def update(self, *args):
self.canvas.clear()
self.canvas.add(Color(rgba=[1, 1, 1, 1]))
self.canvas.add(Rectangle(pos=self.pos, size=self.size))
self.updateGrid()
self.updateWalls()
self.clear_widgets() # 追加部
self.updateBlocks() # 追加部
class PlayWidget(FloatLayout):
pass
# 下記省略
コードの解説
18行目から32行目の「updateBlocks」は、盤面のブロックを表示する関数です。
19行目から21行目の「for文」は、blocksListに対して操作を行っています。blocksListは、落ちてくるミノ以外のブロックが格納されているリストです。
blocksListをちょっと詳しく説明
blocksListはアプリケーション内で1~9の数字を表現しているColorWidgetが格納されています。落ちてくるミノは含まれていません。
21行目の「self.remove_widget(cw)」は、特定のウィジェットを削除しています。この場合においては、blocksListから取り出したColorWidgetを削除しています。
22行目の「posList」は、tenplusのfieldから1~9の数字が代入されている座標を全て取得しています。
23行目から31行目の「for文」は、posListに格納されている座標1つひとつに対して、ColorWidgetを作成しています。
24行目の「IF文」は、アプリケーション上では座標(4, 0)から座標(25, 11)までしか表示していないため、行が4以上の座標のみを判定する条件です。
25行目の「num = tenplus.field[pos]」は、fieldから数字を取り出しています。
26行目から29行目は、ColorWidgetの諸設定を行っています。
表示位置の横軸方向は、「self.x + int(self.wsize*pos[1])」と設定しています。「int(self.wsize*pos[1])」によって、ウィンドウ上の左からの描画位置を指定しています。「self.x」をプラスすることで、盤面の左のからの描画位置にColorWidgetの左下を持ってくることができます。
表示位置の縦軸方向は、「self.y + int(self.hsize*(25-pos[0]))」と設定しています。「int(self.hsize*(25-pos[0]))」によって、ウィンドウ上の下からの描画位置を指定しています。「self.y」をプラスすることで、盤面の下のからの描画位置にColorWidgetの左下を持ってくることができます。
30行目の「self.blocksList.append(cw)」は、blocksListに盤面上のブロックとして追加しています。
31行目の「self.add_widget(cw)」は、ブロックを設定した位置に表示しています。
update関数で次の関数を実行しています。
39行目の「self.clear_widgets()」は、FieldWidgetに追加されているウィジェットを全て削除しています。
40行目の「self.updateBlocks()」は、updateBlocks関数を実行しています。
動作確認
動作確認を行う前に、tenplusのfieldに適当に数字を代入しておきます。私は以下のようにしました。
# 上記省略
class TenplusApp(App):
def build(self):
tenplus.field[(24, 4)] = 1
tenplus.field[(24, 5)] = 2
tenplus.field[(23, 5)] = 2
tenplus.field[(23, 6)] = 9
root = RootWidget()
root.gotoTitle()
return root
if __name__ == '__main__':
TenplusApp().run()
fieldへの数字の代入が終わったら、コマンドプロンプトを開きファイルを保存したディレクトリまで移動します。移動したら以下のコマンドを実行します。
python tenplusapp.py
以下のように表示されました。
自分が設定した座標に紫色のブロックが描画されていることを確認できたら動作確認は完了です。
落ちてくるミノの描画の更新を行う関数
落ちてくるミノ(currentMino)の描画を行う関数(updateCurrentMino)を作成します。今回は、動かすことが出来ませんが、ミノがどの位置に1番最初描画されるかを示します。
以下は、updateCurrentMinoのソースコードです。tenplusapp.pyに追記します。
# 上記省略
class ColorWidget(Widget):
# 省略
def getRandomMino():
# 省略
class FieldWidget(Widget):
nextMino = ObjectProperty(None)
score = NumericProperty(0)
def __init__(self, **kwargs):
# 省略
def updateGrid(self, *args):
# 省略
def updateWalls(self, *args):
# 省略
def updateBlocks(self, *args):
# 省略
def updateCurrentMino(self, *args):
for cm in self.cmList:
self.remove_widget(cm)
if(self.currentMino != None):
posList = list(zip(*np.where(self.currentMino.mino != 0)))
for pos in posList:
# fieldにおける座標を計算
num = self.currentMino.mino[pos]
ypos = self.currentMino.base[0] + pos[0]
xpos = self.currentMino.base[1] + pos[1]
if(ypos > 4): # 上部を非表示にするため、動作確認時は、4を-1にする。
cw = ColorWidget(pos=[self.x + int(self.wsize*xpos), self.y + int(self.hsize*(25-ypos))],
size=[self.wsize, self.hsize],
color=[0.86, 0.08, 0.23, 0.5],
number=num)
self.cmList.append(cw)
self.add_widget(cw)
pass
def update(self, *args):
self.canvas.clear()
self.canvas.add(Color(rgba=[1, 1, 1, 1]))
self.canvas.add(Rectangle(pos=self.pos, size=self.size))
self.updateGrid()
self.updateWalls()
self.clear_widgets()
self.updateBlocks()
self.updateCurrentMino() # 追加部
class PlayWidget(FloatLayout):
pass
# 下記省略
コードの解説
20行目から37行目の「updateCurrentMino」は、落ちてくるミノを描画する関数です。
21、22行目の「for文」は、cmListに対する操作を繰り返すためのものです。cmListは、落ちてくるミノ1つひとつのブロックが格納されています。
22行目の「self.remove_widget(cm)」は、特定のウィジェットを削除しています。具体的には、落ちてくるミノのブロックを削除しています。
23行目の「IF文」は、currentMinoがNoneでないことを判定する条件です。
24行目の「posList」は、currentMinoのminoから1~9の数字が代入されている座標を取り出しリスト化しています。
25行目から36行目の「for文」は、posListに代入されている座標に対してColorWidgetを設定しています。
27行目の「num」は、currentMinoの特定の座標に代入されている数字を取り出しています。
28、29行目は、field上のどの座標に数字が代入されるか計算しています。baseは、ミノの配列における(0, 0)が、field上でどの座標に位置するかです。その座標にミノの配列において数字が代入されていた座標を足すことで、field上でミノの数字がどこの座標に代入されるか計算しています。
30行目の「IF文」は、アプリケーション上では座標(4, 0)から座標(25, 11)までしか表示していないため、行が4以上の座標のみを判定する条件です。
31行目から34行目は、ColorWidgetの諸設定を行っています。
表示位置の横軸方向は、「self.x + int(self.wsize*xpos)」と設定しています。「int(self.wsize*xpos)」によって、ウィンドウ上の左からの描画位置を指定しています。「self.x」をプラスすることで、盤面の左のからの描画位置にColorWidgetの左下を持ってくることができます。
表示位置の縦軸方向は、「self.y + int(self.hsize*(25-ypos))」と設定しています。「int(self.hsize*(25-ypos))」によって、ウィンドウ上の下からの描画位置を指定しています。「self.y」をプラスすることで、盤面の下のからの描画位置にColorWidgetの左下を持ってくることができます。
35行目の「self.cmList.append(cw)」は、cmListにcurrentMinoのColorWidgetを追加しています。
36行目の「self.add_widget(cw)」は、作成したColorWidgetをFieldWidgetに追加しています。
動作確認
動作確認を行う前に、「if(ypos > 4)」の部分を「if(ypos > -1)」に変えます。また、RootWidgetクラスのgotoPlayDisplayの内容も以下のように変えます。
class RootWidget(FloatLayout):
def gotoTitle(self):
self.clear_widgets()
self.add_widget(TitleWidget())
def gotoPlayDisplay(self):
self.clear_widgets()
pw = PlayWidget()
pw.ids.fw.update()
pw.ids.fw.currentMino = getRandomMino()
self.add_widget(pw)
pass
コマンドプロンプトを開き、ファイルが保尊してあるディレクトリまで移動します。移動したら以下のコマンドを入力し、実行します。
python tenplusapp.py
以下のように表示されました。
盤面の上に特定のミノが赤色で表示されていることを確認できたら、動作確認は完了です。
スポンサードサーチ
まとめ
今回は、盤面に固定されているブロックの描画を行う関数と落ちてくるミノの描画を行う関数を作成しました。また、ブロックを表現するColorWidgetやランダムにミノを選出する関数(getRandomMino)作成しました。
本記事で作成したコードを以下に示します。
【Kivy】Pythonで落ちもの系ゲーム開発(テトリス風)Part7 コード
次回は、落ちてくるミノの操作をする関数やボタンを作成します。具体的には、左移動、右移動、下移動、左回転、右回転です。
最後までお読みいただきありがとうございます。
スポンサードサーチ