【Kivy】Pythonで迷路開発Part5

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

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

今回は、プレイ画面のレイアウトとボタンを作成します。kivyファイルのコードが長くなっているため、1行ずつ理解しましょう。

 

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

 

 

スポンサードサーチ


【Kivy】Pythonで迷路開発Part5

Part5を表す画像

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

 

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

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

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

 

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

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

 

 

今回は

今回は、迷路アプリケーションのプレイ画面を作成します。プレイ画面はメインのコードが長いため、それ以外の機能を作成します。

レイアウトとボンタンを作成します。

 

 

PlayWidget

PlayWidgetを表す画像

迷路を実際にプレイする画面を作成します。実際には、プレイヤーを表すクラス、壁を表すクラス、ゴールを表すクラスと盤面を表すクラスを作成します。

 

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

# 上記省略

class PlayerWidget(Widget):
    pass

class BlockWidget(Widget):
    pass

class GoalWidget(Widget):
    pass

class FieldWidget(Widget):
    def __init__(self, **kwargs):
        super(FieldWidget, self).__init__(**kwargs)
        self._keyboard = Window.request_keyboard(self._keyboard_closed, self)
        self._keyboard.bind(on_key_down=self._on_keyboard_down)
        self.player = PlayerWidget()
        self.field = None
        self.row = 0
        self.col = 0
        self.goalpos = (0, 0)
        self.xpos = 0
        self.ypos = 0
        self.diff = 10
        self.evt = None
        self.time = 0
        self.bind(size=self.drawBlocks)
    def _keyboard_closed(self):
        print('My keyboard have been closed!')
        self._keyboard.unbind(on_key_down=self._on_keyboard_down)
        #self._keyboard = None
    def _on_keyboard_down(self, keyboard, keycode, text, modifiers):
        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):
        pass
    def drawBlocks(self, *args):
        pass
    def countUp(self, *args):
        pass
    pass

class PlayWidget(BoxLayout):
    def on_press_resetbtn(self, *args):
        print("Reset")
    pass

class TitleWidget(FloatLayout):
    pass

class RootWidget(FloatLayout):
    def gotoTitle(self):
        self.clear_widgets()
        self.add_widget(TitleWidget())
    def gotoPlayWidget(self, level):
        pw = PlayWidget()
        if(level == 1):
            field01 = np.copy(labyrinth04.field01)
            pw.ids.fw.field = makeField(field01)
        elif(level == 2):
            field02 = np.copy(labyrinth04.field02)
            pw.ids.fw.field = makeField(field02)
        elif(level == 3):
            field03 = np.copy(labyrinth04.field03)
            pw.ids.fw.field = makeField(field03)
        pw.ids.fw.row , pw.ids.fw.col = np.shape(pw.ids.fw.field)
        pw.ids.fw.goalpos = list(zip(*np.where(pw.ids.fw.field == 2)))[0]
        self.clear_widgets()
        self.add_widget(pw)
    pass

# 下記省略

 

labyrinthgame.kvに以下のコードを追記します。

# -*- coding: utf-8 -*-

<PlayerWidget>:
    size_hint: [None, None]
    size: [25, 25]
    canvas:
        Color:
            rgb: [0.5, 0.5, 0.5, 1]
        Ellipse:
            pos: self.pos
            size: self.size

<BlockWidget>:
    canvas:
        Color:
            rgb: [0.05, 0.1, 0.1]
        Rectangle:
            pos: self.pos
            size: self.size

<GoalWidget>:
    canvas:
        Color:
            rgb: [0.86, 0.08, 0.23]
        Rectangle:
            pos: self.pos
            size: self.size

<PlayWidget>:
    canvas.before:
        Color:
            rgb: [1, 1, 1]
        Rectangle:
            pos: self.pos
            size: self.size
    orientation: "vertical"
    BoxLayout:
        size_hint_y: 1
        Label:
            canvas.before:
                Color:
                    rgb: [0.1, 0.1, 0.1]
                Rectangle:
                    pos: self.pos
                    size: self.size
            id: timelb
            font_size: 36
            text: "Start"
        Button:
            id: resetbtn
            font_size: 36
            text: "Reset"
            on_press: root.on__press_resetbtn()
        Button:
            id: returnbtn
            font_size: 36
            text: "Return"
            on_press: app.root.gotoTitle()
    FieldWidget:
        id: fw
        size_hint_y: 9

<LevelBtn@Button>:
    # 省略

