乒乓球教程

背景

这篇文章将学到:

1,root和self

2,画布的创建

3,CoCk对象对函数的延时

介绍

欢迎来到乒乓球教程

本教程将教您如何使用 Kivy 编写 pong。我们将从 一个基本应用程序,如创建应用程序并转动中所述的应用程序 它变成了一个可玩的乒乓球游戏,描述了一路上的每一步。

以下是开始本教程之前的检查清单:

  • 您有一个有效的 Kivy 安装。

  • 您知道如何运行基本的 Kivy 应用程序。

准备?好了吗,让我们开始吧!

开始

让我们从启动并运行一个非常简单的 Kivy 应用程序开始。创建一个 游戏的目录和名为 main.py 的文件

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


class PongGame(Widget):
    pass


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


if __name__ == '__main__':
    PongApp().run()

继续运行应用程序。此时它应该只显示一个黑色窗口 点。我们所做的是创建一个非常简单的 Kivy , 它创建了 Widget 类的实例并将其返回为 应用程序 UI 的根元素,您应该在此想象 点作为 Widgets 的分层树。Kivy 将这个小部件树放在 默认窗口。在下一步中,我们将绘制 乒乓球的背景和分数通过定义外观。PongGamePongGame widget

添加简单的图形

创建 pong.kv

我们将使用 .kv 文件来定义类的外观。 由于我们的类被称为 ,我们可以简单地创建一个文件 在将自动加载的同一目录中调用 当应用程序运行时。因此,创建一个名为“pong.kv”的新文件并添加 以下内容。PongGamePongApppong.kv

#:kivy 1.0.9
<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 文件的名称(例如 pong.kv)必须与应用程序的名称匹配, 例如 PongApp(应用程序结束之前的部分)。

如果您现在运行该应用程序,您应该会在中间看到一个垂直条,以及两个 显示玩家分数的零。

解释 Kv 文件语法

在继续下一步之前,您可能需要仔细查看 我们刚刚创建的 KV 文件的内容,并弄清楚发生了什么。 如果你了解发生了什么,你可能会跳到下一个 步。

在第一行,我们有:

#:kivy 1.0.9

每个 kv 文件中都需要第一行。它应该以开头,后跟一个空格和它所针对的 Kivy 版本(以便 Kivy 可以制作 确保您至少具有所需的版本,或处理向后兼容性 稍后)。#:kivy

之后,我们开始定义应用于所有实例的规则:PongGame

<PongGame>:
    ...

与 Python 一样,kv 文件使用缩进来定义嵌套块。定义的块 在 和 字符内有一个类名是一条规则。它将应用于任何实例 命名类。如果在我们的示例中替换为 ,则所有 Widget 实例将包含垂直线和两个 Label 小部件 它们,因为它将为所有 Widget 实例定义这些规则。<>PongGameWidget

在规则部分中,您可以添加各种块来定义样式和 它们将应用于的小部件的内容。您可以:

  • 设置属性值

  • 添加子小组件

  • 定义一个画布部分,您可以在其中添加图形指令 定义小组件的呈现方式。

我们拥有的规则中的第一个块是画布块:<PongGame>

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

个人见解:

canvas中文翻译为画布Rectangle中文翻译为矩形,Canvas是用于绘制图形的一种方式,此代码就是在窗口中绘制了一个矩形的画布。

pos: self.center_x - 5, 0

固定位置:x坐标为窗口中心x坐标后移5个单位,y坐标为0

size: 10, self.height:

固定位置:宽度大小为5个单位,高度大小为窗口的高度


所以这个画布块说小部件应该画一些 图形基元。在本例中,我们在画布上添加一个矩形。我们设置 矩形的 POS 位于 水平中心左侧 5 个像素处 小部件,0 表示 y。矩形的大小设置为 10 像素 宽度,以及小部件的高度高度。定义 像这样的图形,是渲染的矩形将自动 当值表达式中使用的任何小部件的属性发生更改时更新。PongGame

注意

尝试调整应用程序窗口的大小并注意发生的情况。那是 对整个 UI 会自动调整大小的标准行为 Window 是根据元素的属性size_hint调整元素的大小。这 默认小部件size_hint是 (1,1),这意味着它将在两者中 100% 拉伸 x 方向和 y 方向,因此填充可用空间。 由于矩形的位置和大小以及分数的center_x和顶部 标签在 类的上下文中,这些属性将自动 当相应的小组件属性更改时进行更新。使用 Kv language 为您提供自动属性绑定。:)PongGame

我们添加的最后两个部分看起来非常相似。他们每个人都添加了一个标签 Widget 作为子 Widget 添加到 Widget。目前,文本 它们都只是设置为“0”。我们会将其与实际情况联系起来 一旦我们实现了逻辑,但标签已经得分 看起来不错,因为我们设置了更大的font_size,并将它们相对定位 到根小部件。关键字可以在子块内使用 请回溯到规则适用的父/根小部件( 在此 案例):PongGamerootPongGame

