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

落ちもの系ゲーム開発テトリス風ぱーとろくを表すサムネイル

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

今回は、ゲームの流れを担うクラスを作成し、盤面を表示する関数を作成します。具体的には、縦横の線を引く関数と盤面の壁を表示する関数を作成します。

 

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

 

 

スポンサードサーチ


落ちもの系ゲーム開発Part6

落ちもの系ゲーム開発ぱーとろくを表す画像

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

 

 

今回は、

ゲームの流れを担うクラスの内、縦横の線を表示する関数と盤面の壁を表示する関数を作成します。

 

 

ゲームの流れを担うクラス

盤面やゲームの流れを担うクラスを表す画像

ゲームの流れを担うクラスは、FieldWidgetという名前にしました。ここでは、FieldWidgetの初期設定(__init__関数)を説明します。

 

以下は、FieldWidgetクラスと__ini__関数のソースコードです。tenplusapp.pyに追記します。

# 以上省略

class FieldWidget(Widget):
    nextMino = ObjectProperty(None)
    score = NumericProperty(0)
    def __init__(self, **kwargs):
        super(FieldWidget, self).__init__(**kwargs)
        self.wsize = 30         #self.width / 12
        self.hsize = 30         #self.height / 22
        self.size_hint = [None, None]
        self.size = [self.wsize * 12, self.hsize * 21]
        self.currentMino = None
        self.nextMino = None
        self.evt = None
        self.interval_time = 1
        self.blocksList = []
        self.cmList = []        # currentMinoList
        self.bind(pos=self.update)
        self.bind(size=self.update)
        self.update()
    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))

class PlayWidget(FloatLayout):
    pass

# 以下省略

 

以下は、FieldWidgetを装飾するソースコードです。tenplusapp.kvに追記します。

# -*- coding: utf-8 -*-
<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
        BoxLayout:
            orientation: 'vertical'
            size_hint_x: 4

# 以下省略

 

 

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

3行目から24行目の「FieldWidget」は、ゲーム流れを担ったり、盤面を表示したりするクラスです。

4行目の「nextMino」は、次に落ちてくるミノを代入する変数です。「ObjectProperty(None)」を設定することで、この変数が変化したときにイベントを発生できるようにしています。

5行目の「score」は、点数を表す変数です。「NumericProperty(0)」を設定することで、点数が変化したときにイベントが発生するようにしています。

 

6行目から20行目のの「__init__関数」は、FieldWidgetの初期値を設定する関数です。

7行目のコードは、クラスを継承する上で必要なコードです。決まりきったコードであるため、特に説明はしません。

8行目の「self.wsize = 30」は、盤面における1マスの横幅を30に設定しています。

9行目の「self.hsize = 30」は、盤面における1マスの縦幅を30に設定しています。

10行目の「self.size_hint = [None, None]」は、FieldWidgetクラスのサイズを固定値にするため、相対的にサイズを決めるsize_hintにNoneを代入しています。

11行目の「self.size = [self.wsize * 12, self.hsize * 21]」は、FieldWidgetのサイズを盤面のサイズに設定しています。

12行目の「self.currentMino」は、現在落ちているミノを表す変数です。初期値はNoneに設定しています。

13行目の「self.nextMino」は、次に落ちてくるミノをを表す変数です。初期値はNoneに設定しています。

14行目の「self.evt」は、時間関係のオブジェクトを代入する変数です。初期値はNoneに設定しています。

15行目の「self.interval_time」は、ミノが落ちてくる速度を代入する変数です。初期値は、1に設定しています。1秒間隔で落ちくてるということです。

16行目の「self.blocksList」は、固定されているブロックを代入するリストです。言い換えると、currentMino以外のブロックを代入するリストです。

17行目の「self.cmList」は、currentMinoのブロックを代入するリストです。

18、19行目は、表示位置(pos)またはサイズ(size)が変化したとき後述するupdate関数を実行するように設定しています。

