【Kivy】Pythonで落ちもの系ゲーム開発(テトリス風)Part10

【Kivy】Pythonで落ちもの系ゲーム開発(テトリス風)ぱーとじゅう

こんにちは、にわこまです。

今回は、スタートボタン、ストップボタンと実際にブロックを削除する関数、一定間隔でcurrentMinoを下方向に移動させる関数を作成します。

 

誤字脱字や何かございましたらご連絡お願いいたします。

 

 

スポンサードサーチ


落ちもの系ゲーム開発Part10

落ちもの系ゲーム開発ぱーとじゅう

Part1で紹介した、今回作成するゲームの概要を改めて説明します。

 

今回開発するゲームは、テトリスに似ています。テトリスのルールは、以下のような7つのミノと呼ばれるブロックを積み重ねていき、横一列をミノで満たしたらその行が消され得点します。

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番ボリュームがあります。

 

 

今回は、

スタートボタンとストップボタンを作成します。さらに、実際にブロックを削除する関数と一定間隔でcurrentMinoを下方向に移動させる関数を作成します。

 

 

スタート/ストップボタン

スタートストップボタン

Part8(*URLを指定する)で形だけ作成したスタートストップボタンの機能を作成します。スタートボタンをクリックすると一定間隔でミノが落ちてくるようにします。ストップボタンをクリックするとミノが落ちてくるのを止めます。

 

以下は、スタート/ストップボタンのソースコードです。tenplusapp.pyに追記します。

# 上記省略

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):
        # 省略
    def update(self, *args):
        # 省略
    def gameStart(self, *args):
        if(self.evt == None):
            self.evt = Clock.schedule_interval(self.func, 1)
            if(self.currentMino == None):
                self.currentMino = self.nextMino
                self.nextMino = getRandomMino()
                self.updateCurrentMino()
    def gameStop(self, *args):
        if(self.evt != None):
            self.evt.cancel()
            self.evt = None
    def rollLeft(self, *args):
        # 省略
    def rollRight(self, *args):
        # 省略
    def moveLeft(self, *args):
        # 省略
    def moveRight(self, *args):
        # 省略
    def moveUnder(self, *args):
        # 省略

# 下記省略

 以下は、スタート/ストップボタンの機能をボタンに追加しています。tenplusapp.kvファイルに追記します。

# 上記省略

<FieldWidget>:

<PlayWidget>:
    canvas.before:
        Color:
            rgba: [1, 1, 1, 1]
        Rectangle:
            pos: self.pos
            size: self.size
    BoxLayout:
        AnchorLayout:
            anchor_x: 'center'
            anchor_y: 'center'
            size_hint_x: 3
            FieldWidget:
                id: fw
                on_nextMino: root.ids.nw.nextMino=self.nextMino
        BoxLayout:
            orientation: 'vertical'
            size_hint_x: 4
            AnchorLayout:
                anchor_x: 'center'
                anchor_y: 'center'
                size_hint_y: 4
                BoxLayout:
                    orientation: 'vertical'
                    size_hint: [None, None]
                    size: [fw.wsize*16, fw.hsize*16]
                    BoxLayout:
                        size_hint: [1, 0.25]
                        BtnStart:
                            on_press: fw.gameStart()
                        BtnStop:
                            on_press: fw.gameStop()
                    BoxLayout:
                        # 省略
                    BoxLayout:
                        # 省略
                    BtnUnder:
                        # 省略
            Button:
                # 省略

# 下記省略

 

 

コードの解説(Pythonファイル)

18行目から24行目の「gameStart関数」は、ミノを一定間隔で落とすことを開始する関数です。

19行目の「IF文」は、既にゲームがスタートしているかどうかを判定する条件です。既にゲームがスタートしいる場合、self.evtにClockオブジェクトが格納されています。スタートしていない場合は、self.evtにNoneを代入しています。

20行目の「self.evt = Clock.schedule_interval(self.func, 1)」は、1秒間隔で後述するself.func関数を呼び出すことを設定しています。

21行目の「IF文」は、currentMinoにミノが代入されているかどうかを判定する条件です。

22行目の「self.currentMino」は、self.nextMinoからミノを代入されています。