<PongGame>:
    # ...

    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"

个人见解

Widget中文翻译是部件,root指的是当前widget(部件)的根节点,也就是根部件,也可以理解为是窗口本身。在此kv文件中<PongGame>是一个自定义的widget(部件),所以root代指<PongGame>本身。懂了吗小朋友?

翻译一下:

center_x: root.width / 4

水平居中:<PongGame>的四分之一

center_x: root.width * 3 / 4

水平居中:<PongGame>的四分之三


添加球

添加球

好的,所以我们有一个基本的乒乓球竞技场可以玩,但我们仍然需要球员和 一个可以击球的球。让我们从球开始。我们将添加一个新的 PongBall 类来创建一个小部件,该小部件将成为我们的球并使其弹跳。

乒乓球课

下面是 PongBall 类的 Python 代码:

class PongBall(Widget):

    # 小球在x和y轴上的速度
    velocity_x = NumericProperty(0)
    velocity_y = NumericProperty(0)

    # 引用列表属性,这样我们可以使用 ball.velocity 来代替 velocity_x 和 velocity_y,
    # 就像使用 w.pos 来代替 w.x 和 w.y 一样
    velocity = ReferenceListProperty(velocity_x, velocity_y)

    # ``move`` 函数将使小球移动一步。这个函数将在相等的时间间隔内调用,以动画方式移动小球
    def move(self):
        self.pos = Vector(*self.velocity) + self.pos

这是用于将球绘制为白色圆圈的 kv 规则:

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

要使一切正常,您还必须添加所使用的 Properties Property 类的导入和 . 以下是此步骤的完整更新的 python 代码和 kv 文件:

main.py:

from kivy.app import App
from kivy.uix.widget import Widget
from kivy.properties import NumericProperty, ReferenceListProperty
from kivy.vector import Vector


class PongBall(Widget):
    velocity_x = NumericProperty(0)
    velocity_y = NumericProperty(0)
    velocity = ReferenceListProperty(velocity_x, velocity_y)

    def move(self):
        self.pos = Vector(*self.velocity) + self.pos


class PongGame(Widget):
    pass


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


if __name__ == '__main__':
    PongApp().run()

pong.kv:

#:kivy 1.0.9

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

<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"
    
    PongBall:
        center: self.parent.center

请注意,不仅添加了<PongBall>小部件规则,还添加了 <PongGame> 小部件规则中的子部件 PongBall。 


个人见解

Ellipse是创建椭圆形的意思,与上文中的Rectangle矩形有点区别哦,那么问题来了还有没有别的创建图形的代码?

当然有:

线段

Canvas:
    #线段
    Line:
        points: 100, 100, 200, 200, 300, 100

三角形

Canvas:
    #三角形
    Triangle:
        points: 100, 100, 200, 200, 300, 100

组合图形

Canvas:
    InstructionGroup:
        #椭圆形
        Ellipse:
            pos: 100, 100
            size: 50, 50
        #矩形
        Rectangle:
            pos: 200, 200
            size: 50, 50

接下来我们来看看self

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

self是部件本身的意思,理论上此代码的self是不是就是画布?但画布不属于部件,所有这里的self指的是<PongBall>本身。也就是和<PongBall>的size相同。

很棒啊,来我们继续,同理


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

    

中的self就是<PongGame>,self.center_x - 5就是<PongGame>中心点x往左偏移5个单位,self.height就是<PongGame>的高度。

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

中的root承接上文,代指窗口。所以top:root.top-50就是y的顶部坐标为窗口顶部坐标往上偏移50个单位。

    PongBall:
        center: self.parent.center

self指的是PongBall控件的实例,而self.parent指的是PongBall控件的父级对象。也就是<PongGame>,self.parent.center就是<PongGame>的中心点。切记pongball指向的是直接父级而不是根父级


添加球动画

让球移动

很酷,所以现在我们有一个球,它甚至有一个功能......但事实并非如此 搬家了。让我们解决这个问题。move

在时钟上调度功能

我们需要定期调用我们的球的方法。幸运的是,Kivy 通过让我们使用 并指定间隔来安排我们想要的任何函数,使这变得非常容易:move

Clock.schedule_interval(game.update, 1.0/60.0)

例如,这行将导致游戏对象的功能 每 60 秒调用一次(每秒 60 次)。update


 个人见解

在Kivy中,Clock对象提供了一系列用于调度和管理时间事件的方法。

对于这个CoCk对象还有其他方法,这里简单介绍一下其他方法。

延时执行函数,延时单位为秒:

Clock.schedule_once(函数, 延时)

 循环延时执行函数,延时单位为秒:

Clock.schedule_interval(game.update, 1.0/60.0)

返回当前的时间,单位为秒:

time = Clock.get_time()

返回当前应用程序的帧率: 

fps = Clock.get_fps()


对象属性/引用

不过,我们还有另一个问题。我们希望确保 PongBall 的函数被定期调用,但在我们的代码中,我们没有任何引用 添加到球对象,因为我们刚刚通过 kv 文件添加了它 在类的 KV 规则内。唯一参考我们的 游戏是我们在应用程序构建方法中返回的那个。movePongGame

因为我们要做的不仅仅是移动球(例如 把它从墙上弹开,然后从球员球拍上弹开),我们可能需要 无论如何,我们班的方法。此外,鉴于 我们已经有了一个对游戏对象的引用,我们可以在构建应用程序时轻松安排它的新方法:updatePongGameupdate

class PongGame(Widget):

    def update(self, dt):
        # 调用球的移动等操作
        pass

class PongApp(App):

    def build(self):
        game = PongGame()
        Clock.schedule_interval(game.update, 1.0/60.0)
        return game

个人见解 

class是创建类的标识,所有此代码中,创建了,两个类;其中第一个类PongGame是继承widget自定义部件,主要用于创建部件内的部件与其交互逻辑,说白了就是写与UI交互的代码的。第二个类PongApp是继承kivy的App程序类,用于配置和启动kivy的应用程序

class PongGame(Widget):

    def update(self, dt):
        # 调用球的移动等操作
        pass

如果有阅读多篇kivy项目,你会发现很多继承部件的类中,函数基本都带self参数,这是为什么嘞?原因是self所代指的是继承部件类本身,通过这个参数可以访问部件部件中的绝大部分参数,比如获取与设置文本的text值等等,还有添加部件到布局等,所以大都包含self的函数是为来方便对继承部件内部件进行交互操作。其次dt 代表时间间隔(delta time),这里并未声明时间间隔。


但是,这仍然没有改变这样一个事实,即我们没有对 kv 规则创建的子小部件的引用。为了解决这个问题,我们可以在 PongGame 类中添加一个,并将其连接到在 KV 规则。完成后,我们可以很容易地引用 ball 属性 在方法内部,甚至让它从边缘反弹:PongBallupdate

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

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

        # 反弹球的上下边界
        if (self.ball.y < 0) or (self.ball.top > self.height):
        self.ball.velocity_y *= -1

        # 反弹球的左右边界
        if (self.ball.x < 0) or (self.ball.right > self.width):
            self.ball.velocity_x *= -1

不要忘记在 kv 文件中挂接它,给子小部件一个 id 并将 PongGame 的 ObjectProperty 设置为该 id:ball

python
<PongGame>:
    ball: pong_ball

    # ... (画布和标签等)

    PongBall:
        id: pong_ball
        # 设置乒乓球的中心位置为其父部件(PongGame)的中心位置
        center: self.parent.center  

个人见解

         if (self.ball.y < 0) or (self.ball.top > self.height):
        self.ball.velocity_y *= -1

ball示是PongBall冰棒球的id,所以self.ball.y是id为ball的PongBall自定义部件的y坐标,其他同理。对了这里所指的自定义部件指的是在py中那个用来继承部件的类。

        center: self.parent.center

中的parent是父部件,不是根部件,所以center: self.parent.center就是设置乒乓球的居中位置在父部件的中中心。

        self.ball.velovity_y

中的 velocity_y 表示乒乓球(PongBall)在Y轴方向上的速度。这是一个自定义属性,说白了就是继承部件类里面的一个变量,参数而已。并不是通用的参数啊!


注意

在这一点上,一切都被连接起来,让球反弹。如果 你一边编码一边走,你可能想知道为什么球没有 随处移动。球的速度在 x 和 y 上都设置为 0。 在下面的代码列表中,一个方法是 添加到类中并在应用的方法中调用。它设置了一个 随机 x 和 y 速度,并且还重置了位置,所以我们 稍后可以使用它来重置球员得分时的球。serve_ballPongGamebuild

下面是此步骤的完整代码:

main.py:

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 PongBall(Widget):
    velocity_x = NumericProperty(0)  # x轴速度属性
    velocity_y = NumericProperty(0)  # y轴速度属性
    velocity = ReferenceListProperty(velocity_x, velocity_y)  # 速度属性列表

    def move(self):
        self.pos = Vector(*self.velocity) + self.pos  # 更新球的位置