20行目の「self.update()」は、後述するupdate関数を実行しています。

 

21行目から24行目の「update関数」は、FieldWidgetの表示位置(pos)またはサイズ(size)が変化したときに実行する関数です。

22行目の「self.canvas.clear()」は、FieldWidgetのcanvasに追加されているウィジェットを全て削除しています。

23行目の「self.canvas.add(Color(rgba=[1, 1, 1, 1]))」は、背景色を設定しています。

24行目の「self.canvas.add(Rectangle(pos=self.pos, size=self.size))」は、長方形をFieldWidgetの同じ原点、同じサイズで表示しています。

 

 

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

2行目の「<FieldWidget>:」は、FieldWidgetの装飾行えるようにクラスを宣言しています。しかし、FieldWidgetの装飾はPythonファイル内で行います。

 

11行目「BoxLayout:」は、BoxLayoutを宣言しています。本来は、FieldWidgetとボタン群を横並びで表示させます。

12行目の「AnchorLayout:」は、AnchorLayoutを宣言しています。FieldWidgetを真ん中に表示させるためです。

13行目の「anchor_x: ‘center’」と14行目の「anchor_y: ‘center’」は、x軸方向とy軸方向のどの場所に固定するかを設定しています。今回の場合は、真ん中に固定指定ため、どちらともcenterです。

15行目の「size_hint_x: 3」は、FieldWidgetとボタン群を横並びに表示させますが、そのときのFieldWidgetの横幅の比率です。ボタン群は4にします。

16行目の「FieldWidget:」は、FieldWidgetをPlayWidgetレイアウトに宣言しています。

17行目の「id: fw」は、FieldWidgetのidを宣言しています。FieldWidget内にある関数や変数を呼び出すときに使用します。

18行目の「BoxLayout:」は、前述したAnchorLayoutと同じ階層にあるレイアウトです。このレイアウトには後々紹介するボタン群を設置します。

19行目の「orientation: ‘vertical’」は、縦並びになるよに設定しています。

20行目の「size_hint_x: 4」は、ボタン群の横幅を比率で設定しています。FieldWidgetとボタン群が「3:4」になるように横幅を設定しています。

フィールドウィジェットのレイアウト

 

 

動作確認

Fieldwidgetには、まだ何も表示させてないため、真っ白な画面が表示されるだけです。ゆえに、動作確認は行いません。

 

 

スポンサードサーチ


盤面の壁を表示する関数

盤面の壁を表示する関数を表す画像

盤面の壁を表示する関数を「updateWalls」として作成します。

壁は配列上の、0列目、11列目、25行目になります。10が代入されている座標です。U型の壁が表示されます。

 

以下は、盤面の壁を表示する関数のソースコードです。tenplusapp.pyに追記します。

# 以上省略

class FieldWidget(Widget):
    nextMino = ObjectProperty(None)
    score = NumericProperty(0)
    def __init__(self, **kwargs):
        # 省略
    def updateWalls(self, *args):
        posList = list(zip(*np.where(tenplus.field == 10)))
        self.canvas.add(Color(rgba=[0, 0, 0, 1]))
        for pos in posList:
            # fieldの上部を表示させないためのIF文
            if(pos[0] > 4):
                self.canvas.add(Rectangle(pos=[self.x + int(self.wsize*pos[1]), self.y + int(self.hsize*(25-pos[0]))], size=[self.wsize, self.hsize]))
        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.updateWalls()

class PlayWidget(FloatLayout):
    pass

# 以下省略

 

 

コードの解説

8行目から15行目の「updateWalls関数」は、盤面の壁を表示する関数です。

9行目の「posList」は、10が代入されている座標のリストです。

np.whereによって、10が代入されている行と列をそれぞれリストで取得します。このリストは対応しています。

このままだと、座標として使えないためzip関数によって座標にします。zip関数後はzip型になっており、座標を取り出せないためlist関数によってlist型に変換しています。