23行目の「self.nextMino」は、新たに生成したミノが代入されます。

24行目の「self.updateCurrentMino()」は、currentMinoを盤面に表示しています。しかし、ここでの表示は、プレイヤーが見ることのできない範囲外に表示しています。

 

25行目から28行目「gameStop関数」は、落ちてくるミノを停止する関数です。

26行目の「IF文」は、ゲーム中であるかどうかを判定する条件です。ゲーム中であればself.evtはNoneではないです。

27行目の「self.evt.cancel()」は、格納されているClockオブジェクトを停止しています。

28行目の「self.evt」は、初期値であるNoneを代入しています。

 

 

コードの解説(kvファイル)

34行目の「on_press」は、スタートボタンがクリックされたときFieldWidgetのgameStart関数を実行するように設定しています。

 

36行目の「on_press」は、ストップボタンがクリックされたときFieldWidgetのgameStop関数を実行するように設定しています。

 

18行目にて、FieldWidgetのidを「fw」と設定しています。

 

 

動作確認

ブロックを削除する関数と一定間隔でミノをしたお方向に移動させる関数を作成したら、動作確認を行います。

 

 

スポンサードサーチ


ブロックを削除する関数

delBlocksTask関数

ブロックを削除する関数、delBlocksTaskを作成します。

隣り合うブロックを足し合わせて10になるブロックの組み合わせを削除します。また、終了判定もこの関数で行います。さらに、新たなミノの生成もこの関数で行います。

 

以下は、delBlocksTaskのソースコードです。tenplusapp.pyに追記します。

# 上記省略

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):
        # 省略
    def update(self, *args):
        # 省略
    def gameStart(self, *args):
        # 省略
    def gameStop(self, *args):
        # 省略
    def delBlocksTask(self, *args):
        delPosList = tenplus.getdelPosList(tenplus.field)
        while(len(delPosList) != 0):
            nob = len(delPosList)   # nob = Number of Blocks
            self.score = self.score + nob * 100
            tenplus.downNum(tenplus.field, delPosList)
            delPosList = tenplus.getdelPosList(tenplus.field)
            self.updateBlocks()
        tof45 = tenplus.checkExitCond(tenplus.field, (4, 5))    # 終了していなければ、Falseが返される
        tof46 = tenplus.checkExitCond(tenplus.field, (4, 6))    # 終了していなければ、Falseが返される
        if(not(tof45) and not(tof46)):
            # 最初のミノ生成以外はここから生成
            self.currentMino = self.nextMino
            self.nextMino = getRandomMino()
            self.updateCurrentMino()
            self.evt = Clock.schedule_interval(self.func, 1)
        else:
            print("Game over!")
    def rollLeft(self, *args):

    def rollRight(self, *args):
        # 省略
    def moveLeft(self, *args):
        # 省略
    def moveRight(self, *args):
        # 省略
    def moveUnder(self, *args):
        # 省略

# 下記省略

 

以下は、スコアを更新するためのコードです。tenpluapp.kvに追記します。

# 上記省略

<PlayWidget>:
    canvas.before:
        Color:
            rgba: [1, 1, 1, 1]
        Rectangle:
            pos: self.pos
            size: self.size
    BoxLayout:
        AnchorLayout:
            anchor_x: 'center'
            anchor_y: 'center'
            size_hint_x: 3
            FieldWidget:
                id: fw
                on_nextMino: root.ids.nw.nextMino=self.nextMino
                on_score: root.ids.slb.text = str(self.score)
        BoxLayout:
            # 省略

# 下記省略

 

 

コードの解説(Pythonファイル)

22行目から39行目の「delBlocksTask関数」は、ブロックを削除する関数です。

23行目の「delPosList」は、削除する必要のある座標を取得しています。

24行目から29行目の「while文」は、ブロックの削除を繰り返しています。

25行目の「nob」は、削除するブロックの数を数えています。

26行目の「self.score」は、スコアを更新しています。削除するブロックの数×100をスコアとしています。

27行目は、実際に盤面から数字を削除し、その座標より上位にある数字を落としています。

28行目の「delPosList」は、新たな盤面において削除する座標を取得しています。

29行目の「self.updateBlocks()」は、盤面の表示を更新しています。

 

