【Kivy】Pythonで迷路開発Part6

【Kivy】Pythonで迷路開発part6を表すサムネイル

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

今回は、プレイ画面に盤面とプレイヤーを表示させる関数とプレイヤーの座標を計算する関数と経過時間を計測する関数を作成します。座標に関しては、kivy上で扱う座標とnp.array上で扱う座標がことなるため複雑になります。

 

誤字脱字や分かりにくい点が、ございましたらご連絡お願いいたします。

 

 

スポンサードサーチ


【Kivy】Pythonで迷路開発Part6

Part6を表す画像

今回のシリーズは、2Dの迷路を開発することを目的としています。(プレイヤーを操作してスタートからゴールまでの時間を競います。)

 

今回のシリーズの主な内容

1.迷路の盤面を生成するプログラムの開発

2.Kivyで迷路アプリケーション開発

 

迷路の盤面を生成するプログラムでは、手書きで迷路を作るような処理を、プログラムに直していきます。

Kivyでの開発は、プレイヤーと壁との当たり判定に気を付けて開発を行います。

 

 

今回は

今回は、プレイ画面に盤面とプレイヤーを表示させる関数とプレイヤーの座標を計算する関数と経過時間を計測する関数を作成します。

 

 

drawBlocks

drawBlocksを表す画像

盤面とプレイヤーを表示する関数(drawBlocks)では、壁とゴールとプレイヤーを表示させます。

 

盤面を表現しているnp.arrayの座標とkivyでの座標はことなるため、下の図を用いて説明します。

kivyでの座標とnp.arrayでの座標の違い

np.arrayは左上を基準としているため、kivyにそのまま表示させると上下反転して表示されます。さらにnp.arrayで座標と指定するときは、(row(縦方向), col(横方向))であり、kivyで座標を指定するときは、(横軸、縦軸)となります。

しかし、個人的には左上がスタート地点、右下がゴールという方がしっくりくるため、kivyで表示させるときも左上がスタートとなるようにします。

つまり、np.arrayで(1, 0)のときはkivyでは(0*width, 5*height)となります。widthは1つの座標の横幅、heightは1つの座標の縦幅とします。

 

drawBlocksのソースコードです。labyrinthgame.pyに以下のコードを追記します。

# 上記省略

class FieldWidget(Widget):
    def __init__(self, **kwargs):
        # 省略
    def _keyboard_closed(self):
        # 省略
    def _on_keyboard_down(self, keyboard, keycode, text, modifiers):
        # 省略
    def calcPos(self):
        pass
    def drawBlocks(self, *args):
        self.clear_widgets()
        self.xpos = self.widht / self.col
        self.ypos = self.height / self.row
        for i in range(self.row):
            for j in range(self.col):
                block = self.field[(i, j)]
                if(block == 0):
                    bw = BlockWidget(pos=[j*self.xpos, (self.row-1-i)*self.ypos], size=[self.xpos, self.ypos])
                    self.add_widget(bw)
                elif(block == 2):
                    gw = GoalWidget(pos=[j*self.xpos, (self.row-1-i)*self.ypos], size=[self.xpos, self.ypos])
                    self.add_widget(gw)
        # draw Player
        half_size = self.player.size[0] / 2
        position = [(1*self.xpos)+(self.xpos*0.5)-half_size, ((self.row-2)*self.ypos)+(self.ypos*0.5)-half_size]
        self.player.pos = position
        self.add_widget(self.player)
    def countUp(self, *args):
        pass
    pass

# 下記省略

 

 

コードの解説

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

 

14、15行目の「self.xpos = self.width / self.col」と「self.ypos = self.height / self.row」は、それぞれ1つの座標分の横幅と縦幅を計算しています。self.widhtとself.heightはFieldWidgetの横幅と縦幅です。self.rowとself.colは、np.arrayの行数と列数です。

 

16、17行目の「for文」は、np.arrayの左上から右下に向かって取得します。つまり、kivyでも左上から右下に向かって表示するようにしなければなりません。

 

18行目の「block = self.field[(i, j)]」は、ブロックを取得しています。0、1、2のどれかが代入されます。

 

