【Python】マッチ棒クイズ開発 Part19

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

今回は、kvファイルを使ったマッチ棒を作成します。kvファイルに記述出来るコードとできないコードを解説します。また、pythonファイルのみで作成したときとの違いも解説します。

 

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

メールまたはTwitterのDMまで!!

 

 

 

スポンサードサーチ


スポンサードサーチ


マッチ棒

ここではpythonファイルでマッチ棒の作成とkvファイルでマッチ棒の初期化と表示を作成します。

pythonファイルではマッチ棒のクラスを宣言するだけであり、今の段階では関数などは定義しません。

kvファイルではinit関数で行っていたことを記述します。sizeの指定やcanvasへの追加などを記述します。

 

 

コードの提示

kvファイルでマッチ棒を表現するクラスのソースコードを以下に示します。

# -*- coding: utf-8 -*-
#:import np numpy

<MatchStickWidget>:
    size: [0, 0]
    past_pos: (0, 0)
    before_move_pos: (0, 0)
    ms_w: 25
    angle: 0
    origin: (self.x + self.ms_w / 2, self.y + self.ms_w * 6.8 / 2)
    # Rotate Theta
    theta: np.radians(self.angle)
    new_x: lambda t: (t[0] - self.origin[0]) * round(np.cos(self.theta), 3) - (t[1] - self.origin[1]) * round(np.sin(self.theta), 3) + self.origin[0]
    new_y: lambda t: (t[0] - self.origin[0]) * round(np.sin(self.theta), 3) + (t[1] - self.origin[1]) * round(np.cos(self.theta), 3) + self.origin[1]
    new_p: lambda t, o=(0, 0): (self.new_x(t) + o[0], self.new_y(t) + o[1])
    # Stick
    posStick: [self.x+self.ms_w*0.2, self.y]
    sizeStick: [self.ms_w * 0.6, self.ms_w * 5.6]
    pointsStick: [(self.posStick[0], self.posStick[1]), (self.posStick[0] + self.sizeStick[0], self.posStick[1]), (self.posStick[0], self.posStick[1] + self.sizeStick[1]), (self.posStick[0] + self.sizeStick[0], self.posStick[1] + self.sizeStick[1])]
    rotatedPointsStick: sorted([self.new_p(point) for point in self.pointsStick], key=lambda t: t[1])
    # Powder
    posPowder: [self.x, self.y+self.ms_w*5.2]
    sizePowder: [self.ms_w, self.ms_w*1.6]
    canvas:
        PushMatrix:
        Rotate:
            group: "rot"
            angle: self.angle
            origin: (self.x + self.ms_w / 2, self.y + self.ms_w * 6.8 / 2)
        Color:
            rgb: [0.96, 0.87, 0.7]
        Rectangle:
            group: "stick"
            pos: [self.x+self.ms_w*0.2, self.y]
            size: [self.ms_w * 0.6, self.ms_w * 5.6]
        Color:
            rgb: [0.8, 0.1, 0]
        Ellipse:
            pos: [self.x, self.y+self.ms_w*5.2]
            size: [self.ms_w, self.ms_w*1.6]
        PopMatrix:


<RootWidget>:
    canvas:
        Color:
            rgb: [1, 1, 1]
        Rectangle:
            pos: self.pos
            size: self.size

  

pythonファイルでマッチ棒を表現するクラスのソースコードを以下に示します。

import kivy
kivy.require('2.0.0')

from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.widget import Widget

from kivy.lang import Builder
Builder.load_file('matchstickquiz.kv')


class MatchStickWidget(Widget):
    pass

class RootWidget(FloatLayout):
    pass


class MatchStickQuizApp(App):
    def build(self):
        root = RootWidget()
        root.add_widget(MatchStickWidget())
        return root


if __name__ == '__main__':
    MatchStickQuizApp().run()
    pass

  

上記のコードは以下からダウンロードできます。

 

 

コードの解説 kvファイル

1行目の「# -*- coding: utf-8 -*-」は、記述している文字コードを設定しています。基本的には設定しなくても問題ありませんが、エディタによってはShift-JISを使っていることもあるため心配な方は設定しておくと安心です。

 

2行目の「#:import np numpy」は、numpyをインポートしています。この書き方はkv特有のもであり、「import 略称 ライブラリ名」という順番で記述します。pythonでのインポートと異なるため気を付けましょう。

詳しくは以下のリンクから確認してください。

 

4行目から41行目の「<MatchStickWidget>:」は、マッチ棒を表現するクラスです。

 

5行目の「size: [0, 0]」は、マッチ棒のサイズを横幅縦幅ともに0を代入しています。

 

6行目の「past_pos: (0, 0)」は、過去にいた座標を保持する変数です。初期値として0を代入しています。

 

7行目の「before_move_pos: (0, 0)」は、移動前の座標を保持する変数です。初期値として0を代入しています。

 

8行目の「ms_w: 25」は、マッチ棒の横幅を保持する変数です。初期値として25を代入しています。

 