<TitleWidget>:
    canvas.before:
        Color:
            rgb: [1, 1, 1]
        Rectangle:
            pos: self.pos
            size: self.size
    Label:
        bold: True
        color: [0.33, 0.33, 0.33, 1]
        font_size: 60
        pos_hint: {"center_x":0.5, "center_y":0.75}
        text: "LABYRINTH."
    LevelBtn:
        level: 1
        background_color: [0.86, 0.08, 0.23, 1]
        pos_hint: {"center_x":0.5, "center_y":0.35}
        text: "Level 1"
        on_press: app.root.gotoPlayWidget(self.level)  # 追加部
    LevelBtn:
        level: 2
        background_color: [0.23, 0.86, 0.08, 1]
        pos_hint: {"center_x":0.5, "center_y":0.25}
        text: "Level 2"
        on_press: app.root.gotoPlayWidget(self.level)  # 追加部
    LevelBtn:
        level: 3
        background_color: [0.23, 0.08, 0.86, 1]
        pos_hint: {"center_x":0.5, "center_y":0.15}
        text: "Level 3"
        on_press: app.root.gotoPlayWidget(self.level)  # 追加部

<RootWidget>:
    # 省略

 

 

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

3行目の「PlayerWidget」は、プレイヤーを表すクラスです。PlayerWidgetのデザインはkivyファイルで全て表現できるため、pythonファイルにはクラスの宣言だけを行います。

 

6行目の「BlockWidget」は、迷路の壁を表すクラスです。壁のデザインはkivyファイルで全て表現できるため、pythonファイルにはクラスの宣言だけを行います。

9行目の「GoalWidget」は、ゴールを表すクラスです。ゴールのデザインもkivyファイルで全て表現できるため、pythonファイルにはクラスの宣言だけを行います。

 

12行目~47行目の「FieldWidget」は、盤面を表すクラスです。また、プレイヤーを動かす処理を担うクラスです。

13行目~27行目の「__init__」は、インスタンス化を行っています。 15、16行目は、プレイヤーを動かすためにキーボード入力をできるようにするための変数を設定しています。

17行目の「self.player = PlayerWidget()」は、実際に動かすプレイヤーを生成しています。

18行目~21行目の「self.field」、「self.row」、「self.col」、「self.goalpos」は盤面を表示するための変数であり、初期値を代入しています。後述するRootWidgetのgotoPlayWidget関数において、実データを代入します。

22、23行目の「self.xpos = 0」と「self.xpos = 0」は、壁1つの横幅と縦幅を表します。

24行目の「self.diff = 10」は、キーボードの1回の入力でどれだけプレイヤーを進めるかを指定する変数です。

25、26行目の「self.evt = None」と「self.time = 0」は、時間を計測するために使用する変数です。

27行目の「self.bind(size=self.drawBlocks)」は、FieldWidgetのサイズが変更されたときに後述するdrawBlocksを実行するように設定しています。

28行目~31行目の「_keyboard_closed」は、Window.request_keyboardのコールバック関数です。キーボード入力を終了させる関数です。

32行目~40行目の「_on_keyboard_down」は、キーボードが押されたときに実行する関数です。今回は方向キーの「left」、「up」、「right」と「down」が押されたときに対して処理を行います。確認用に各キーが押されたときにその方向がプリントされるようにしています。(未完成)

41行目の「calcPos」は、プレイヤーの座標から盤面の座標を求める関数です。壁との当たり判定に使用します。(未完成)

43行目の「drawBlocks」は、実際に盤面を表示する関数です。(未完成)

45行目の「countUp」は、経過秒数をラベルへ代入する関数です。時間を計測するときに使用します。(未完成)

 

49行目~53行目の「PlayWidget」は、プレイ画面を表すクラスです。レイアウトはkivyファイルで表現できるため、タイトル画面へ戻るためのボタンの関数のみを作成します。

54行目の「on_press_resetbtn」は、タイトル画面へ戻る用のリセットボタンが押されたときに行う関数です。(未完成)

 

65行目~79行目の「gotoPlayWidget」は、プレイ画面に移動するための関数です。

66行目の「pw = PlayWidget()」は、プレイ画面のクラスを生成しています。

67行目~75行目は、それぞれのレベルに合わせた盤面を生成しています。

pw.ids.fw.field」は、後述するkivyファイルで設定されたPlayWidgetに含まれるid(fw)のfieldを指しています。

76行目は、FieldWidgetのrowとcolに数値を代入しています。

77行目は、FieldWidgetのgoalposに座標を代入しています。