class PongGame(Widget):
    ball = ObjectProperty(None)  # 球对象属性

    def serve_ball(self):
        self.ball.center = self.center  # 将球放置在游戏区中心
        self.ball.velocity = Vector(4, 0).rotate(randint(0, 360))  # 设置球的初始速度向量

    def update(self, dt):
        self.ball.move()  # 更新球的位置

        # 球与顶部和底部的碰撞
        if (self.ball.y < 0) or (self.ball.top > self.height):
            self.ball.velocity_y *= -1  # 反转y轴速度

        # 球与左侧和右侧的碰撞
        if (self.ball.x < 0) or (self.ball.right > self.width):
            self.ball.velocity_x *= -1  # 反转x轴速度


class PongApp(App):
    def build(self):
        game = PongGame()  # 创建游戏实例
        game.serve_ball()  # 发球
        Clock.schedule_interval(game.update, 1.0 / 60.0)  # 每帧更新游戏状态
        return game  # 返回游戏实例

if __name__ == '__main__':
    PongApp().run()  # 运行游戏

pong.kv:

#:kivy 1.0.9

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

<PongGame>:
    ball: pong_ball
    
    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"
    
    PongBall:
        id: pong_ball
        center: self.parent.center

连接输入事件

添加播放器并对触摸输入做出反应

很棒的,我们的球在蹦蹦跳跳。现在唯一缺少的是可移动的 球员球拍并跟踪比分。我们不会一一赘述 再次创建类和 kv 规则的细节,因为这些概念是 前面的步骤中已经介绍过。相反,让我们专注于如何移动 响应用户输入的播放器小部件。您可以获取整个代码和 kv 本节末尾的类规则。PongPaddle

在 Kivy 中,小部件可以通过实现 、 和 方法对输入做出反应。默认情况下,Widget 类 通过在其所有 子小部件传递事件,直到其中一个子项返回。True

乒乓球很简单。球拍只需要上下移动。事实上,它是 如此简单,我们甚至不需要让播放器小部件处理 事件本身。我们将只实现该类的函数,并让它根据左或右玩家的位置设置 关于触摸是发生在屏幕的左侧还是右侧。on_touch_movePongGame

检查处理程序:on_touch_move

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

我们将把每个玩家的分数保存在一个 .的分数标签通过更改 NumericProperty 来保持更新,这反过来又会更新 更新子 Labels 文本属性。此绑定 发生是因为 Kivy 自动绑定到任何引用 在它们相应的 KV 文件中。当球 从边路逃脱,我们将更新比分并发球 再次通过更改类中的方法。该类还实现了一个方法,以便球反弹 根据它击中球拍的位置而有所不同。以下是 PongPaddle 类的代码:PongGamescorePongGameupdatePongGamePongPaddlebounce_ball

class PongPaddle(Widget):

    score = NumericProperty(0)

    def bounce_ball(self, ball):
        if self.collide_widget(ball):
            speedup  = 1.1
            offset = 0.02 * Vector(0, ball.center_y-self.center_y)
            ball.velocity =  speedup * (offset - ball.velocity)

注意

这种球弹跳算法非常简单,但会有奇怪的行为 如果球从侧面或底部击中球拍......这是你可以做到的 如果你愿意,试着修复自己。

这是上下文。差不多完成了:

main.py:

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

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 PongBall(Widget):
    velocity_x = NumericProperty(0)  # x轴速度属性
    velocity_y = NumericProperty(0)  # y轴速度属性
    velocity = ReferenceListProperty(velocity_x, velocity_y)  # 速度属性列表

    def move(self):
        """
        根据球的速度属性移动球的位置
        """
        self.pos = Vector(*self.velocity) + self.pos

class PongGame(Widget):
    ball = ObjectProperty(None)  # 球对象属性
    player1 = ObjectProperty(None)  # 球拍1对象属性
    player2 = ObjectProperty(None)  # 球拍2对象属性

    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

        # 球出界得分
        if self.ball.x < self.x:
            self.player2.score += 1
            self.serve_ball(vel=(4, 0))
        if self.ball.right > 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

class PongApp(App):
    def build(self):
        """
        创建游戏实例并启动游戏循环
        """
        game = PongGame()
        game.serve_ball()
        Clock.schedule_interval(game.update, 1.0 / 60.0)
        return game

if __name__ == '__main__':
    PongApp().run()  # 运行游戏

pong.kv:

#:kivy 1.0.9

<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>:
    ball: pong_ball
    player1: player_left
    player2: player_right
    
    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: str(root.player1.score)
        
    Label:
        font_size: 70  
        center_x: root.width * 3 / 4
        top: root.top - 50
        text: str(root.player2.score)
    
    PongBall:
        id: pong_ball
        center: self.parent.center
        
    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

好的朋友,到这一步你已经很棒了。运行后就将得到这个程序

如下是打包后在安卓端运行的效果

  • 17
    点赞
  • 25
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值