9行目の「angle: 0」は、マッチ棒の角度を保持する変数です。初期値として0を代入しています。

 

10行目の「origin」は、マッチ棒の回転の中心を保持する変数です。計算はPart7で解説しています。

 

12行目の「theta」は、マッチ棒の角度をラジアン単位に変換して保持する変数です。

 

13行目の「new_x」は、回転後のx座標を求める無名関数です。詳しくはPart8で解説しています。

 

14行目の「new_y」は、回転後のy座標を求める無名関数です。詳しくはPart8で解説しています。

 

15行目の「new_p」は、new_xとnew_yを用いて回転後の座標を求める無名関数です。詳しくはPart8で解説しています。

 

17行目の「posStick」は、マッチ棒の棒部分の表示座標を保持する変数です。計算はPart5で解説しています。

 

18行目の「sizeStick」は、マッチ棒の棒部分のサイズを保持する変数です。計算はPart5で解説しています。

 

19行目の「pointsStick」は、マッチ棒の棒部分の各頂点を保持する変数です。計算はPart8で解説しています。

 

20行目の「rotatedPointsStick」は、マッチ棒の棒部分の回転後の各頂点を保持する変数です。計算はPart8で解説しています。

 

22行目の「posPowder」は、マッチ棒の火薬部分の座標を保持する変数です。計算はPart5で解説しています。

 

23行目の「sizePowder」は、マッチ棒の火薬部分のサイズを保持する変数です。計算はPart5で解説しています。

 

24行目の「canvas:」は、canvasオブジェクトを宣言しています。この宣言以降でインデントを1つ下げるとcanvasへ追加するという扱いになります。

 

25行目の「PushMatrix:」は、回転する範囲の始まりを宣言しています。後述するPopMatrixとで囲まれたオブジェクトは一緒に回転します。

 

26行目の「Rotate」は、回転するために必要なオブジェクトです。角度や回転の中心を設定します。

27行目の「group: ‘rot’」は、Rotateオブジェクトにrotグループという属性を付与しています。pythonファイル側からRotateオブジェクトを呼び出す時に使用します。

28行目の「angle」は、角度を保持する変数です。

29行目の「origin」は、回転の中心を保持位する変数です。計算はPart7で解説しています。

  

30行目の「Color」は、後述するRectangleの色を設定するオブジェクトです。

31行目では、色をベージュに設定しています。

 

32行目の「Rectangle」は、マッチ棒の棒部分を表現するオブジェクトです。

33行目の「group: ‘stick’」は、Rectangleオブジェクトにstickグループという属性を付与しています。pythonファイル側からRectangleオブジェクトを呼び出す時に使用します。

34行目の「pos」は、マッチ棒の棒部分の座標を保持する変数です。35行目の「size」は、マッチ棒の棒部分のサイズを保持する変数です。

 

36行目の「Color」は、後述するEllipseの色を設定するオブジェクトです。

37行目では、色を赤に設定しています。

 

38行目の「Ellipse」は、マッチ棒の火薬部分を表現するオブジェクトです。

39行目の「pos」は、マッチ棒の火薬部分の座標を保持する変数です。

40行目の「size」は、マッチ棒の火薬部分のサイズを保持する変数です。

41行目の「PopMatrix」は、回転する範囲の終わりを宣言しています。前述したPushMatrixとで囲まれたオブジェクトは一緒に回転します。

 

 

コードの解説 pythonファイル

pythonに書かれているコードは、ほとんどこれまでのPartで解説しているため、新しく記述したコードや気を付けて欲しいコードについて解説します。

 

8行目の「import文」は、Builderをインポートしています。このクラスは、kvファイルを読み込むためのクラスです。

 

9行目の「Builder.load_file(‘matchstickquiz.kv’)」は、matchstickquiz.kvファイルを読み込むということを行っています。

 

MatchStickWidgetやRootWidgetはクラスの中身がほとんどkvファイル側で設定しているため、passのみとなっています。

 

 

動作確認

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

python matchstickquiz.py

 

以下のように表示されれば動作確認完了です。

  

 

スポンサードサーチ


スポンサードサーチ


マッチ棒

ここでは、マッチ棒クラスの関数を作成します。

具体的には、あたり判定と動かす関数と重なっているか判定する関数を作成します。

マッチ棒を表示する関数(drawMS)と回転したときの関数(rotateTheta)の2つの関数を書かなくてよくなります。

  

 

コードの提示

pythonファイルに関数を追記したソースコードを以下に示します。

import kivy
kivy.require('2.0.0')

from kivy.app import App
from kivy.uix.floatlayout import FloatLayout
from kivy.uix.widget import Widget

from kivy.lang import Builder
Builder.load_file('matchstickquiz.kv')

import numpy as np