78行目の「self.clear_widgets()」は、RootWidgetに追加されているオブジェクトを全てクリアしています。

79行目の「self.add_widget(pw)」は、PlayWidgetをRootWidgetに追加しています。

 

 

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

3行目~11行目の「<PlayerWidget>」は、プレイヤーのデザインです。円で表しています。

4、5行目の「size_hint: [None, None]」と「size: [25, 25]」は、プレイヤーのサイズを設定しています。サイズは絶対指定にするためsize_hintはNoneにしてあります。

7、8行目は、プレイヤーの色を指定しています。(グレー)

9、10、11行目の「Ellipse」は、円を作成しています。

 

13行目~19行目の「<BlockWidget>」は、壁のデザインです。

15、16行目は、壁の色を設定しています。(ほぼ黒)

17、18、19行目の「Rectangle」は、BlockWidget自身の横幅と縦幅の長方形を設定しています。

 

21行目~27行目の「<GoalWidget>」は、ゴールのデザインです。

23、24行目は、ゴールの色を設定しています。(赤)

25、26、27行目の「Rectangle」は、GoalWidget自身の横幅と縦幅の長方形を設定しています。

 

29行目~61行目の「<PlayWidget>」は、プレイ画面のデザインです。

30行目~35行目は、背景を真っ白に設定しています。

36行目の「orientation: “vertical”」は、垂直方向にウィジェット追加するということを設定しています。

37行目の「BoxLayout」は、タイマー、リセットボタンとリターンボタンを配置するためのレイアウトです。

38行目の「size_hint_y: 1」は、相対的に前述のBoxLayoutの縦幅を指定しています。

 

39行目~48行目の「Label」は、経過時間を表示するデザインです。

40行目~45行目は、背景色を黒に設定しています。

46行目の「id: timelb」は、このラベルにtimelbというidを設定しています。このidに接続することでtextなどを変更することができます。

47行目の「font_size: 36」は、ラベルの文字サイズを36に設定しています。

48行目の「text: “Start”」は、ラベルの初期文字をStartに設定しています。

 

49行目~53行目の「Button」は、リセットボタンのデザインです。

50行目の「id: resetbtn」は、このボタンにresetbtnというidを設定しています。

51行目の「font_size: 36」は、このボタンの文字のサイズを36に設定しています。

52行目の「text: “Reset”」は、このボタンに表示される文字列を設定しています。

53行目の「on_press: root.on_press_resetbtn()」は、リセットボタンが押されたときの処理を設定しています。

 

54行目~58行目の「Button」は、タイトルへ戻るボタンのデザインです。

55行目の「id: returnbtn」は、このボタンにreturnbtnというidを設定しています。

56行目の「font_size: 36」は、このボタンの文字サイズを36に設定しています。

57行目の「text: “Return”」は、このボタンに表示される文字列を設定しています。

58行目の「on_press: app.root.gotoTitle()」は、このボタンが押されたときの処理を設定しています。

 

59行目~61行目の「FieldWidget」は、盤面のデザインです。盤面の表示自体はpythonファイルで行うため、idの設定と縦幅の設定を行います。

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

61行目の「size_hint_y: 9」は、FieldWidgetの縦幅を相対的に設定しています。ボタンなどを表示するBoxLayoutでは1を設定したため、縦幅が「BoxLayout:FieldWidget = 1:9」で表示されます。

 

88、94、100行目の「on_press」は、ボタンが押されたときプレイ画面が表示されるように設定しています。また、レベル別にしたいため自身のlevelを代入しています。

 

 

スポンサードサーチ


動作確認

動作確認を表す画像

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

python labyrinthgame.py

 

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

プレイ画面をキャプチャした画像

 

また、リセットボタンを押すとコマンドプロンプトに「Reset」が表示されるかの確認とリターンボタンを押すとタイトル画面へ戻るかの確認も行います。

さらに、プレイ画面を表示した状態で方向キーのどれかを押すとそれに対応した文字が表示されることを確認します。

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

 

 

まとめ

まとめの画像

今回は、プレイ画面のレイアウトとボタンを作成しました。レイアウトの構造自体は複雑ではなかったため、理解できたと思います。

ボタンが押されたときの処理を設定する方法が、少し特殊だったと思います。

 

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

 

次回は、プレイ画面に盤面とプレイヤーを表示させる関数(drawBlocks)とプレイヤーの座標を計算する関数(calcPos)を作成します。

kivyで表示させる座標とnp.arrayの座標がことなり、複雑になるため少し難しくなります。

 

 

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


スポンサードサーチ