目录
前言:
一、规则的简单说明
(1)关于碰撞
(2)关于计分
二、关于规则的改进
(1)关于小球运行
(2)关于智障AI的添加
(3) 关于界面的美化
(4)添加背景音效和音乐
三、其他细节的强调
(1)widget组件
(2)kv中的画布和标签
四、完整代码
五、不足总结
这还是我学校学习课程上的一个项目,因为我也要考研,就没有花费太多时间去做很好的项目。所以基于网上的一个乒乓球游戏做了一些简单的改进。如下是我参考的文档和源码下载地址,大家有需要可以自取:
GitHub - attreyabhatt/Kivy-Pong-Game: Pong game created using Kivyhttps://github.com/attreyabhatt/Kivy-Pong-Game
如果想要了解我做的这个项目,最后可以先看下面这个文档,介绍了它最基本的运行逻辑:
乒乓球游戏教程 — Kivy 2.1.0 文档https://kivy.org/doc/stable/tutorials/pong.html
众所周知,弹球游戏的碰撞逻辑:当小球碰到板子或边界会进行反弹,碰到上下边界要反转竖直速度,碰到左右边界要反转水平速度。
对于计分规则更是多种多样,首先你可以定义碰到板子就加分,或者碰到己方边界,给对面加分等等。分值也是可以进行设定的。
小球的运行过程中是符合逻辑的,这样会使得玩家在游玩的时候会预判到小球的走向,大大降低了难度,所以我在这里设置了一个无规则运行的逻辑,同时球的速度和大小会随时间进行随机改变。下面用代码说明一下:
我先定义了几个参数,方便后续对速度和大小的改变:
speed_x = NumericProperty(0) # 球的水平速度属性
speed_y = NumericProperty(0) # 球的垂直速度属性
speed = ReferenceListProperty(speed_x, speed_y) # 球的速度向量属性
r = NumericProperty(0) #球的大小属性
max_speed = 5 # 球的最大速度
接着,move函数是对小球的位置进行实时更新的。而randomize函数则是小球的速度是不断变化的,其中vel_y和vel_x正数就是代表向上和向右的速度,负数反之。daxiao函数让球在20*20到80*80之前随机变化。
def move(self):
self.pos = Vector(*self.speed) + self.pos # 根据速度更新球的位置
def randomize(self):
# 随机化速度
vel_x = uniform(-5, 5) # 在[-5, 10]范围内生成一个随机数作为水平速度
vel_y = uniform(-5, 5) # 垂直速度
self.speed = Vector(vel_x, vel_y).normalize() * self.max_speed # 设置球的速度向量,并将其归一化后乘以最大速度
def daxiao(self):
# 随机化大小
r = uniform(20, 80)
self.size = r , r # 在[20, 80]范围内生成一个随机数作为球的大小
print(self.size)
接着在后续的类中进行调用
def serve_ball(self):
self.ball.center = self.center # 将球的中心位置设置为窗口的中心位置
self.ball.randomize() # 随机化球的速度
self.ball.daxiao() # 随机化球的大小
self.timer = 0 # 计时器初始化为0
为什么叫它智障AI呢?顾名思义,就是它的实现逻辑太简单了,我们只需要让其中一个板子的中心跟着球的中心移动就行了,并且设置好运行速度,是不是很简单阿巴巴(• •)~
def move_player2(self):
# 根据球的位置移动玩家2的挡板
if self.ball.center_y < self.player2.center_y:
self.player2.center_y -= 5 # 将玩家2的挡板向下移动5个单位
elif self.ball.center_y > self.player2.center_y:
self.player2.center_y += 5
然后记得在前面调用哦,这样板子就会随着我们的小球去动了,这样就不用自己一个人控制两个玩家了。当然你也可以把player1的板子也设置成自动的,这样就可以看两个板子自己玩了。
关于界面的美化我们主要是修改kv文件,以下我的完整的kv文件,我们可以设置大小、颜色等等,对于其中的具体参数,大家可以去网上查阅参考。
:
canvas:
Color:
rgba: .9,.9,.1 ,9
Ellipse:
pos: self.pos
size: self.size
source: "出生.jpg"
:
size: 25, 150
canvas:
Color:
rgba: 1, 1, 1, .9
Rectangle:
pos: self.pos
size: self.size
source: "无标题.png"
:
ball: pong_ball
player1: player_left
player2: player_right
canvas:
Color:
rgba:1, 1, 1, .9
Rectangle:
size:self.size
source:"233.jpg"
Color:
rgba: .2, 1, 1, .9
Rectangle:
pos: self.center_x - 5, 0
size: 10, self.height
Label:
color: .9,.6,.1 ,9
font_size: 70
center_x: root.width / 4
top: root.top - 50
text: str(root.player2.score)
Label:
color: .8,.1,.1 ,9
font_size: 70
center_x: root.width * 3/4
top: root.top - 50
text: str(root.player1.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
下面是我用到的一些图片:
最终得到的界面效果如下:
对于添加音效和音乐,我们要使用kivy的一个库:
from kivy.core.audio import SoundLoader
我们只需要下面几行代码:
sound = SoundLoader.load('away.mp3')
if sound:
sound.loop = True # 设置循环播放
sound.play()
1. Widget不是布局,Widget不会改变其包含的子组件的位置与大小。
2. Widget的默认大小是100*100。
3. Widget的size_hint默认是1*1,如果其父组件是一个布局组件,则其大小是父组件中的布局需要的大小。
4. on_touch_down(), on_touch_move(), on_touch_up() 等函数不进行组件边界范围判定(碰撞判定),需要调用collide_point()。
Canvas:创建画布,下面是一些Canvas的属性介绍:
1 .add(): 向画布中添加一个图形对象,可以是图形或者组。
2. insert(): 在画布中指定位置之前插入一个图形对象。
3. remove(): 从画布中删除指定的图形对象。
4. clear(): 从画布中删除所有图形对象。
5. size: 画布的大小,其值决定了画布的尺寸。
6. pos: 画布的位置,其值决定了画布相对于父控件的位置。
7. opacity: 画布的不透明度,其值在0-1之间。
8. canvas.before: 在画布中的所有元素之前绘制的图形对象。
9. canvas.after: 在画布中的所有元素之后绘制的图形对象。
10. canvas.clear(): 清除整个画布。
11. canvas.ask_update(): 强制更新画布的内容。
12. canvas.export_to_png(): 将画布保存到PNG文件中。
Label标签:在Kivy中,Label小部件用于呈现文本,它仅支持ASCII和Unicode编码的字符串(不支持中文),在Label中,可以设置文本内容、字体、大小、颜色、对齐方式、换行、引用以及标记文字等内容
1. text:标签显示的文本
2. text_size:标签文本大小
3. font_name:要使用字体的文件名
4. font_size:文本的字体大小
5. bold:字体使用粗体
6. italic:字体使用斜体
7. color:字体颜色,格式为rgba,默认为白色[1,1,1,1]
8. halign:文本的水平对齐方式,可选参数为:left、center、right、justify
9. valign:文本的垂直对齐方式,可选参数为:bottom、middle(或center)、top
10. markup:是否分割所有标记的文本
11. underline:在文本上添加下划线
12. padding_x:小部件框内文本的水平填充
13. padding_y:小部件框内文本的垂直填充
14. texture:文本的纹理对象,属性更改时会自动呈现文本
15. texture_size 文本的纹理大小,由字体大小和文本确定
16. strikethrough:在文本中添加删除线
17. strip:是否删除空格以及换行符
18. outline_color:文本轮廓的颜色
19. outline_width:文本周围轮廓的宽度,单位为像素
20. max_line:要使用的最大行数,默认为0,表示无限制
21. shorten:是否应该尽可能缩短文本内容,默认为False
22. shorten_from:在哪一侧缩短文本,默认为center,可选参数为:left、right和center
23. is_shortend:是否以缩短时间的方式进行渲染
24. line_height:文本的行高,默认为1.0
注释我已经给大家写的很详细了,大家有问题可以随时留言提问哦或者私聊我
py代码:
from random import randint, uniform # 导入randint和uniform函数用于生成随机数
from kivy.app import App # 导入App类,用于创建应用
from kivy.clock import Clock # 导入Clock类,用于调度函数的定时器
from kivy.core.audio import SoundLoader
from kivy.properties import NumericProperty, ReferenceListProperty, ObjectProperty # 导入各种属性类,用于定义属性
from kivy.uix.widget import Widget # 导入Widget类,用于创建控件
from kivy.vector import Vector # 导入Vector类,用于处理向量运算
from kivy.core.window import Window
class PongPaddle(Widget):
score = NumericProperty(0) # 分数属性,用于记录得分
def bounce_ball(self, ball):
if self.collide_widget(ball): # 如果球与挡板相撞
ball.speed_x *= -1.1 # 改变球的水平速度,反弹出去
class PongBall(Widget):
speed_x = NumericProperty(0) # 球的水平速度属性
speed_y = NumericProperty(0) # 球的垂直速度属性
speed = ReferenceListProperty(speed_x, speed_y) # 球的速度向量属性
r = NumericProperty(0) #球的大小属性
max_speed = 5 # 球的最大速度
def move(self):
self.pos = Vector(*self.speed) + self.pos # 根据速度更新球的位置
def randomize(self):
# 随机化速度
vel_x = uniform(-5, 5) # 在[-5, 10]范围内生成一个随机数作为水平速度
vel_y = uniform(-5, 5) # 垂直速度
self.speed = Vector(vel_x, vel_y).normalize() * self.max_speed # 设置球的速度向量,并将其归一化后乘以最大速度
def daxiao(self):
# 随机化大小
r = uniform(20, 80)
self.size = r , r # 在[20, 80]范围内生成一个随机数作为球的大小
print(self.size)
class PongGame(Widget):
ball = ObjectProperty(None) # 球的实例对象属性
player1 = ObjectProperty(None) # 玩家1挡板的实例对象属性
player2 = ObjectProperty(None) # 玩家2挡板的实例对象属性
music = None
def serve_ball(self):
self.ball.center = self.center # 将球的中心位置设置为窗口的中心位置
self.ball.randomize() # 随机化球的速度
self.ball.daxiao() # 随机化球的大小
self.timer = 0 # 计时器初始化为0
def update(self, dt):
self.ball.move()
if self.timer >= 3: # 如果计时器大于等于3秒
self.ball.randomize() # 随机化球的速度和大小
self.ball.daxiao()
self.timer = 0 # 重置计时器为0
else:
self.timer += dt # 更新计时器的值,增加经过的时间
# 球与上下边界的碰撞检测,如果球的底部超出窗口底部或者球的顶部超出窗口顶部
if (self.ball.y < 0) or (self.ball.top > self.height):
self.ball.speed_y *= -1 # 反转球的垂直速度,使其向相反的方向运动
# 球与左边界的碰撞检测,如果球的左边超出窗口左边界
if self.ball.x < 0:
self.ball.speed_x *= -1 # 反转球的水平速度,使其向相反的方向运动
sound1 = SoundLoader.load('你好.mp3')
if sound1:
#sound1.loop = True
sound1.play()
self.player2.score += 1 # 2 + 1
# 球与右边界的碰撞检测,如果球的右边超出窗口右边界
if self.ball.right > self.width:
self.ball.speed_x *= -1 # 反转球的水平速度,使其向相反的方向运动
sound2 = SoundLoader.load('村民叫.mp3')
if sound2:
#sound2.loop = True # 设置循环播放
sound2.play()
self.player1.score += 1 # 1 + 1
self.player1.bounce_ball(self.ball)
self.player2.bounce_ball(self.ball)
self.move_player2()
def move_player2(self):
# 根据球的位置移动玩家2的挡板
if self.ball.center_y < self.player2.center_y:
self.player2.center_y -= 5 # 将玩家2的挡板向上移动5个单位
elif self.ball.center_y > self.player2.center_y:
self.player2.center_y += 5
def on_touch_move(self, touch):
if touch.x < self.width / 1 / 4:
self.player1.center_y = touch.y # 根据触摸点的y坐标移动玩家1的挡板的中心位置
class PongApp(App):
def build(self):
self.title = '一个弹球游戏嘿嘿嘿'
game = PongGame()
game.serve_ball() # 开始游戏,将球放在中心位置并随机化速度和大小
sound = SoundLoader.load('qsbl.mp3')
if sound:
sound.loop = True # 设置循环播放
sound.play()
Clock.schedule_interval(game.update, 1.0 / 60.0) # 使用Clock类的schedule_interval方法以1/60秒的间隔调用game.update函数
return game
PongApp().run() # 创建PongApp的实例对象并运行应用程序
kv代码:
:
canvas:
Color:
rgba: .9,.9,.1 ,9
Ellipse:
pos: self.pos
size: self.size
source: "出生.jpg"
:
size: 25, 150
canvas:
Color:
rgba: 1, 1, 1, .9
Rectangle:
pos: self.pos
size: self.size
source: "无标题.png"
:
ball: pong_ball
player1: player_left
player2: player_right
canvas:
Color:
rgba:1, 1, 1, .9
Rectangle:
size:self.size
source:"233.jpg"
Color:
rgba: .2, 1, 1, .9
Rectangle:
pos: self.center_x - 5, 0
size: 10, self.height
Label:
color: .9,.6,.1 ,9
font_size: 70
center_x: root.width / 4
top: root.top - 50
text: str(root.player2.score)
Label:
color: .8,.1,.1 ,9
font_size: 70
center_x: root.width * 3/4
top: root.top - 50
text: str(root.player1.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
仍有一些bug,比如速度过快会在边界一直卡死。对于难度的设置可以在做一个难度选择的界面,这样可以根据难度调整速度。
希望这些可以帮助大家~如果觉得帮助到了你,请留个赞给我吧~