第089讲: Pygame:游戏胜利 | 学习记录(小甲鱼零基础入门学习Python)

之前我们通过摩擦摩擦,可以使得小球变绿色,并不再随机移动:
第089讲: Pygame:游戏胜利 | 学习记录(小甲鱼零基础入门学习Python)_第1张图片
现在我们需要设置在小球变绿之后只用键盘来控制他。

——通过 w,a,s,d 或者 ↑,↓,←,→ ,来完成对变成绿色的小球的 向上,向左,向下,向右 的控制。

from pygame.locals import *
from random import *
import traceback
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.grayball_image = grayball_image
        self.greenball_image = greenball_image
        self.rect = self.image.get_rect()   #获得球的尺寸
        self.rect.left , self.rect.top = position   #将出现的位置赋给球
        self.direction = [ speed[0]/abs(speed[0]) , speed[1]/abs(speed[1]) ]  #小球当前运动的方向
        self.speed = speed  #设置速度大小
        self.colide = False #设置是否发生碰撞属性
        self.target = target    #设置一个使小球变为可控的目标
        self.control = False    #小球是否人为可控的标志
        self.width , self.height = bg_size[0] , bg_size[1]  #获得活动边界,就是背景的边界
    
    def drawball(self):
        if self.control :   #如果小球可用,control=1
            self.image = pygame.image.load(self.greenball_image)     #重新绘制小球为绿色的小球
        else :  #否则绘制灰色小球
            self.image = pygame.image.load(self.grayball_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 - 4 <= motion <= self.target + 4 :
            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    #设置一个变量,用来表示鼠标是否进入玻璃板范围
    
    #设置响应键盘连续输入
    pygame.key.set_repeat(100,100)

    #用来计数一秒钟内移动的次数
    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()  #播放失败的音乐
                pygame.time.delay(2000) #延时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()    #调用球类中的画图方法将灰色球划成绿色球
                        each.speed = [0,0]  #将他们的速度设为0,等待人类的控制
                motion = 0    #等到所有的球都进行判断之后将motion重新设为0,进行下一秒的循环

            elif event.type == KEYDOWN :    #如果时间类型是键盘上的键按下
                if event.key == K_UP or event.key == K_w :    #如果按下的键是向上键或者w键
                    for each in balls :     #遍历所有的小球 查看他们的control属性
                        if each.control :   #如果control属性为真,即小球可控
                            each.speed[1] -= 1  #每按一下就减一 营造加速度的效果 下面的类似
                if event.key == K_DOWN or event.key == K_s :   #如果按下的键是向下键或者s键
                    for each in balls :     #遍历所有的小球 查看他们的control属性
                        if each.control :   #如果control属性为真,即小球可控
                            each.speed[1] += 1
                if event.key == K_LEFT or event.key == K_a :   #如果按下的键是向左键或者a键
                    for each in balls :     #遍历所有的小球 查看他们的control属性
                        if each.control :   #如果control属性为真,即小球可控
                            each.speed[0] -= 1
                if event.key == K_RIGHT or event.key == K_d :  #如果按下的键是向右键或者d键
                    for each in balls :     #遍历所有的小球 查看他们的control属性
                        if each.control :   #如果control属性为真,即小球可控
                            each.speed[0] += 1

        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()

这一部分需要特别讲解的就是:

在默认情况下,无论你是简单的按一下按键或是按住按键不放,

Pygame都只为你发送一个键盘按下的事件

不过我们可以通过 key 模块的 set_repeat() 方法来设置是否重复响应按下某个按键

pygame.key.set_repeat(delay, interval)

–delay 参数制动第一次发送事件的延迟时间

–interval 参数指定重复发送事件的时间间隔

–如果不带任何参数,表示取消重复发送事件

这一部分成功了,接下来就是解决当小球发生碰撞时,无论你是绿色还是灰色,都会失控并脱离控制状态,这就只需要在检测到碰撞时把 control 属性设置为 False。

因为我是看了一边思路后自己动手开始写的,所以前面有些地方和小甲鱼的不是很相似,这里使用我自己的想法,文章最后我会贴上小甲鱼的代码,你们可以参考,欢迎交流沟通!

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]
            item.control = False    #发生碰撞后小球重新变成不可控
            item.drawball()    #调用小球的绘制函数,重新把小球画成灰色
        balls.insert(i , item)  #最后不要忘记把这个小球放回原位

这个很好搞定,只需要在碰撞判断中发生碰撞时,把球的 control 属性变为 False 即可,然后重新绘图 ,接下来我们要做的就是让小球在碰撞的时候获得一个新的随机速度,这将大大的加大游戏的难度,因为均匀的速度是可以测算出来的,但是如果是以很慢的速度碰撞,却以新的速度反弹,你就会躲闪不及。

所以我们仍然需要改一下碰撞检测这一部分的代码:

但是如果我们这样子改,就会出现一个bug:

        for i in range (BALL_NUM) : #循环5个小球,分别判断这个小球有没有和另外四个小球发生碰撞
            item = balls.pop(i)    #因为是判断和其他四个小球,所以需要先将这个小球取出
            if collide_check( item , balls ):  #调用碰撞检测的函数,如果结果为真,也就是有发生碰撞的小球
                item.speed = [ randint(-10,10) , randint(-10,10) ]
                item.control = False    #发生碰撞后小球重新变成不可控
                item.drawball()    #调用小球的绘制函数,重新把小球画成灰色
            balls.insert(i , item)  #最后不要忘记把这个小球放回原位

就会出现有的两个小球碰撞之后,不停的来回碰撞,像新婚的小夫妻一样不愿意分开,如下图红框所示:
第089讲: Pygame:游戏胜利 | 学习记录(小甲鱼零基础入门学习Python)_第2张图片
但是这样这就会让玩家感觉这个游戏像卡顿了一样,优化没做好,所以肯定是要解决的!

解决一个东西之前呢,首先要知道产生这种情况的原因:

因为碰撞之后,我们随机给一个新的速度,这个速度是带方向的速度,如果两个小球碰撞之后得到的新的随机速度的方向是相像的,它们就会再次发生碰撞,然后速度再相像,再检测到碰撞。。。然后。。。。就一直纠缠不清了。直到获得的速度是反向的,并且速度的大小能够脱离彼此,才会分开。

既然知道了原因,那么解决方法也就不难说了。

解决这个问题的方法就是 将方向和速度这两个概念独立开来,大家注意到了,问题的根源在于我们得到的随机速度是带方向的,它可能获取一个向左的,也可能获取一个向右的,所以我们把问题细分,把方向和速度独立开,速度永远为正,只描述书速度的大小,而方向为 -1表示向左,+1 表示向右。把方向乘以速度得到带方向的速度。然后在每一次撞击的时候,它以撞击的速度反向移动一格,这两个小球就分开了,再获得一个随机的速度就可以了。

我们来尝试是否能够实现:

from pygame.locals import *
from random import *
import traceback
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.grayball_image = grayball_image
        self.greenball_image = greenball_image
        self.rect = self.image.get_rect()   #获得球的尺寸
        self.rect.left , self.rect.top = position   #将出现的位置赋给球
        self.direction = [ speed[0]/abs(speed[0]) , speed[1]/abs(speed[1]) ]  #小球当前运动的方向
        self.speed = [ abs(speed[0]) , abs(speed[1]) ]  #设置速度大小
        self.colide = False #设置是否发生碰撞属性
        self.target = target    #设置一个使小球变为可控的目标
        self.control = False    #小球是否人为可控的标志
        self.width , self.height = bg_size[0] , bg_size[1]  #获得活动边界,就是背景的边界
    
    def drawball(self):
        if self.control :   #如果小球可用,control=1
            self.image = pygame.image.load(self.greenball_image)     #重新绘制小球为绿色的小球
        else :  #否则绘制灰色小球
            self.image = pygame.image.load(self.grayball_image)
    
    def move(self):
        self.rect = self.rect.move(self.direction[0]*self.speed[0] , self.direction[0]*self.speed[1])  #根据自身的速度进行移动

        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 - 4 <= motion <= self.target + 4 :
            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    #设置一个变量,用来表示鼠标是否进入玻璃板范围
    
    #设置响应键盘连续输入
    pygame.key.set_repeat(100,100)

    #用来计数一秒钟内移动的次数
    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) ]    因为这种方法会生成新bug有的时候速度生成值为0所以使用新的方法
        speed = [choice([-1,1])*randint(1,10),choice([-1,1])*randint(1,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()  #播放失败的音乐
                pygame.time.delay(2000) #延时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()    #调用球类中的画图方法将灰色球划成绿色球
                        each.speed = [0,0]  #将他们的速度设为0,等待人类的控制
                motion = 0    #等到所有的球都进行判断之后将motion重新设为0,进行下一秒的循环

            elif event.type == KEYDOWN :    #如果时间类型是键盘上的键按下
                if event.key == K_UP or event.key == K_w :    #如果按下的键是向上键或者w键
                    for each in balls :     #遍历所有的小球 查看他们的control属性
                        if each.control :   #如果control属性为真,即小球可控
                            each.speed[1] -= 1  #每按一下就减一 营造加速度的效果 下面的类似
                if event.key == K_DOWN or event.key == K_s :   #如果按下的键是向下键或者s键
                    for each in balls :     #遍历所有的小球 查看他们的control属性
                        if each.control :   #如果control属性为真,即小球可控
                            each.speed[1] += 1
                if event.key == K_LEFT or event.key == K_a :   #如果按下的键是向左键或者a键
                    for each in balls :     #遍历所有的小球 查看他们的control属性
                        if each.control :   #如果control属性为真,即小球可控
                            each.speed[0] -= 1
                if event.key == K_RIGHT or event.key == K_d :  #如果按下的键是向右键或者d键
                    for each in balls :     #遍历所有的小球 查看他们的control属性
                        if each.control :   #如果control属性为真,即小球可控
                            each.speed[0] += 1

        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()    
            if each.colide :    #如果这个小球刚发生过碰撞
                each.speed = [randint(3,10),randint(3,10)]    #改变速度
                each.colide = False
            screen.blit(each.image, each.rect)
        
        for i in range (BALL_NUM) : #循环5个小球,分别判断这个小球有没有和另外四个小球发生碰撞
            item = balls.pop(i)    #因为是判断和其他四个小球,所以需要先将这个小球取出
            if collide_check( item , balls ):  #调用碰撞检测的函数,如果结果为真,也就是有发生碰撞的小球
                item.colide = True  #小球碰撞属性设为真
                item.direction[0] = - item.direction[0] #小球的运动方向反向,然后速度随机
                item.direction[1] = - item.direction[1] 
                if item.control:
                    item.direction[0] = -1 
                    item.direction[1] = -1 
                    item.control = False
                    item.drawball()    #调用小球的绘制函数,重新把小球画成灰色
            balls.insert(i , item)  #最后不要忘记把这个小球放回原位

        pygame.display.flip()
        clock.tick(30)

if __name__ == "__main__":
    main()

上面的问题是解决了,可是我们发现有的时候 两个绿色的小球在键盘的操控下,居然向着相反的方向移动,这是为什么呢?

新的Bug 出现了,控制权交到玩家手中的时候,小球并不能正确的按照玩家的操纵去移动。我遇到的就是按下 A 的时候,两个绿色小球一个向左,一个向右。

其实在现实的开发中你常常会遇到这样的事情:好不容易补完一个Bug,或者说添加一个新的功能进去,直接影响了原来的程序逻辑,导致了另一个Bug甚至一些Bug的出现,那么既然有Bug,我们就应该及时的补上。

为什么会导致玩家的操作无法正确的控制小球呢?

原来是我们将带方向的速度拆分为方向和速度,而响应玩家的按键这里我们依然认为速度是带方向的,所以说你这里尽管说是减到0 了,它还是会继续减,而move() 方法这里写的是方向乘以速度,如果我的速度减到变为负数,方向为负数,那得到的却是一个反方向的移动的效果。

为了保留玩家操控小球是带加速度的效果,我们对于操控绿色小球这一部分的代码就不能怎么去修改,如果硬要去改的话,就会使得代码变得更加麻烦,逻辑上变得更加复杂,所以我们这里不妨将小球的移动给区分开:

随机移动的话就按照方向乘以速度,而由玩家操控的移动我们还是保留带方向的速度。

那应该怎么做呢?

我们在move这里对小球进行修改,

def move(self):
        if self.control:    #如果小球被控制
            self.rect = self.rect.move(self.speed)          #根据自己的速度移动
        else:
            self.rect = self.rect.move((self.direction[0] * self.speed[0],\
                                                    self.direction[1] * self.speed[1]))         #没被控制的小球则要乘上方向

        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 - 4 <= motion <= self.target + 4 :
            self.control = True #小球可控标志设为1
            return True
        else :
            return False

然后还需要对个绿球的相撞进行修改,

for i in range (BALL_NUM) : #循环5个小球,分别判断这个小球有没有和另外四个小球发生碰撞
            item = balls.pop(i)    #因为是判断和其他四个小球,所以需要先将这个小球取出
            if collide_check( item , balls ):  #调用碰撞检测的函数,如果结果为真,也就是有发生碰撞的小球
                item.colide = True  #小球碰撞属性设为真
                item.direction[0] = - item.direction[0] #小球的运动方向反向,然后速度随机
                item.direction[1] = - item.direction[1] 
                if item.control:
                    item.direction[0] = -1 
                    item.direction[1] = -1 
                    item.control = False
                    item.drawball()    #调用小球的绘制函数,重新把小球画成灰色
            balls.insert(i , item)  #最后不要忘记把这个小球放回原位

接下来,当绿色的小球移动到黑洞的正上方的时候,只要玩家点击键盘上的空格键,就会提示一个音效,并且填入黑洞中,此后其它小球就会忽略它,直接从它上面飘过,无视它的存在,音乐结束之前,如果你能够把所有的小球都填入到各个黑洞中,那么游戏胜利。

这里有两点需要注意的:

1、每个黑洞只能填入一个绿色的小球。

2、当小球填入黑洞时,其它小球是从它上方飘过,而不是从下方。

首先,我们5个黑洞的位置已经定义好了,我在作图的时候,已经把位置量好了。由于如果要100%命中太难了,所以我们保留了一定活动的范围,也就是说,代码给出的范围比黑洞实际范围大一点。

最终完整代码如下:

from pygame.locals import *
from random import *
import traceback
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.grayball_image = grayball_image
        self.greenball_image = greenball_image
        self.rect = self.image.get_rect()   #获得球的尺寸
        self.rect.left , self.rect.top = position   #将出现的位置赋给球
        self.direction = [ speed[0]/abs(speed[0]) , speed[1]/abs(speed[1]) ]  #小球当前运动的方向
        self.speed = [ abs(speed[0]) , abs(speed[1]) ]  #设置速度大小
        self.colide = False #设置是否发生碰撞属性
        self.hole = False #设置是否进黑洞
        self.target = target    #设置一个使小球变为可控的目标
        self.control = False    #小球是否人为可控的标志
        self.width , self.height = bg_size[0] , bg_size[1]  #获得活动边界,就是背景的边界
    
    def drawball(self):
        if self.control :   #如果小球可用,control=1
            self.image = pygame.image.load(self.greenball_image)     #重新绘制小球为绿色的小球
        else :  #否则绘制灰色小球
            self.image = pygame.image.load(self.grayball_image)
    
    def move(self):
        if not self.hole :  #如果小球没有进过黑洞
            if self.control:    #如果小球被控制
                self.rect = self.rect.move(self.speed)          #根据自己的速度移动
            else:
                self.rect = self.rect.move((self.direction[0] * self.speed[0],\
                                                        self.direction[1] * self.speed[1]))         #没被控制的小球则要乘上方向

            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 - 4 <= motion <= self.target + 4 :
            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\pygame10:游戏胜利\background.png"    #背景图
    grayball_image = r"D:\Code\Python\Pygame\pygame10:游戏胜利\gray_ball.png"   #灰小球的图片
    greenball_image = r"D:\Code\Python\Pygame\pygame10:游戏胜利\green_ball.png"     #绿小球的图片
    galss_image = r"D:\Code\Python\Pygame\pygame10:游戏胜利\glass.png"      #玻璃板图片
    hand_image = r"D:\Code\Python\Pygame\pygame10:游戏胜利\hand.png"    #鼠标在玻璃板上的样子
    hand1_image = r"D:\Code\Python\Pygame\pygame10:游戏胜利\hand1.png"      #鼠标在其他地方的样子

    running = True  #为了以后而已有多种方法退出程序

     # 存放要打印的消息
    msgs = []

    #加载背景音乐
    pygame.mixer.music.load(r"D:\Code\Python\Pygame\pygame10:游戏胜利\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\pygame10:游戏胜利\hole.wav")
    laugh_music = pygame.mixer.Sound(r"D:\Code\Python\Pygame\pygame10:游戏胜利\laugh.wav")    
    winner_music = pygame.mixer.Sound(r"D:\Code\Python\Pygame\pygame10:游戏胜利\winner.wav")
    loser_music = pygame.mixer.Sound(r"D:\Code\Python\Pygame\pygame10:游戏胜利\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 = []
    group = [] #会发生碰撞的小球

    #加载鼠标图片
    hand = pygame.image.load(hand_image).convert_alpha()   #画玻璃版内鼠标
    hand1 = pygame.image.load(hand1_image).convert_alpha()  #画平常鼠标
    flag = False    #设置一个变量,用来表示鼠标是否进入玻璃板范围
    
    #设置响应键盘连续输入
    pygame.key.set_repeat(100,100)

    #用来计数一秒钟内移动的次数
    motion = 0  
    #设置一个自定义事件,用来检测鼠标移动的值是否符合控制小球的目标值
    MYTIMER = USEREVENT + 1     #因为之前已经定义了一个自定义事件,所以根据之前说的这个自定义事件应该是之前的加1
    pygame.time.set_timer(MYTIMER,1000)     #计时器事件为1s  
    # 创建五个小球
    BALL_NUM = 5

    #地图上的黑洞的坐标,因为 100% 命中太难,所以只要在范围内即可
    # 每个元素:(x1, x2, y1, y2)
    hole = [(117, 119, 199, 201), (225, 227, 390, 392), \
            (503, 505, 320, 322), (698, 700, 192, 194), \
            (906, 908, 419, 421)]

    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) ]    因为这种方法会生成新bug有的时候速度生成值为0所以使用新的方法
        speed = [choice([-1,1])*randint(1,10),choice([-1,1])*randint(1,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)  #将所有的球对象添加到列表中,方便管理
        group.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()  #播放失败的音乐
                pygame.time.delay(2000) #延时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()    #调用球类中的画图方法将灰色球划成绿色球
                        each.speed = [0,0]  #将他们的速度设为0,等待人类的控制
                motion = 0    #等到所有的球都进行判断之后将motion重新设为0,进行下一秒的循环

            elif event.type == KEYDOWN :    #如果时间类型是键盘上的键按下
                if event.key == K_UP or event.key == K_w :    #如果按下的键是向上键或者w键
                    for each in balls :     #遍历所有的小球 查看他们的control属性
                        if each.control :   #如果control属性为真,即小球可控
                            each.speed[1] -= 1  #每按一下就减一 营造加速度的效果 下面的类似
                if event.key == K_DOWN or event.key == K_s :   #如果按下的键是向下键或者s键
                    for each in balls :     #遍历所有的小球 查看他们的control属性
                        if each.control :   #如果control属性为真,即小球可控
                            each.speed[1] += 1
                if event.key == K_LEFT or event.key == K_a :   #如果按下的键是向左键或者a键
                    for each in balls :     #遍历所有的小球 查看他们的control属性
                        if each.control :   #如果control属性为真,即小球可控
                            each.speed[0] -= 1
                if event.key == K_RIGHT or event.key == K_d :  #如果按下的键是向右键或者d键
                    for each in balls :     #遍历所有的小球 查看他们的control属性
                        if each.control :   #如果control属性为真,即小球可控
                            each.speed[0] += 1
                if event.key == K_SPACE :    #如果按下的是键盘上的空格键
                    for each in group :     
                        if each.control :       #遍历所有小球中被人为控制的球
                            for i in hole : #遍历所有黑洞
                                #如果被控制的小球位置在黑洞运行的范围内
                                if ((i[0] <= each.rect.left <= i[1]) and (i[2] <= each.rect.top <= i[3])) :         
                                    #播放黑洞音效
                                    hole_music.play()
                                    #小球速度变为0
                                    each.speed = [0,0]
                                    #进黑洞属性变为True
                                    each.hole = True
                                    #将这个小球从碰撞组中删除,这样其他球就不会撞他
                                    group.remove(each)
                                    #并将这个小球从之前的balls列表中删除,然后插到列表的最前面,这样其他球就会在他的上面飘过
                                    temp = balls.pop(balls.index(each))
                                    balls.insert(0,temp)
                                    # 一个坑一个球
                                    hole.remove(i)
                            # 坑都补完了,游戏结束
                            if not hole:
                                pygame.mixer.music.stop()
                                winner_music.play()
                                pygame.time.delay(3000)
                                # 打印“然并卵”
                                
                                msg = pygame.image.load(r"D:\Code\Python\Pygame\pygame10:游戏胜利\win.png").convert_alpha()
                                msg_pos = (width - msg.get_width()) // 2, \
                                          (height - msg.get_height()) // 2
                                msgs.append((msg, msg_pos))
                                laugh_music.play()

        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()    
            if each.colide :    #如果这个小球刚发生过碰撞
                each.speed = [randint(3,10),randint(3,10)]    #改变速度
                each.colide = False
            screen.blit(each.image, each.rect)
        
        for i in range (len(group)) : #循环5个小球,分别判断这个小球有没有和另外四个小球发生碰撞
            item = group.pop(i)    #因为是判断和其他四个小球,所以需要先将这个小球取出
            if collide_check( item , group ):  #调用碰撞检测的函数,如果结果为真,也就是有发生碰撞的小球
                item.colide = True  #小球碰撞属性设为真
                item.direction[0] = - item.direction[0] #小球的运动方向反向,然后速度随机
                item.direction[1] = - item.direction[1] 
                if item.control:
                    item.direction[0] = -1 
                    item.direction[1] = -1 
                    item.control = False
                    item.drawball()    #调用小球的绘制函数,重新把小球画成灰色
            group.insert(i, item)  #最后不要忘记把这个小球放回原位

        #画上文字
        for msg in msgs:
            screen.blit(msg[0], msg[1])
            #延时5秒关闭
            pygame.time.delay(5000)

        pygame.display.flip()
        clock.tick(30)

if __name__ == "__main__":
    # 这样做的好处是双击打开时如果出现异常可以报告异常,而不是一闪而过!
    try:
        main()
    except SystemExit: #这是按下 × 的异常,直接忽略
        pass
    except:
        traceback.print_exc()
        pygame.quit()
        input()

第089讲: Pygame:游戏胜利 | 学习记录(小甲鱼零基础入门学习Python)_第3张图片
还有一点需要补充一下:

因为我本人是在VScode或者pycharm中运行了,所以我遇不到这种困扰,但是平时在IDLE里面执行,但是我们点 关闭(×)没有反应,有没有什么好的办法?

有的,你只需要在响应 QUIT 事件这里,加一条 pygame.quit() 即可。

        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()

还有一种情况就是:用户双击打开游戏文件,如果有逻辑错误或者代码错误,程序就不会给你报错,而是一打开就闪退。

我们也有解决方案:

就是添加捕获异常的模块 import traceback

然后:

if __name__ == "__main__":
    # 这样做的好处是双击打开时如果出现异常可以报告异常,而不是一闪而过!
    try:
        main()
    except SystemExit: #这是按下 × 的异常,直接忽略
        pass
    except:
        traceback.print_exc()
        pygame.quit()
        input()

另外在这个的基础上,昨天对代码进行了更新,在最前加入了故事情节,然后附带机械键盘的敲击音效,故事情节结束后,弹出新的窗口进行难度等级的选择,
第089讲: Pygame:游戏胜利 | 学习记录(小甲鱼零基础入门学习Python)_第4张图片
第089讲: Pygame:游戏胜利 | 学习记录(小甲鱼零基础入门学习Python)_第5张图片
第089讲: Pygame:游戏胜利 | 学习记录(小甲鱼零基础入门学习Python)_第6张图片
第089讲: Pygame:游戏胜利 | 学习记录(小甲鱼零基础入门学习Python)_第7张图片
之后与原来没有什么区别:
代码这里就不贴了,太长占篇幅,感兴趣的请点击→ https://blog.csdn.net/qq_38970783/article/details/89318673

小甲鱼的代码:

import pygame
import sys
import traceback
from pygame.locals import *
from random import *

# 球类继承自Spirte类
class Ball(pygame.sprite.Sprite):
    def __init__(self, grayball_image, greenball_image, position, speed, bg_size, target):
        # 初始化动画精灵
        pygame.sprite.Sprite.__init__(self)

        self.grayball_image = pygame.image.load(grayball_image).convert_alpha()
        self.greenball_image = pygame.image.load(greenball_image).convert_alpha()
        self.rect = self.grayball_image.get_rect()
        # 将小球放在指定位置
        self.rect.left, self.rect.top = position
        self.side = [choice([-1, 1]), choice([-1, 1])]
        self.speed = speed
        self.collide = False
        self.target = target
        self.control = False
        self.width, self.height = bg_size[0], bg_size[1]
        self.radius = self.rect.width / 2

    def move(self):
        if self.control:
            self.rect = self.rect.move(self.speed)
        else:
            self.rect = self.rect.move((self.side[0] * self.speed[0], \
                                        self.side[1] * self.speed[1]))

        # 如果小球的左侧出了边界,那么将小球左侧的位置改为右侧的边界
        # 这样便实现了从左边进入,右边出来的效果
        if self.rect.right <= 0:
            self.rect.left = self.width

        elif self.rect.left >= self.width:
            self.rect.right = 0

        elif self.rect.bottom <= 0:
            self.rect.top = self.height

        elif self.rect.top >= self.height:
            self.rect.bottom = 0

    def check(self, motion):
        if self.target < motion < self.target + 5:
            return True
        else:
            return False
            

class Glass(pygame.sprite.Sprite):
    def __init__(self, glass_image, mouse_image, bg_size):
        # 初始化动画精灵
        pygame.sprite.Sprite.__init__(self)

        self.glass_image = pygame.image.load(glass_image).convert_alpha()
        self.glass_rect = self.glass_image.get_rect()
        self.glass_rect.left, self.glass_rect.top = \
                             (bg_size[0] - self.glass_rect.width) // 2, \
                             bg_size[1] - self.glass_rect.height

        self.mouse_image = pygame.image.load(mouse_image).convert_alpha()
        self.mouse_rect = self.mouse_image.get_rect()
        self.mouse_rect.left, self.mouse_rect.top = \
                              self.glass_rect.left, self.glass_rect.top
        pygame.mouse.set_visible(False)
        
def main():
    pygame.init()

    grayball_image = r"D:\Code\Python\Pygame\pygame10:游戏胜利\gray_ball.png"
    greenball_image = r"D:\Code\Python\Pygame\pygame10:游戏胜利\green_ball.png"
    glass_image = r"D:\Code\Python\Pygame\pygame10:游戏胜利\glass.png"
    mouse_image = r"D:\Code\Python\Pygame\pygame10:游戏胜利\hand.png"
    bg_image = r"D:\Code\Python\Pygame\pygame10:游戏胜利\background.png"

    running = True

    # 添加魔性的背景音乐
    pygame.mixer.music.load(r"D:\Code\Python\Pygame\pygame10:游戏胜利\bg_music.ogg")
    pygame.mixer.music.play()

    # 添加音效
    loser_sound = pygame.mixer.Sound(r"D:\Code\Python\Pygame\pygame10:游戏胜利\loser.wav")
    laugh_sound = pygame.mixer.Sound(r"D:\Code\Python\Pygame\pygame10:游戏胜利\laugh.wav")
    winner_sound = pygame.mixer.Sound(r"D:\Code\Python\Pygame\pygame10:游戏胜利\winner.wav")
    hole_sound = pygame.mixer.Sound(r"D:\Code\Python\Pygame\pygame10:游戏胜利\hole.wav")

    # 音乐播放完时游戏结束
    GAMEOVER = USEREVENT
    pygame.mixer.music.set_endevent(GAMEOVER)

    # 根据背景图片指定游戏界面尺寸
    bg_size = width, height = 1024, 681
    screen = pygame.display.set_mode(bg_size)
    pygame.display.set_caption("Play the ball - FishC Demo")

    background = pygame.image.load(bg_image).convert_alpha()

    # 5 个坑的范围,因为 100% 命中太难,所以只要在范围内即可
    # 每个元素:(x1, x2, y1, y2)
    hole = [(117, 119, 199, 201), (225, 227, 390, 392), \
            (503, 505, 320, 322), (698, 700, 192, 194), \
            (906, 908, 419, 421)]

    # 存放要打印的消息
    msgs = []

    # 用来存放小球对象的列表
    balls = []
    group = pygame.sprite.Group()
    
    # 创建 5 个小球
    for i in range(5):
        # 位置随机,速度随机
        position = randint(0, width-100), randint(0, height-100)
        speed = [randint(1, 10), randint(1, 10)]
        ball = Ball(grayball_image, greenball_image, position, speed, bg_size, 5 * (i+1))
        # 检测新诞生的球是否会卡住其他球
        while pygame.sprite.spritecollide(ball, group, False, pygame.sprite.collide_circle):
            ball.rect.left, ball.rect.top = randint(0, width-100), randint(0, height-100)
        balls.append(ball)
        group.add(ball)

    # 生成摩擦摩擦的玻璃面板
    glass = Glass(glass_image, mouse_image, bg_size)

    # motion 记录鼠标在玻璃面板产生的事件数量
    motion = 0

    # 1 秒检查 1 次鼠标摩擦摩擦产生的事件数量
    MYTIMER = USEREVENT + 1
    pygame.time.set_timer(MYTIMER, 1000)

    # 设置持续按下键盘的重复响应
    pygame.key.set_repeat(100, 100)

    clock = pygame.time.Clock()

    print(balls)
    print(group)

    while running:
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()

            # 游戏失败
            elif event.type == GAMEOVER:
                loser_sound.play()
                pygame.time.delay(2000)
                laugh_sound.play()
                running = False

            # 1 秒检查 1 次鼠标摩擦摩擦产生的事件数量
            elif event.type == MYTIMER:
                if motion:
                    for each in group:
                        if each.check(motion):
                            each.speed = [0, 0]
                            each.control = True
                    motion = 0

            elif event.type == MOUSEMOTION:
                motion += 1

            # 当小球的 control 属性为 True 时
            # 可是使用按键 w、s、a、d 分别上、下、左、右移动小球
            # 带加速度的哦^_^
            elif event.type == KEYDOWN:
                if event.key == K_w:
                    for each in group:
                        if each.control:
                            each.speed[1] -= 1

                if event.key == K_s:
                    for each in group:
                        if each.control:
                            each.speed[1] += 1

                if event.key == K_a:
                    for each in group:
                        if each.control:
                            each.speed[0] -= 1

                if event.key == K_d:
                    for each in group:
                        if each.control:
                            each.speed[0] += 1

                if event.key == K_SPACE:
                    # 判断小球是否在坑内
                    for each in group:
                        if each.control:
                            for i in hole:
                                if i[0] <= each.rect.left <= i[1] and \
                                   i[2] <= each.rect.top <= i[3]:
                                    # 播放音效
                                    hole_sound.play()
                                    each.speed = [0, 0]
                                    # 从 group 中移出,这样其他球就会忽视它
                                    group.remove(each)
                                    # 放到 balls 列表中的最前,也就是第一个绘制的球
                                    # 这样当球在坑里时,其它球会从它上边过去,而不是下边
                                    temp = balls.pop(balls.index(each))
                                    balls.insert(0, temp)
                                    # 一个坑一个球
                                    hole.remove(i)
                            # 坑都补完了,游戏结束
                            if not hole:
                                pygame.mixer.music.stop()
                                winner_sound.play()
                                pygame.time.delay(3000)
                                # 打印“然并卵”
                                msg = pygame.image.load("win.png").convert_alpha()
                                msg_pos = (width - msg.get_width()) // 2, \
                                          (height - msg.get_height()) // 2
                                msgs.append((msg, msg_pos))
                                laugh_sound.play()

            
        screen.blit(background, (0, 0))
        screen.blit(glass.glass_image, glass.glass_rect)

        # 限制鼠标只能在玻璃内摩擦摩擦
        glass.mouse_rect.left, glass.mouse_rect.top = pygame.mouse.get_pos()
        if glass.mouse_rect.left < glass.glass_rect.left:
            glass.mouse_rect.left = glass.glass_rect.left
        if glass.mouse_rect.left > glass.glass_rect.right - glass.mouse_rect.width:
            glass.mouse_rect.left = glass.glass_rect.right - glass.mouse_rect.width
        if glass.mouse_rect.top < glass.glass_rect.top:
            glass.mouse_rect.top = glass.glass_rect.top
        if glass.mouse_rect.top > glass.glass_rect.bottom - glass.mouse_rect.height:
            glass.mouse_rect.top = glass.glass_rect.bottom - glass.mouse_rect.height

        screen.blit(glass.mouse_image, glass.mouse_rect)

        for each in balls:
            each.move()
            if each.collide:
                each.speed = [randint(1, 10), randint(1, 10)]
                each.collide = False
            if each.control:
                screen.blit(each.greenball_image, each.rect)
            else:
                screen.blit(each.grayball_image, each.rect)

        for each in group:
            # 先从组中移出当前球
            group.remove(each)
            # 判断当前球与其他球是否相撞
            if pygame.sprite.spritecollide(each, group, False, pygame.sprite.collide_circle):
                each.side[0] = -each.side[0]
                each.side[1] = -each.side[1]
                each.collide = True
                if each.control:
                    each.side[0] = -1
                    each.side[1] = -1
                    each.control = False
            # 将当前球添加回组中
            group.add(each)

        for msg in msgs:
            screen.blit(msg[0], msg[1])

        pygame.display.flip()
        clock.tick(30)


if __name__ == "__main__":
    # 这样做的好处是双击打开时如果出现异常可以报告异常,而不是一闪而过!
    try:
        main()
    except SystemExit:
        pass
    except:
        traceback.print_exc()
        pygame.quit()
        input()

你可能感兴趣的:(第089讲: Pygame:游戏胜利 | 学习记录(小甲鱼零基础入门学习Python))