10行目の「self.canvas.add(Color(rgba=[0, 0, 0, 1]))」は、この行以降にcanvasに追加されるウィジェットの色を設定しています。今回の場合は黒色。

11行目から14行目の「for文」は、posListから順位座標を取り出し、その座標に合う場所に壁を表示しています。

13行目の「IF文」は、プレイヤーが見ることのできる盤面の範囲を設定しています。

14行目は、長方形のウィジェット(Rectangle)によって壁をcanvasに追加しています。Rectangleには、表示位置(pos)とサイズ(size)を設定しています。

 

20行目の「self.updateWalls()」は、updateWallsを実行しています。

 

 

動作確認

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

python tenplusapp.py

 

1.タイトル画面が表示されます。次に、スタートボタンをクリックします。

タイトル画面

 

2.以下のように、U字型の壁が表示されます。

フィールドウィジェットで盤面の壁が表示された状態

 

壁が表示されていることを確認できたら、動作確認は完了です。

 

 

盤面の縦横の線を表示する関数

盤面の縦と横の線を表示する関数を表す画像

盤面に縦と横の線を表示する関数をupdateGridとして作成します。縦横の線があるとミノの動きが良く分かるため表示させます。

 

以下は、盤面に縦と横の線を表示する関数のソースコードです。tenplusapp.pyに追記します。

# 以上省略

class FieldWidget(Widget):
    nextMino = ObjectProperty(None)
    score = NumericProperty(0)
    def __init__(self, **kwargs):
        # 省略
    def updateGrid(self, *args):
        self.canvas.add(Color(rgba=[0.85, 0.85, 0.85, 1]))
        for i in range(1, 22):
            self.canvas.add(Line(points=[self.x, self.y + self.hsize*i, self.x + self.wsize*12, self.y + self.hsize*i], width=1))
        for i in range(2, 11):
            self.canvas.add(Line(points=[self.x + self.wsize*i, self.y, self.x + self.wsize*i, self.y + self.hsize*21], width=1))
        pass
    def updateWalls(self, *args):
        # 省略
    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()

class PlayWidget(FloatLayout):
    pass

# 以下省略

 

 

コードの解説

8行目から14行目の「updateGrid関数」は、盤面に縦と横の線を表示する関数です。

9行目の「self.canvas.add(Color(rgba=[0.85, 0.85, 0.85, 1]))」は、この行以降にcanvasに追加されるウィジェットの色を設定しています。今回の場合は、灰色です。

10、11行目の「for文」は、横方向に線を引くための繰り返し文です。

11行目は、Lineをcanvasに追加しています。pointsによって、開始座標と終了座標を指定しています。線の太さ(width)は1にしています。

12、13行目の「for文」は、縦方向に線を引くための繰り返し文です。

13行目は、Lineをcanvasに追加しています。pointsによって、開始座標と終了座標を指定しています。線の太さ(width)は1にしています。

壁に接している部分の線は引かなくても良いと判断したため、2~10列のみに線を引いています。

 

21行目の「self.updateGrid()」は、updateGrid関数を実行しています。

 

 

動作確認

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

python tenplusapp.py

 

1.タイトル画面が表示されます。次に、スタートボタンをクリックします。

タイトル画面

 

2.壁と縦横の線が引かれた盤面が表示されます。

フィールドウィジェットに盤面の壁と縦横の線が引かれた状態

 

壁と線を確認できたら、動作確認は完了です。

 

 

スポンサードサーチ


まとめ

まとめの画像

今回は、FieldWidgetの初期設定の説明と壁を表示する関数の作成と縦横の線を表示する関数を作成しました。

 レイアウトや表示位置(pos)が複雑になってくるため、分からない箇所があればメールで連絡お願いいたします。

 

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

 

次回Part7では、落ちてくるミノの描画を行う関数と盤面のブロックを描画する関数を作成します。また、ブロックを表現するためのクラスと7種類のミノからランダムにミノを選ぶ関数を作成します。

 

 

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


スポンサードサーチ