19、22行目の「if文」は、ブロックが壁である時とゴールである時を判定しています。

 

20、23行目は、それぞれBlockWidgetとGoalWidgetを作成しています。指定する座標とサイズは同じです。

座標の横幅は変数jが0から増えていくため、「j*self.xpos」のように表すことができます。縦幅は変数iが0からself.row-1まで増えていくため、「(self.row-1-i)*self.ypos」というように表します。

サイズは、15、16行目で求めたものを代入します。

 

26行目の「half_size = self.player.size[0] / 2」は、プレイヤーのサイズの半分を求めています。プレイヤーの表示位置を微調整するために求めています。

27行目の「position」は、プレイヤーの表示位置を指定しています。プレイヤーの表示させる位置は、np.array上では(1, 1)であり、kivy上では(1, self.row-2)になります。

しかし「1*self.xpos, (self.row-2)*self.ypos」と指定すると、プレイヤーが座標の左下によってしまい見栄えが悪いため、座標の真ん中に表示させます。すると27行目のように指定できます。(下図)

プレイヤーの座標を詳しく解説した画像

 

29行目の「self.add_widget(self.player)」は、プレイヤーをFieldWidgetに追加しています。

 

 

動作確認

盤面が正しく表示されるか動作確認を行います。コマンドプロンプトを開き、ファイルを保存したフォルダまで移動します。移動したら以下のコマンドを入力し、実行します。

python labyrinthgame.py

 

以下のように表示されていれば、正しく動作しています。

drawBlocksによって盤面を表示した画像

上の図はレベル1の迷路ですが、他のレベルでも迷路が正しく表示されるか確認します。

 

以上で動作確認は完了です。

 

 

スポンサードサーチ


calcPos

calcPosを表す画像

calcPosは、kivyにおける現在のプレイヤーの座標からnp.arrayの座標へ変換する関数です。

プレイヤーには大きさがあるため、上下左右で異なる座標に属する可能性があります。そのため、上下左右(top、y、x、right)の点において座標を計算します。

 

calcPosのソースコードです。labyrinthgame.pyに以下のコードを追記します。

# 上記省略

class FieldWidget(Widget):
    def __init__(self, **kwargs):
        # 省略
    def _keyboard_closed(self):
        # 省略
    def _on_keyboard_down(self, keyboard, keycode, text, modifiers):
        xpoint, ypoint, rightpoint, toppoint = self.calcPos()                                        # 追加部
        print("x, y, right, top = {0}, {1}, {2}, {3}".format(xpoint, ypoint, rightpoint, toppoint))  # 追加部
        if(keycode[1] == 'left'):
            print("Left")
        elif(keycode[1] == 'up'):
            print("Up")
        elif(keycode[1] == 'right'):
            print("Right")
        elif(keycode[1] == 'down'):
            print("Down")
    def calcPos(self):
        for i in range(self.row):
            if(i*self.ypos <= self.player.y and self.player.y < i*self.ypos+self.ypos):
                ypoint = self.row - 1 - i
                if(i*self.ypos <= self.player.top and self.player.top < i*self.ypos+self.ypos):
                    toppoint = ypoint
                else:
                    toppoint = ypoint + 1
                break
        for i in range(self.col):
            if(i*self.xpos <= self.player.x and self.player.x < i*self.xpos+self.xpos):
                xpoint = i
                if(xpoint*self.xpos <= self.player.right and self.player.right < xpoint*self.xpos+self.xpos):
                    rightpoint = xpoint
                else:
                    rightpoint = xpoint + 1
                break
        return xpoint, ypoint, rightpoint, toppoint
    def drawBlocks(self, *args):
        # 省略
    def countUp(self, *args):
        pass
    pass

 

 

コードの解説

20行目の「for文」は、縦軸に対して繰り返し処理を行います。

21行目の「if文」は、y(ypoint)に対して座標の判別を行っています。

23行目の「if文」は、top(toppoint)に対して座標の判別を行っています。

縦方方向の座標の取得を表した画像

 

28行目の「for文」は、横軸に対して繰り返し処理を行います。