class MatchStickWidget(Widget):
    def __init__(self, ms_w, angle, **kwargs):
        super(MatchStickWidget, self).__init__(**kwargs)
        self.ms_w = ms_w
        self.angle = angle
        self.rot = self.canvas.get_group("rot")[0]
        self.stick = self.canvas.get_group("stick")[0]
    def collide_point_stick(self, touch, *args):
        fml = lambda a, t: a * (touch[0] - t[0]) + t[1]
        xmini, xmax = min([p[0] for p in self.rotatedPointsStick]), max([p[0] for p in self.rotatedPointsStick])
        ymini, ymax = min([p[1] for p in self.rotatedPointsStick]), max([p[1] for p in self.rotatedPointsStick])
        if((xmini <= touch[0] and touch[0] <= xmax
            and ymini <= touch[1] and touch[1] <= ymax)
            and ((fml(-1, self.rotatedPointsStick[0]) <= touch[1] and fml(1, self.rotatedPointsStick[0]) <= touch[1]
            and touch[1] <= fml(-1, self.rotatedPointsStick[3]) and touch[1] <= fml(1, self.rotatedPointsStick[3]))
            or  round(self.rotatedPointsStick[0][1]) == round(self.rotatedPointsStick[1][1]))):
            return True
        return False
    def collide_point_powder(self, touch, *args):
        origin_elli = self.new_p((self.origin[0], self.posPowder[1] + self.sizePowder[1] / 2)) # window上の座標(楕円の中心)を代入
        num_x = ((touch[0] - origin_elli[0]) * np.cos(self.theta) + (touch[1] - origin_elli[1]) * np.sin(self.theta))**2
        num_y = ((touch[1] - origin_elli[1]) * np.cos(self.theta) - (touch[0] - origin_elli[0]) * np.sin(self.theta))**2
        value = num_x / (self.sizePowder[0]/2)**2 + num_y / (self.sizePowder[1]/2)**2
        if(value <= 1):
            return True
        return False
    def on_touch_down(self, touch, *args):
        tof = (self.collide_point_stick(touch.pos) or self.collide_point_powder(touch.pos))
        if(touch.is_double_tap and tof):
            self.angle += 45
            return True
        elif(not(touch.is_double_tap) and tof):
            touch.grab(self)
            self.past_pos = touch.pos
            return True
        return False
    def on_touch_up(self, touch, *args):
        if(touch.grab_current is self):
            touch.ungrab(self)
            return True
        return False
    def on_touch_move(self, touch, *args):
        if(touch.grab_current is self):
            self.x += touch.x - self.past_pos[0]
            self.y += touch.y - self.past_pos[1]
            self.past_pos = touch.pos
            return True
        return False
    def collide_center_circle(self, wid, *args):
        diff_x = self.rot.origin[0] - wid.rot.origin[0]
        diff_y = self.rot.origin[1] - wid.rot.origin[1]
        distance = np.sqrt(diff_x**2 + diff_y**2)
        if(self != wid and distance <= self.stick.size[0] and self.angle % 180 == wid.angle % 180):
            return True
        return False

class RootWidget(FloatLayout):
    pass


class MatchStickQuizApp(App):
    def build(self):
        root = RootWidget()
        root.add_widget(MatchStickWidget(25, 0))
        return root


if __name__ == '__main__':
    MatchStickQuizApp().run()
    pass

 

上記のコードは以下からダウンロードできます。

 

 

コードの解説 pythonファイル

追記した関数の内、init関数のみが以前のinit関数と異なるため、init関数のみ解説します。

 

14行目から19行目の「init関数」は、MatchStickWidgetを初期化する関数です。

 

16行目の「self.ms_w」は、マッチ棒の横幅を保持する変数です。kvファイルにて初期値を25としていますが、こちらに代入した値が優先されます。

 

17行目の「self.angle」は、マッチ棒の角度を保持する変数です。kvファイルにて初期値を0としていますが、こちらに代入した値が優先されます。

 

18行目の「self.rot = self.canvas.get_group(“rot”)[0]」は、self.rotにkvファイルで宣言したRotateオブジェクトを代入しています。

get_group関数はcanvas上のオブジェクトでrotというグループが付与されている全てのオブジェクトをリストで取得しています。

rotというグループが付与されているオブジェクトはRotateしかないため、0番目を指定してRotateオブジェクトを取得しています。

 

19行目の「self.stick = self.canvas.get_group(“stick”)[0]」は、self.stickにkvファイルで宣言したRectangleオブジェクトを代入しています。

get_group関数はcanvas上のオブジェクトでstickというグループが付与されている全てのオブジェクトをリストで取得しています。

stickというグループが付与されているオブジェクトはRectangleしかないため、0番目を指定してRectangleオブジェクトを取得しています。

  

 

 

動作確認

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

python matchstickquiz.py

 

以下のように動かすこと回転させることを確認できたら動作確認完了です。

 

  

スポンサードサーチ


まとめ

今回は、pythonファイルとkvファイルを使ってマッチ棒クラスを作成しました。

kvファイルを使うことで関数を削減することができました。pythonファイルに記述するコードの量は減ったと思います。

 

次回Part20は、マッチ棒枠とマッチ棒を動かす画面を作成します。

 

 

  

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


スポンサードサーチ