30、31行目は、それぞれ座標(4, 5)、座標(4, 6)に1~9の数字が存在していないかを確認しています。

32行目の「IF文」は、ゲームの終了判定を行っています。終了していない場合をTrueとしています。

34行目の「self.currentMino」は、新たにnextMinoからミノを代入されています。

35行目の「self.nextMino」は、新たに生成されたミノを代入しています。

36行目の「self.updateCurrentMino()」は、落ちてくるミノの表示を更新しています。

37行目の「self.evt」は、新たに1秒間隔でself.funcを実行することを設定しています。

 

後述しますが、delBlocksTask関数を実行する前にself.evtはcancel()を実行しNoneを代入しています。さらに、currentMinoを盤面に固定しています。

 

 

コードの解説(kvファイル)

18行目の「on_score」は、scoreが変化したとき、同じPlayWidget上にあるScoreLabelにスコアを文字列に変換して代入しています。

 

 

動作確認

一定間隔でミノを下方向に移動させる関数を作成し終わったら、動作確認を行います。

 

 

一定間隔で呼び出す関数

func関数

一定間隔で呼び出す関数(func)を作成します。ミノを下方向に移動させたり、ブロックを削除したりする処理を行います。

 

以下は、funcのソースコードです。tenplusapp.pyに追記します。

# 上記省略

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):
        # 省略
    def update(self, *args):
        # 省略
    def gameStart(self, *args):
        # 省略
    def gameStop(self, *args):
        # 省略
    def delBlocksTask(self, *args):
        # 省略
    def func(self, *args):
        tof = self.currentMino.moveUnder(tenplus.field)
        if(tof):
            self.updateCurrentMino()
        else:
            self.evt.cancel()
            self.evt = None
            self.currentMino.updateField(tenplus.field)     # 実フィールドに現ミノを固定
            self.currentMino = None
            self.updateCurrentMino()
            self.updateBlocks()
            # 条件を満たしたminoを削除
            self.delBlocksTask()
    def rollLeft(self, *args):
        # 省略
    def rollRight(self, *args):
        # 省略
    def moveLeft(self, *args):
        # 省略
    def moveRight(self, *args):
        # 省略
    def moveUnder(self, *args):
        # 省略

# 下記省略

 

 

コードの解説

24行目から36行目の「func関数」は、一定間隔で呼び出される関数です。

 

25行目の「tof」は、currentMinoを下方向に移動させることができるかの判定を取得しています。移動出来ればTrueが代入されます。

27行目の「self.updateCurrentMino()」は、移動出来たcurrentMinoの表示を更新しています。

29、30行目は、1秒間隔で呼びだすことを停止し、Noneを代入しています。停止する理由としては、ブロックの削除に1秒以上かかる可能性があるためです。

31行目は、現在のcurretMinoの座標を盤面に固定しています。

32行目の「self.currentMino」は、Noneを代入しています。currentMinoが空であることを示しています。

33行目の「self.updateCurrentMino()」は、currentMinoの表示を更新しています。currentMinoは空になっているため、表示中であったcurretMinoを削除しています。

34行目の「self.updateBlocks()」は、盤面の表示を更新しています。

36行目の「self.delBlocksTask()」は、ブロックの削除を実行しています。

 

 

動作確認

コマンドプロンプトを開き、ファイルが保存してあるフォルダまで移動します。移動したら以下のコマンドを実行します。

python tenplusapp.py

 

スタートボタンをクリックし、ミノが落ちてくることを確認します。さらに、条件が整ったブロックが削除されることも確認します。

ゲームスタート前
ゲーム中
ブロックが削除された

 

確認できたら動作確認は完了です。

 

 

スポンサードサーチ


まとめ

まとめの画像

今回は、スタート/ストップボタンとブロックを削除する関数、一定間隔で呼び出す関数を作成しました。基本的には、本記事までで完成しています。

 

本記事で作成したコードを以下に示します。

 

しかし、私は遊んでみて気が付きました。ブロックの削除が早すぎて、どこが消されたのか分からないということです。

よって、次回Part11では削除するブロックを目立たせる関数を作成します。次回で本当の完成です。

 

 

最後までお読みいただきありがとうございます。


スポンサードサーチ