29行目の「if文」は、x(xpoint)に対して座標の判別を行っています。

31行目の「if文」は、right(rightpoint)に対して座標の判別を行っています。

横方向の座標の取得を表した画像

 

 

動作確認

プレイヤーを動かすことができないため、動作確認ができません。次回にプレイヤーを動かす関数(_on_keyboard_down)を作成するため、その動作確認と一緒に確認します。

 

 

countUp

countUpを表す画像

countUp関数は、経過時間を計測することとその時間をラベルに表示する関数です。Clock関数を使って、1秒ごとに呼び出すため1秒ごとに行って欲しい処理を記述します。

 

countUpのソースコードです。labyrinthgame.pyに以下のコードを追記します。

# 上記省略

class FieldWidget(Widget):
    def __init__(self, **kwargs):
        # 省略
    def _keyboard_closed(self):
        # 省略
    def _on_keyboard_down(self, keyboard, keycode, text, modifiers):
        if(self.evt == None):                                     # 追加部
            self.evt = Clock.schedule_interval(self.countUp, 1)   # 追加部
        xpoint, ypoint, rightpoint, toppoint = self.calcPos()
        print("x, y, right, top = {0}, {1}, {2}, {3}".format(xpoint, ypoint, rightpoint, toppoint))
        if(keycode[1] == 'left'):
            print("Left")
        elif(keycode[1] == 'up'):
            print("Up")
        elif(keycode[1] == 'right'):
            print("Right")
        elif(keycode[1] == 'down'):
            print("Down")
        xpoint, ypoint, rightpoint, toppoint = self.calcPos()                                                 # 追加部
        if((ypoint, xpoint) == self.goalpos and (toppoint, rightpoint) == self.goalpos and self.evt != None): # 追加部
            self.evt.cancel()                                                                                 # 追加部
            self.time = 0                                                                                     # 追加部
            self._keyboard_closed()                                                                           # 追加部
    def calcPos(self):
        # 省略
    def drawBlocks(self, *args):
        # 省略
    def countUp(self, *args):
        self.time += 1
        self.parent.ids.timelb.text = str(self.time) + "s"
    pass

# 下記省略

 

 

コードの解説

7、8行目は、時間の計測を開始するコードです。「self.evt = Clock.schedule_interval(self.countUp, 1)」とすることで、self.evtにClockオブジェクトが代入されます。また1秒ごとにself.countUpを実行します。

 

19~23行目は、時間の計測を終了するコードです。プレイヤーの全体がゴールに入ったとき終了します。

21行目の「self.evt.cancel()」は、Clockオブジェクトを終了しています。

22行目の「self.time = 0」は、計測時間の変数を初期値に戻しています。

23行目の「self._keyboard_closed()」は、キーボード入力を遮断しています。

 

29行目の「self.time += 1」は、計測時間を表す変数self.timeに1を追加しています。1秒ごとに実行されるため1を追加します。2秒ごとに実行されるとしたら2を追加します。

30行目の「self.parent.ids.timelb.text = str(self.time) + “s”」は、PlayWidgetに含まれるラベルの文字列に経過時間を代入しています。

 

 

動作確認

プレイヤーを動かすことが出来ないため、時間の計測が開始するか動作確認を行います。コマンドプロンプトを開き、ファイルを保存したフォルダまで移動します。移動したら以下のコマンドを入力し、実行します。

python labyrinthgame.py

 

以下のように、方向キーを押したら時間の計測が開始することを確認します。

カウントが動いていることを表す画像

 

プレイヤーを動かすことができないため、経過時間の終了を確認できません。よって、次回確認します。

 

以上で動作確認は完了です。

 

 

スポンサードサーチ


まとめ

まとめの画像

今回は、盤面の表示、kivyの座標からnp.arrayの座標へ変換、1秒ごとに実行する処理を作成しました。

プレイヤーを動かすことができないと、確認できないこともあったと思います。

 

今回作成したコードを以下に示します。

 

次回は、プレイヤーを動かす関数を作成します。壁との当たり判定が少し複雑です。

 

 

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


スポンサードサーチ