『Python Kivy』官方乒乓球游戲示例解析


本篇文章用於對Kivy框架官方所給出的一個「乒乓球」小游戲的源碼進行簡單地解析。我會盡可能的將方方面面的內容都說清楚。在文章的最下方為官方所給出的這個小游戲的教程以及游戲源碼。

由於篇幅所限,本文只簡單介紹Kivy框架,對於Python語言的簡介,請大家通過別的文章進行學習。

Kivy簡介

Kivy 是基於Python編程語言的一種用於開發跨平台GUI客戶端程序的框架。可以打包成iOS、Android、Windows、OSX等多種平台。目前已經實現一次編碼,到處運行的目的。

Kivy框架主要包含兩種文件,一種為Python源碼文件,一種為KV文件。Python源碼文件用於控制程序的運行邏輯,KV文件用於定義用戶界面。

由於Kivy本質上是使用OpenGL進行界面的繪制的,而且通過對Kivy的經典應用「2048 by kivy」進行了簡單的運行,Kivy所開發出的應用,在性能上還是可圈可點的,從某種程度上來說,要比使用HTML5/JS的應用在性能以及穩定性上,要好很多。

關於Kivy框架的基本使用以及開發環境,可以參考我的另外兩篇文章:

創建應用

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

from kivy.properties import NumericProperty, ReferenceListProperty, ObjectProperty
from kivy.vector import Vector
from kivy.clock import Clock

from random import randint

class PongGame(Widget):
    pass

class PongApp(App):
    def build(self):
        return PongGame()

if __name__ == '__main__':
    PongApp().run()
  • App類是創建Kivy應用的基礎。我們可以認為它是整個應用的入口,如果在我們的應用中沒有引入這個類的話,我們的應用是沒有辦法運行起來的。
  • Widget類是創建一個「Widget」的基礎。Widget是我們繪制界面最重要的類,我們的界面元素都應該繼承它,不論是內置好的「文本框」還是用於繪制我們自定義界面元素的「畫布」,都需要引入這個類,才能夠正常工作。
  • PongGame是一個「界面元素」類,在整個Pong示例中,小球、球拍等,都是界面元素,所不同的僅僅是界面元素的層級關系。
  • PongApp是我們的「主程序」類,主要用於各種操作以及界面元素的交互等的調度。不建議在這里寫入太復雜的邏輯,因為如果把復雜邏輯寫在這里的話,隨着我們應用的不斷擴展、維護,這個類將逐步陷入「不可維護」的尷尬境地。
  • 最后一塊用於「啟動應用」。每一個Kivy應用都應該有這段代碼。

在Kivy中,我們使用XXXApp來控制應用的運行,使用XXX來控制應用的行為,比如PongApp以及PongGame

繪制界面

kv文件是kivy框架所使用的界面描繪文件,類似於c#的xaml。我們可以在kv文件中,添加各種由kivy框架本身所提供的控件,比如buttonlabel等。還可以在kv文件中使用canvas標簽,並在其中繪制各種圖形,比如矩形、圓形等

在kv文件中,對於界面的描繪,是__樹形__的,也就是說,在最頂層需要一個__根節點__用來作為整個應用的根。

#:kivy 1.8.0

<PongGame>:
    canvas:
        Rectangle:
            pos: self.center_x - 5, 0
            size: 10, self.height

    Label:
        font_size: 70
        center_x: root.width / 4
        top: root.top -50
        text: "0"
    Label:
        font_size: 70
        center_x: root.width * 3 / 4
        top: root.top - 50
        text: "0"

在上面的kv文件中,

  • 第一行#:kivy 1.8.0聲明當前kv文件中所用到的kivy的版本。(當前最新的版本是1.9.0 beta)
  • canvas向應用中添加了一個畫板,用於在界面中繪制乒乓球游戲的各種擋板之類的界面元素。
  • 之后在canvas中對畫板進行設置,包括畫板的位置、尺寸等。
  • 每一個根標簽,都將對應於在項目文件中的一個類。
  • 在最后加入兩個標簽,並在標簽中設置相應的屬性,這里包括字號、位置、默認文字。

添加小球

小球類

# ...

class PongBall(Widget):
    # 小球在x、y軸上的速度
    velocity_x = NumericProperty(0)
    velocity_y = NumericProperty(0)

    # 創建索引屬性列表,以精簡對坐標屬性的使用。
    velocity = ReferenceListProperty(velocity_x, velocity_y)

    # ``move`` 方法將移動小球一步。
    # 這個方法將會在需要小球移動的時候被調用。
    def move(self):
        self.pos = Vector(*self.velocity) + self.pos

# ...

  • 為了讓我們的小球會動,我們需要給小球一個位置,然后控制這個位置以一定的速度進行改變,從而實現讓小球移動的目的。
  • ReferenceListProperty 允許創建一個由其他屬性構成的__元組__。在這里,我們提供了兩個數值型屬性,用於標示坐標。
  • 在PongBall類中,我們提供用於控制速度與位置的類屬性,以及一個用於控制移動的方法。
  • kivy.vector.Vector(*largs) 用於表示一個矢量。利用矢量屬性與當前的位置,可以控制移動的方向以及速度。

繪制小球

<PongBall>:
    size: 50, 50
    canvas:
        Ellipse:
            pos: self.pos
            size: self.size

加入球拍

繪制球拍

<PongPaddle>:
    size: 25, 200
    canvas:
        Rectangle:
            pos:self.pos
            size:self.size

...

<PongGame>:
    ...

    player1: player_left
    player2: player_right

    ...

    PongPaddle:
        id: player_left
        x: root.x
        center_y: root.center_y
        
    PongPaddle:
        id: player_right
        x: root.width-self.width
        center_y: root.center_y

事件處理

class PongPaddle(Widget):
    score = NumericProperty(0)

    def bounce_ball(self, ball):
        if self.collide_widget(ball):
            vx, vy = ball.velocity
            offset = (ball.center_y - self.center_y) / (self.height / 2)
            bounced = Vector(-1 * vx, vy)
            vel = bounced * 1.1
            ball.velocity = vel.x, vel.y + offset

# ...

class PongGame(Widget):
    ball = ObjectProperty(None)
    player1 = ObjectProperty(None)
    player2 = ObjectProperty(None)

    def serve_ball(self, vel=(4, 0)):
        self.ball.center = self.center
        self.ball.velocity = vel

    def update(self, dt):
        self.ball.move()

        # 敲擊小球
        self.player1.bounce_ball(self.ball)
        self.player2.bounce_ball(self.ball)

        # 限定小球在屏幕中的上下邊界
        if (self.ball.y < self.y) or (self.ball.top > self.top):
            self.ball.velocity_y *= -1

        #went of to a side to score point?
        if self.ball.x < self.x:
            self.player2.score += 1
            self.serve_ball(vel=(4, 0))
        if self.ball.x > self.width:
            self.player1.score += 1
            self.serve_ball(vel=(-4, 0))

    def on_touch_move(self, touch):
        if touch.x < self.width / 3:
            self.player1.center_y = touch.y
        if touch.x > self.width - self.width / 3:
            self.player2.center_y = touch.y

參考資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM