现在 Play The Ball 这个小游戏现在已经有了背景音乐,有了小球,有了碰撞检测,接下来我们要做的就是摩擦摩擦。
我们有一块玻璃面板的图片,如下图所示:这些是我的图片素材。
在这里,因为再看了一点之后就自己动手去做了,所以没有使用小甲鱼的方法——建一个glass类来实现,而是直接绘画glass来实现,就和绘制背景一样。
现在我们的要求就是把这块玻璃面板加载到程序底部的中央位置,这个还是很好实现的。
from pygame.locals import *
from random import *
import pygame
import math
import time
import sys
class Ball(pygame.sprite.Sprite) : #继承动画精灵基类
def __init__ (self,grayball_image,position,speed,bg_size) :
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(grayball_image).convert_alpha()
self.rect = self.image.get_rect() #获得球的尺寸
self.rect.left , self.rect.top = position #将出现的位置赋给球
self.speed = speed #设置速度
self.width , self.height = bg_size[0] , bg_size[1] #获得活动边界,就是背景的边界
def move(self):
self.rect = self.rect.move(self.speed) #根据自身的速度进行移动
if self.rect.right < 0: #图片的右边已经超出边界的左边,即整个球已经出界
self.rect.left = self.width #让他从右边界回来
if self.rect.bottom < 0: #图片的底已经超出边界的上面
self.rect.top = self.height #让他从底部回来
if self.rect.left > self.width: #图片的左边已经超出边界的右边
self.rect.right = 0 #让他从左边回来
if self.rect.top > self.height: #如果图片的顶部已经超出边界的底部
self.rect.bottom = 0 #让他从顶部回来
#判断碰撞检测函数
def collide_check(item,target):
col_balls = [] #添加碰撞小球
for each in target: #对 target 中所有的目标小球进行检测
#两个球心之间的距离
distance = math.sqrt( math.pow( (item.rect.center[0] - each.rect.center[0]) , 2 ) + \
math.pow( (item.rect.center[1] - each.rect.center[1]) , 2) )
if distance <= ( item.rect.width + each.rect.width ) / 2: #如果距离小于等于两者间的半径之和也就是两个直径之和的一半
col_balls.append(each) #将这个发生碰撞的小球添加到列表中
return col_balls
def main() :
pygame.init() #初始化
#将所有图片的路径写入
bg_image = r"D:\Code\Python\Pygame\pygame9:摩擦摩擦\background.png" #背景图
grayball_image = r"D:\Code\Python\Pygame\pygame9:摩擦摩擦\gray_ball.png" #灰小球的图片
galss_image = r"D:\Code\Python\Pygame\pygame9:摩擦摩擦\glass.png" #玻璃板图片
running = True #为了以后而已有多种方法退出程序
#加载背景音乐
pygame.mixer.music.load(r"D:\Code\Python\Pygame\pygame8:播放声音和音效\bg_music.ogg")
pygame.mixer.music.set_volume(0.2) #设置音量
pygame.mixer.music.play() #播放背景音乐
#设置一个背景音乐完毕之后的结束事件
GAMEOVER = USEREVENT
pygame.mixer.music.set_endevent(GAMEOVER) #当音乐播完后,发送一个GAMEOVER事件
#加载四个音效
hole_music = pygame.mixer.Sound(r"D:\Code\Python\Pygame\pygame9:摩擦摩擦\loser.wav")
laugh_music = pygame.mixer.Sound(r"D:\Code\Python\Pygame\pygame9:摩擦摩擦\laugh.wav")
winner_music = pygame.mixer.Sound(r"D:\Code\Python\Pygame\pygame9:摩擦摩擦\winner.wav")
loser_music = pygame.mixer.Sound(r"D:\Code\Python\Pygame\pygame9:摩擦摩擦\loser.wav")
#设置背景
bg_size = width , height = 1024 , 681 #背景大小
screen = pygame.display.set_mode(bg_size) # 设置背景大小
background = pygame.image.load(bg_image).convert_alpha() #画背景
#绘制用于摩擦的玻璃板,要花在小球前面,不然的话,后面小球会从玻璃板的下方划过
galss = pygame.image.load(galss_image).convert_alpha() #画玻璃板
galss_rect = galss.get_rect() #获得玻璃板的尺寸
galss_rect.left , galss_rect.top = ((width-galss_rect[2])/2 , (height-galss_rect[3])) #玻璃板的位置
balls = []
# 创建五个小球
BALL_NUM = 5
for i in range (BALL_NUM) : #生成5个球
position = randint (0,width-100) , randint(0,height-100) #要减去100是因为球图片尺寸的大小为100,随机生成位置
speed = [ randint (-10,10) , randint(-10,10) ]
ball = Ball(grayball_image,position,speed,bg_size) #生成球的对象
while collide_check(ball,balls): #如果生成的小球和之前的球发生碰撞,那么重新生成小球
ball.rect.left , ball.rect.top = randint (0,width-100) , randint(0,height-100)
balls.append(ball) #将所有的球对象添加到类表中,方便管理
clock = pygame.time.Clock() #生成刷新帧率控制器
while running :
for event in pygame.event.get():
if event.type == QUIT: #如果事件类型是退出
sys.exit()
elif event.type == GAMEOVER: #如果音乐结束事件类型为其返回的自定义事件游戏结束
loser_music.play() #播放失败的音乐
time.sleep(2) #延时2s
laugh_music.play() #播放大笑音效
running = False
screen.blit(background, (0, 0)) #将背景画到screen上
screen.blit(galss,(galss_rect.left , galss_rect.top)) #将玻璃板绘制在screen上
for each in balls: #每个球进行移动并重新绘制
each.move()
screen.blit(each.image, each.rect)
for i in range (BALL_NUM) : #循环5个小球,分别判断这个小球有没有和另外四个小球发生碰撞
item = balls.pop(i) #因为是判断和其他四个小球,所以需要先将这个小球取出
if collide_check( item , balls ): #调用碰撞检测的函数,如果结果为真,也就是有发生碰撞的小球
item.speed[0] = - item.speed[0] #碰撞后向反方向运动
item.speed[1] = - item.speed[1]
balls.insert(i , item) #最后不要忘记把这个小球放回原位
pygame.display.flip()
clock.tick(30)
if __name__ == "__main__":
main()
接下来我们就需要对鼠标动手了,大家也看到了,Pygame 默认的鼠标非常难看,又小又黑又土,而作为一个游戏的话,漂亮的光标往往就是决胜的关键,游戏开发注重的就是细节,现在我们就用自定义的光标图片替换原来又小又黑又土的光标。如图·所示:
前者为在窗口其他位置的样子,后者为在玻璃板上的样子
关于自定义鼠标的光标:
我们的做法就是使用 set_visible() 方法来将光标设为不可见,然后使用 get_pos() 方法获取鼠标的实时位置,获取之后,我们实时的把 hand.png 图片画到这个位置上去,这样子就相当于实现了自定义光标。
pygame.mouse.set_visible() —— 隐藏或显示鼠标光标
pygame.mouse.get_pos() —— 获取鼠标光标的位置
代码实现:
from pygame.locals import *
from random import *
import pygame
import math
import time
import sys
class Ball(pygame.sprite.Sprite) : #继承动画精灵基类
def __init__ (self,grayball_image,position,speed,bg_size,target) :
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(grayball_image).convert_alpha()
self.rect = self.image.get_rect() #获得球的尺寸
self.rect.left , self.rect.top = position #将出现的位置赋给球
self.speed = speed #设置速度
self.width , self.height = bg_size[0] , bg_size[1] #获得活动边界,就是背景的边界
def move(self):
self.rect = self.rect.move(self.speed) #根据自身的速度进行移动
if self.rect.right < 0: #图片的右边已经超出边界的左边,即整个球已经出界
self.rect.left = self.width #让他从右边界回来
if self.rect.bottom < 0: #图片的底已经超出边界的上面
self.rect.top = self.height #让他从底部回来
if self.rect.left > self.width: #图片的左边已经超出边界的右边
self.rect.right = 0 #让他从左边回来
if self.rect.top > self.height: #如果图片的顶部已经超出边界的底部
self.rect.bottom = 0 #让他从顶部回来
#判断碰撞检测函数
def collide_check(item,target):
col_balls = [] #添加碰撞小球
for each in target: #对 target 中所有的目标小球进行检测
#两个球心之间的距离
distance = math.sqrt( math.pow( (item.rect.center[0] - each.rect.center[0]) , 2 ) + \
math.pow( (item.rect.center[1] - each.rect.center[1]) , 2) )
if distance <= ( item.rect.width + each.rect.width ) / 2: #如果距离小于等于两者间的半径之和也就是两个直径之和的一半
col_balls.append(each) #将这个发生碰撞的小球添加到列表中
return col_balls
def main() :
pygame.init() #初始化
#将所有图片的路径写入
bg_image = r"D:\Code\Python\Pygame\pygame9:摩擦摩擦\background.png" #背景图
grayball_image = r"D:\Code\Python\Pygame\pygame9:摩擦摩擦\gray_ball.png" #灰小球的图片
galss_image = r"D:\Code\Python\Pygame\pygame9:摩擦摩擦\glass.png" #玻璃板图片
hand_image = r"D:\Code\Python\Pygame\pygame9:摩擦摩擦\hand.png" #鼠标在玻璃板上的样子
hand1_image = r"D:\Code\Python\Pygame\pygame9:摩擦摩擦\hand1.png" #鼠标在其他地方的样子
running = True #为了以后而已有多种方法退出程序
#加载背景音乐
pygame.mixer.music.load(r"D:\Code\Python\Pygame\pygame8:播放声音和音效\bg_music.ogg")
pygame.mixer.music.set_volume(0.2) #设置音量
pygame.mixer.music.play() #播放背景音乐
#设置一个背景音乐完毕之后的结束事件
GAMEOVER = USEREVENT
pygame.mixer.music.set_endevent(GAMEOVER) #当音乐播完后,发送一个GAMEOVER事件
#加载四个音效
hole_music = pygame.mixer.Sound(r"D:\Code\Python\Pygame\pygame9:摩擦摩擦\loser.wav")
laugh_music = pygame.mixer.Sound(r"D:\Code\Python\Pygame\pygame9:摩擦摩擦\laugh.wav")
winner_music = pygame.mixer.Sound(r"D:\Code\Python\Pygame\pygame9:摩擦摩擦\winner.wav")
loser_music = pygame.mixer.Sound(r"D:\Code\Python\Pygame\pygame9:摩擦摩擦\loser.wav")
#设置背景
bg_size = width , height = 1024 , 681 #背景大小
screen = pygame.display.set_mode(bg_size) # 设置背景大小
background = pygame.image.load(bg_image).convert_alpha() #画背景
#绘制用于摩擦的玻璃板,要花在小球前面,不然的话,后面小球会从玻璃板的下方划过
galss = pygame.image.load(galss_image).convert_alpha() #画玻璃板
galss_rect = galss.get_rect() #获得玻璃板的尺寸
galss_rect.left , galss_rect.top = ((width-galss_rect[2])/2 , (height-galss_rect[3])) #玻璃板的位置
balls = []
#加载鼠标图片
hand = pygame.image.load(hand_image).convert_alpha() #画玻璃版内鼠标
hand1 = pygame.image.load(hand1_image).convert_alpha() #画平常鼠标
flag = False #设置一个变量,用来表示鼠标是否进入玻璃板范围
# 创建五个小球
BALL_NUM = 5
for i in range (BALL_NUM) : #生成5个球
position = randint (0,width-100) , randint(0,height-100) #要减去100是因为球图片尺寸的大小为100,随机生成位置
speed = [ randint (-10,10) , randint(-10,10) ]
ball = Ball(grayball_image,position,speed,bg_size,6*(i+1)) #生成球的对象
while collide_check(ball,balls): #如果生成的小球和之前的球发生碰撞,那么重新生成小球
ball.rect.left , ball.rect.top = randint (0,width-100) , randint(0,height-100)
balls.append(ball) #将所有的球对象添加到类表中,方便管理
clock = pygame.time.Clock() #生成刷新帧率控制器
while running :
for event in pygame.event.get():
if event.type == QUIT: #如果事件类型是退出
sys.exit()
elif event.type == GAMEOVER: #如果音乐结束事件类型为其返回的自定义事件游戏结束
loser_music.play() #播放失败的音乐
time.sleep(2) #延时2s
laugh_music.play() #播放大笑音效
running = False
elif event.type == MOUSEMOTION: #如果事件类型为鼠标移动
mouse_x , mouse_y = pygame.mouse.get_pos() #获取鼠标移动的当前位置
if (galss_rect.left <= mouse_x <= galss_rect.right) \
and (galss_rect.top <= mouse_y <= galss_rect.bottom): #如果鼠标在玻璃板内,那么flag=1
flag = True
else : #不再范围内鼠标可见
flag = False
screen.blit(background, (0, 0)) #将背景画到screen上
screen.blit(galss,(galss_rect.left , galss_rect.top)) #将玻璃板绘制在screen上
if flag : #如果鼠标进入玻璃板
#设置鼠标不可见
mouse_x , mouse_y = pygame.mouse.get_pos() #获取鼠标移动的当前位置
pygame.mouse.set_visible(False) #原鼠标不可见
screen.blit(hand,(mouse_x,mouse_y)) #画上我们玻璃版内鼠标
else : #如果鼠标没进入玻璃板
pygame.mouse.set_visible(False) #原鼠标不可见
screen.blit(hand1,(mouse_x,mouse_y)) #画上我们自己的平常鼠标
for each in balls: #每个球进行移动并重新绘制
each.move()
screen.blit(each.image, each.rect)
for i in range (BALL_NUM) : #循环5个小球,分别判断这个小球有没有和另外四个小球发生碰撞
item = balls.pop(i) #因为是判断和其他四个小球,所以需要先将这个小球取出
if collide_check( item , balls ): #调用碰撞检测的函数,如果结果为真,也就是有发生碰撞的小球
item.speed[0] = - item.speed[0] #碰撞后向反方向运动
item.speed[1] = - item.speed[1]
balls.insert(i , item) #最后不要忘记把这个小球放回原位
pygame.display.flip()
clock.tick(30)
if __name__ == "__main__":
main()
我们需要让小球响应光标一定频率的摩擦摩擦而停下来变成绿色,就是说,鼠标在玻璃面板上摩擦,达到一定频率的时候,某一个匹配的小球会停下来,而不是所有小球,停下来的小球就接受你的控制啦。
我们知道,鼠标的一定会产生事件,我们可以利用这一点。假设单位时间是一秒,我们检测一秒内鼠标在玻璃面板内产生多少事件,从而判定每一秒产生的事件数是否能够匹配到某一个小球需要的要求。
步骤如下:
1、为每个小球设定一个不同的目标;
2、创建一个motion 变量来记录每一秒钟产生事件数量;
3、为小球添加一个 check() 方法,用于判断鼠标在1秒钟内产生的事件数量是否匹配此目标;
4、添加一个自定义事件,每一秒钟触发一次。调用每个小球的 check() 检测 motion 的值是否匹配某一个小球的目标,并将motion重新初始化,以便记录下1秒鼠标事件数量;
5、小球应该添加一个 control 属性,用于记录当前的状态(绿色 -> 玩家控制 or 灰色 -> 随机移动)。
6、通过检查 control 属性决定绘制什么颜色的小球。
代码实现:
from pygame.locals import *
from random import *
import pygame
import math
import time
import sys
class Ball(pygame.sprite.Sprite) : #继承动画精灵基类
def __init__ (self,grayball_image,greenball_image,position,speed,bg_size,target) :
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(grayball_image).convert_alpha()
self.rect = self.image.get_rect() #获得球的尺寸
self.rect.left , self.rect.top = position #将出现的位置赋给球
self.speed = speed #设置速度
self.target = target #设置一个使小球变为可控的目标
self.control = False #小球是否人为可控的标志
self.width , self.height = bg_size[0] , bg_size[1] #获得活动边界,就是背景的边界
def drawball(self,greenball_image):
if self.control : #如果小球可用,control=1
self.image = pygame.image.load(greenball_image) #重新绘制小球为绿色的小球
def move(self):
self.rect = self.rect.move(self.speed) #根据自身的速度进行移动
if self.rect.right < 0: #图片的右边已经超出边界的左边,即整个球已经出界
self.rect.left = self.width #让他从右边界回来
if self.rect.bottom < 0: #图片的底已经超出边界的上面
self.rect.top = self.height #让他从底部回来
if self.rect.left > self.width: #图片的左边已经超出边界的右边
self.rect.right = 0 #让他从左边回来
if self.rect.top > self.height: #如果图片的顶部已经超出边界的底部
self.rect.bottom = 0 #让他从顶部回来
def check(self,motion): #检查鼠标移动的频率是否打到控制小球的目标值
if self.target - 2 <= motion <= self.target + 2 :
self.control = True #小球可控标志设为1
return True
else :
return False
#判断碰撞检测函数
def collide_check(item,target):
col_balls = [] #添加碰撞小球
for each in target: #对 target 中所有的目标小球进行检测
#两个球心之间的距离
distance = math.sqrt( math.pow( (item.rect.center[0] - each.rect.center[0]) , 2 ) + \
math.pow( (item.rect.center[1] - each.rect.center[1]) , 2) )
if distance <= ( item.rect.width + each.rect.width ) / 2: #如果距离小于等于两者间的半径之和也就是两个直径之和的一半
col_balls.append(each) #将这个发生碰撞的小球添加到列表中
return col_balls
def main() :
pygame.init() #初始化
#将所有图片的路径写入
bg_image = r"D:\Code\Python\Pygame\pygame9:摩擦摩擦\background.png" #背景图
grayball_image = r"D:\Code\Python\Pygame\pygame9:摩擦摩擦\gray_ball.png" #灰小球的图片
greenball_image = r"D:\Code\Python\Pygame\pygame9:摩擦摩擦\green_ball.png" #绿小球的图片
galss_image = r"D:\Code\Python\Pygame\pygame9:摩擦摩擦\glass.png" #玻璃板图片
hand_image = r"D:\Code\Python\Pygame\pygame9:摩擦摩擦\hand.png" #鼠标在玻璃板上的样子
hand1_image = r"D:\Code\Python\Pygame\pygame9:摩擦摩擦\hand1.png" #鼠标在其他地方的样子
running = True #为了以后而已有多种方法退出程序
#加载背景音乐
pygame.mixer.music.load(r"D:\Code\Python\Pygame\pygame8:播放声音和音效\bg_music.ogg")
pygame.mixer.music.set_volume(0.2) #设置音量
pygame.mixer.music.play() #播放背景音乐
#设置一个背景音乐完毕之后的结束事件
GAMEOVER = USEREVENT
pygame.mixer.music.set_endevent(GAMEOVER) #当音乐播完后,发送一个GAMEOVER事件
#加载四个音效
hole_music = pygame.mixer.Sound(r"D:\Code\Python\Pygame\pygame9:摩擦摩擦\loser.wav")
laugh_music = pygame.mixer.Sound(r"D:\Code\Python\Pygame\pygame9:摩擦摩擦\laugh.wav")
winner_music = pygame.mixer.Sound(r"D:\Code\Python\Pygame\pygame9:摩擦摩擦\winner.wav")
loser_music = pygame.mixer.Sound(r"D:\Code\Python\Pygame\pygame9:摩擦摩擦\loser.wav")
#设置背景
bg_size = width , height = 1024 , 681 #背景大小
screen = pygame.display.set_mode(bg_size) # 设置背景大小
background = pygame.image.load(bg_image).convert_alpha() #画背景
#绘制用于摩擦的玻璃板,要花在小球前面,不然的话,后面小球会从玻璃板的下方划过
galss = pygame.image.load(galss_image).convert_alpha() #画玻璃板
galss_rect = galss.get_rect() #获得玻璃板的尺寸
galss_rect.left , galss_rect.top = ((width-galss_rect[2])/2 , (height-galss_rect[3])) #玻璃板的位置
balls = []
#加载鼠标图片
hand = pygame.image.load(hand_image).convert_alpha() #画玻璃版内鼠标
hand1 = pygame.image.load(hand1_image).convert_alpha() #画平常鼠标
flag = False #设置一个变量,用来表示鼠标是否进入玻璃板范围
#用来计数一秒钟内移动的次数
motion = 0
#设置一个自定义事件,用来检测鼠标移动的值是否符合控制小球的目标值
MYTIMER = USEREVENT + 1 #因为之前已经定义了一个自定义事件,所以根据之前说的这个自定义事件应该是之前的加1
pygame.time.set_timer(MYTIMER,1000) #计时器事件为1s
# 创建五个小球
BALL_NUM = 5
for i in range (BALL_NUM) : #生成5个球
position = randint (0,width-100) , randint(0,height-100) #要减去100是因为球图片尺寸的大小为100,随机生成位置
speed = [ randint (-10,10) , randint(-10,10) ]
ball = Ball(grayball_image,greenball_image,position,speed,bg_size,5*(i+1)) #生成球的对象
while collide_check(ball,balls): #如果生成的小球和之前的球发生碰撞,那么重新生成小球
ball.rect.left , ball.rect.top = randint (0,width-100) , randint(0,height-100)
balls.append(ball) #将所有的球对象添加到类表中,方便管理
clock = pygame.time.Clock() #生成刷新帧率控制器
while running :
for event in pygame.event.get():
if event.type == QUIT: #如果事件类型是退出
sys.exit()
elif event.type == GAMEOVER: #如果音乐结束事件类型为其返回的自定义事件游戏结束
loser_music.play() #播放失败的音乐
time.sleep(2) #延时2s
laugh_music.play() #播放大笑音效
running = False
elif event.type == MOUSEMOTION: #如果事件类型为鼠标移动
mouse_x , mouse_y = pygame.mouse.get_pos() #获取鼠标移动的当前位置
if (galss_rect.left <= mouse_x <= galss_rect.right) \
and (galss_rect.top <= mouse_y <= galss_rect.bottom): #如果鼠标在玻璃板内,那么flag=1
motion += 1 #如果鼠标在玻璃版内滑动,那么motion+1
flag = True
else : #不再范围内鼠标可见
flag = False
elif event.type == MYTIMER: #如果事件类型为自己的定义的定时器事件
for each in balls : #遍历所有的球
if each.check(motion): #调用他们各自的check()函数,看是否打到控制要求的目标,如果达到要求,即返回值为真
each.drawball(greenball_image) #调用球类中的画图方法将灰色球划成绿色球
each.speed = [0,0] #将他们的速度设为0,等待人类的控制
motion = 0 #等到所有的球都进行判断之后将motion重新设为0,进行下一秒的循环
screen.blit(background, (0, 0)) #将背景画到screen上
screen.blit(galss,(galss_rect.left , galss_rect.top)) #将玻璃板绘制在screen上
if flag : #如果鼠标进入玻璃板
#设置鼠标不可见
mouse_x , mouse_y = pygame.mouse.get_pos() #获取鼠标移动的当前位置
pygame.mouse.set_visible(False) #原鼠标不可见
screen.blit(hand,(mouse_x,mouse_y)) #画上我们玻璃版内鼠标
else : #如果鼠标没进入玻璃板
pygame.mouse.set_visible(False) #原鼠标不可见
screen.blit(hand1,(mouse_x,mouse_y)) #画上我们自己的平常鼠标
for each in balls: #每个球进行移动并重新绘制
each.move()
screen.blit(each.image, each.rect)
for i in range (BALL_NUM) : #循环5个小球,分别判断这个小球有没有和另外四个小球发生碰撞
item = balls.pop(i) #因为是判断和其他四个小球,所以需要先将这个小球取出
if collide_check( item , balls ): #调用碰撞检测的函数,如果结果为真,也就是有发生碰撞的小球
item.speed[0] = - item.speed[0] #碰撞后向反方向运动
item.speed[1] = - item.speed[1]
balls.insert(i , item) #最后不要忘记把这个小球放回原位
pygame.display.flip()
clock.tick(30)
if __name__ == "__main__":
main()