《零基础入门学习Python》第089讲:Pygame:游戏胜利

通过摩擦摩擦,可以使得小球变绿色,并不再随机移动:

玩家此时可以通过键盘上的 WSAD 按键来上下左右的移动小球,我们现在就来写响应相关按键事件的代码:

import pygame
import sys
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.speed = speed
        #1、为每个小球设定一个不同的目标;
        self.target = target
        #5、小球应该添加一个 control 属性,用于记录当前的状态(绿色 -> 玩家控制 or 灰色 -> 随机移动)。
        self.control = False
        self.width, self.height = bg_size[0], bg_size[1]
        self.radius = self.rect.width / 2  #增加半径属性

    def move(self):
        self.rect = self.rect.move(self.speed)

        # 如果小球的左侧出了边界,那么将小球左侧的位置改为右侧的边界
        # 这样便实现了从左边进入,右边出来的效果
        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
    #3、为小球添加一个 check() 方法,用于判断鼠标在1秒钟内产生的事件数量是否匹配此目标;
    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)#设置Pygame 光标不可见
       
def main():
    pygame.init()

    grayball_image = "gray_ball.png"
    greenball_image = "green_ball.png"
    bg_image = "background.png"
    glass_image = "glass.png"
    mouse_image = "hand.png"

    running = True

    #添加背景音乐
    pygame.mixer.music.load('bg_music.ogg')
    pygame.mixer.music.set_volume(0.2)#设置音量    
    pygame.mixer.music.play()#播放

    #加载音效
    winner_sound = pygame.mixer.Sound("winner.wav")
    winner_sound.set_volume(0.2)
    loser_sound = pygame.mixer.Sound("loser.wav")
    loser_sound.set_volume(0.2)
    laugh_sound = pygame.mixer.Sound("laugh.wav")
    laugh_sound.set_volume(0.2)
    hole_sound = pygame.mixer.Sound("hole.wav")
    hole_sound.set_volume(0.2)

    #背景音乐会贯穿游戏的始终,背景音乐完整播放一次我们视为游戏的时间,
        
    #音乐播放完时,游戏结束
    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 - Python Demo")

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

    # 用来存放小球对象的列表
    balls = []
    group = pygame.sprite.Group()

    # 创建五个小球
    for i in range(5):
        # 位置随机,速度随机
        position = randint(0, width-100), randint(0, height-100)
        speed = [randint(-10, 10), randint(-10, 10)]
        ball = Ball(grayball_image, greenball_image, position, speed, bg_size, 5 * (i + 1)) #target 设为5-30,比较适中
        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)
    #2、创建一个motion 变量来记录每一秒钟产生事件数量;
    motion = 0
    #########################################
    #4.1、添加一个自定义事件,每一秒钟触发一次。
    MYTIMER = USEREVENT + 1   #自定义事件的知识点可以查看上一节课的末尾注解
    pygame.time.set_timer(MYTIMER, 1000)

    pygame.key.set_repeat(100, 100)
    
    clock = pygame.time.Clock()

    while running:
        for event in pygame.event.get():
            if event.type == QUIT:
                sys.exit()
          
            elif event.type == GAMEOVER: #判断事件是否为我们自定义的GAMEOVER事件
                loser_sound.play()  
                pygame.time.delay(2000)#暂停2秒
                laugh_sound.play()
                running = False

            #4.2、 调用每个小球的 check() 检测 motion 的值是否匹配某一个小球的目标,并将motion重新初始化,以便记录下1秒鼠标事件数量;
            elif event.type == MYTIMER:
                if motion:
                    for each in group:
                        if each.check(motion):
                            each.speed = [0, 0]
                            each.control = True
                    motion = 0
            #需要计算一下motion
            elif event.type == MOUSEMOTION:
                motion += 1

            #用户通过WSAD按键移动绿色小球,使用+= 和 -= 而不是 +1 或者 -1,是为了体验加速度的感觉
            elif event.type == KEYDOWN:
                if event.key == K_w:  #W,上
                    for each in group:
                        if each.control:
                            each.speed[1] -= 1
                        
                if event.key == K_a:  #a,左
                    for each in group:
                        if each.control:
                            each.speed[0] -= 1
                        
                if event.key == K_s:  #s,下
                    for each in group:
                        if each.control:
                            each.speed[1] += 1
                        
                if event.key == K_d:  #d, 右
                    for each in group:
                        if each.control:
                            each.speed[0] += 1
        #在默认我们是不能感受到加速度的感觉的,
        #因为在默认情况下,无论你是简单的按一下按键或是按住按键不放,
        #Pygame都只为你发送一个键盘按下的事件
        #不过我们可以通过 key 模块的 set_repeat() 方法来设置是否重复响应按下某个按键
        # pygame.key.set_repeat(delay, interval)
        #--delay 参数制动第一次发送事件的延迟时间
        #--interval 参数指定重复发送事件的时间间隔
        #--如果不带任何参数,表示取消重复发送事件
        #为了使小球获得加速度的快感,我们设置按键的重复间隔为100毫秒,在while 循环前面设置。
            
                
        screen.blit(background, (0, 0))
        
        screen.blit(glass.glass_image, glass.glass_rect)

        #将小手光标画在Pygame 默认光标位置上
        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.mouse_rect.bottom - glass.mouse_rect.height:
            glass.mouse_rect.top = glass.mouse_rect.bottom - glass.mouse_rect.height

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

        for each in balls:
            each.move()
            #如果小球的 control 属性为 True,就画绿球
            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.speed[0] = -each.speed[0]
                each.speed[1] = -each.speed[1]

            group.add(each)#还要把自己放进去

        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 each in group:
            group.remove(each) #把自身拿出来

            if pygame.sprite.spritecollide(each, group, False, pygame.sprite.collide_circle):#把自己和别的球进行碰撞检测
                each.speed[0] = -each.speed[0]
                each.speed[1] = -each.speed[1]

                #发生碰撞,绿球就会失控
                each.control = False

            group.add(each)#还要把自己放进去

这个很好搞定,接下来我们要做的就是让小球在碰撞的时候获得一个新的随机速度,这将大大的加大游戏的难度,因为均匀的速度是可以测算出来的,但是如果是以很慢的速度碰撞,却以新的速度反弹,你就会躲闪不及。

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

如果我们这样子改:

#碰撞检测
for each in group:
            group.remove(each) #把自身拿出来

            if pygame.sprite.spritecollide(each, group, False, pygame.sprite.collide_circle):#把自己和别的球进行碰撞检测
                each.speed = [randint(-10, 10), randint(-10, 10)]
                
                #发生碰撞,绿球就会失控
                each.control = False

            group.add(each)#还要把自己放进去

就会出现有的两个小球碰撞之后 如胶似漆的不愿意分开,如下图红框所示:

这就会让用户感觉这个游戏好卡,我们又该怎么解决呢?

首先分析产生这种情况的原因:

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

那怎么解决呢?

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

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

import pygame
import sys
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
        #加多一个side表示速度的方向
        self.side = [choice([-1, 1]), choice([-1, 1])]#第一个元素表示水平方向,第二个元素表示垂直方向,使用random 的choice() 方法随机选择-1和1
        self.speed = speed
        #加多一个属性表示是否碰撞了
        self.collide = False
        #1、为每个小球设定一个不同的目标;
        self.target = target
        #5、小球应该添加一个 control 属性,用于记录当前的状态(绿色 -> 玩家控制 or 灰色 -> 随机移动)。
        self.control = False
        self.width, self.height = bg_size[0], bg_size[1]
        self.radius = self.rect.width / 2  #增加半径属性

    def move(self):        
        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
    #3、为小球添加一个 check() 方法,用于判断鼠标在1秒钟内产生的事件数量是否匹配此目标;
    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)#设置Pygame 光标不可见
       
def main():
    pygame.init()

    grayball_image = "gray_ball.png"
    greenball_image = "green_ball.png"
    bg_image = "background.png"
    glass_image = "glass.png"
    mouse_image = "hand.png"

    running = True

    #添加背景音乐
    pygame.mixer.music.load('bg_music.ogg')
    pygame.mixer.music.set_volume(0.2)#设置音量    
    pygame.mixer.music.play()#播放

    #加载音效
    winner_sound = pygame.mixer.Sound("winner.wav")
    winner_sound.set_volume(0.2)
    loser_sound = pygame.mixer.Sound("loser.wav")
    loser_sound.set_volume(0.2)
    laugh_sound = pygame.mixer.Sound("laugh.wav")
    laugh_sound.set_volume(0.2)
    hole_sound = pygame.mixer.Sound("hole.wav")
    hole_sound.set_volume(0.2)

    #背景音乐会贯穿游戏的始终,背景音乐完整播放一次我们视为游戏的时间,
        
    #音乐播放完时,游戏结束
    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 - Python Demo")

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

    # 用来存放小球对象的列表
    balls = []
    group = pygame.sprite.Group()

    # 创建五个小球
    for i in range(5):
        # 位置随机,速度随机
        position = randint(0, width-100), randint(0, height-100)
        #speed 初始化就只为正数了
        speed = [randint(1, 10), randint(1, 10)]
        ball = Ball(grayball_image, greenball_image, position, speed, bg_size, 5 * (i + 1)) #target 设为5-30,比较适中
        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)
    #2、创建一个motion 变量来记录每一秒钟产生事件数量;
    motion = 0
    #########################################
    #4.1、添加一个自定义事件,每一秒钟触发一次。
    MYTIMER = USEREVENT + 1   #自定义事件的知识点可以查看上一节课的末尾注解
    pygame.time.set_timer(MYTIMER, 1000)

    pygame.key.set_repeat(100, 100)
    
    clock = pygame.time.Clock()

    while running:
        for event in pygame.event.get():
            if event.type == QUIT:
                sys.exit()
          
            elif event.type == GAMEOVER: #判断事件是否为我们自定义的GAMEOVER事件
                loser_sound.play()  
                pygame.time.delay(2000)#暂停2秒
                laugh_sound.play()
                running = False

            #4.2、 调用每个小球的 check() 检测 motion 的值是否匹配某一个小球的目标,并将motion重新初始化,以便记录下1秒鼠标事件数量;
            elif event.type == MYTIMER:
                if motion:
                    for each in group:
                        if each.check(motion):
                            each.speed = [0, 0]
                            each.control = True
                    motion = 0
            #需要计算一下motion
            elif event.type == MOUSEMOTION:
                motion += 1

            #用户通过WSAD按键移动绿色小球,使用+= 和 -= 而不是 +1 或者 -1,是为了体验加速度的感觉
            elif event.type == KEYDOWN:
                if event.key == K_w:  #W,上
                    for each in group:
                        if each.control:
                            each.speed[1] -= 1
                        
                if event.key == K_a:  #a,左
                    for each in group:
                        if each.control:
                            each.speed[0] -= 1
                        
                if event.key == K_s:  #s,下
                    for each in group:
                        if each.control:
                            each.speed[1] += 1
                        
                if event.key == K_d:  #d, 右
                    for each in group:
                        if each.control:
                            each.speed[0] += 1
        #在默认我们是不能感受到加速度的感觉的,
        #因为在默认情况下,无论你是简单的按一下按键或是按住按键不放,
        #Pygame都只为你发送一个键盘按下的事件
        #不过我们可以通过 key 模块的 set_repeat() 方法来设置是否重复响应按下某个按键
        # pygame.key.set_repeat(delay, interval)
        #--delay 参数制动第一次发送事件的延迟时间
        #--interval 参数指定重复发送事件的时间间隔
        #--如果不带任何参数,表示取消重复发送事件
        #为了使小球获得加速度的快感,我们设置按键的重复间隔为100毫秒,在while 循环前面设置。
            
                
        screen.blit(background, (0, 0))
        
        screen.blit(glass.glass_image, glass.glass_rect)

        #将小手光标画在Pygame 默认光标位置上
        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.mouse_rect.bottom - glass.mouse_rect.height:
            glass.mouse_rect.top = glass.mouse_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
            #如果小球的 control 属性为 True,就画绿球
            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
                                
                #发生碰撞,绿球就会失控
                each.control = False

            group.add(each)#还要把自己放进去

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


if __name__ == "__main__":
    main()

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

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

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

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

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

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

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

那应该怎么做呢?

我们在move这里对小球进行修改,然后还需要对两个绿球的相撞进行修改,代码如下:

import pygame
import sys
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
        #加多一个side表示速度的方向
        self.side = [choice([-1, 1]), choice([-1, 1])]#第一个元素表示水平方向,第二个元素表示垂直方向,使用random 的choice() 方法随机选择-1和1
        self.speed = speed
        #加多一个属性表示是否碰撞了
        self.collide = False
        #1、为每个小球设定一个不同的目标;
        self.target = target
        #5、小球应该添加一个 control 属性,用于记录当前的状态(绿色 -> 玩家控制 or 灰色 -> 随机移动)。
        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
    #3、为小球添加一个 check() 方法,用于判断鼠标在1秒钟内产生的事件数量是否匹配此目标;
    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)#设置Pygame 光标不可见
       
def main():
    pygame.init()

    grayball_image = "gray_ball.png"
    greenball_image = "green_ball.png"
    bg_image = "background.png"
    glass_image = "glass.png"
    mouse_image = "hand.png"

    running = True

    #添加背景音乐
    pygame.mixer.music.load('bg_music.ogg')
    pygame.mixer.music.set_volume(0.2)#设置音量    
    pygame.mixer.music.play()#播放

    #加载音效
    winner_sound = pygame.mixer.Sound("winner.wav")
    winner_sound.set_volume(0.2)
    loser_sound = pygame.mixer.Sound("loser.wav")
    loser_sound.set_volume(0.2)
    laugh_sound = pygame.mixer.Sound("laugh.wav")
    laugh_sound.set_volume(0.2)
    hole_sound = pygame.mixer.Sound("hole.wav")
    hole_sound.set_volume(0.2)

    #背景音乐会贯穿游戏的始终,背景音乐完整播放一次我们视为游戏的时间,
        
    #音乐播放完时,游戏结束
    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 - Python Demo")

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

    # 用来存放小球对象的列表
    balls = []
    group = pygame.sprite.Group()

    # 创建五个小球
    for i in range(5):
        # 位置随机,速度随机
        position = randint(0, width-100), randint(0, height-100)
        #speed 初始化就只为正数了
        speed = [randint(1, 10), randint(1, 10)]
        ball = Ball(grayball_image, greenball_image, position, speed, bg_size, 5 * (i + 1)) #target 设为5-30,比较适中
        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)
    #2、创建一个motion 变量来记录每一秒钟产生事件数量;
    motion = 0
    #########################################
    #4.1、添加一个自定义事件,每一秒钟触发一次。
    MYTIMER = USEREVENT + 1   #自定义事件的知识点可以查看上一节课的末尾注解
    pygame.time.set_timer(MYTIMER, 1000)

    pygame.key.set_repeat(100, 100)
    
    clock = pygame.time.Clock()

    while running:
        for event in pygame.event.get():
            if event.type == QUIT:
                sys.exit()
          
            elif event.type == GAMEOVER: #判断事件是否为我们自定义的GAMEOVER事件
                loser_sound.play()  
                pygame.time.delay(2000)#暂停2秒
                laugh_sound.play()
                running = False

            #4.2、 调用每个小球的 check() 检测 motion 的值是否匹配某一个小球的目标,并将motion重新初始化,以便记录下1秒鼠标事件数量;
            elif event.type == MYTIMER:
                if motion:
                    for each in group:
                        if each.check(motion):
                            each.speed = [0, 0]
                            each.control = True
                    motion = 0
            #需要计算一下motion
            elif event.type == MOUSEMOTION:
                motion += 1

            #用户通过WSAD按键移动绿色小球,使用+= 和 -= 而不是 +1 或者 -1,是为了体验加速度的感觉
            elif event.type == KEYDOWN:
                if event.key == K_w:  #W,上
                    for each in group:
                        if each.control:
                            each.speed[1] -= 1
                        
                if event.key == K_a:  #a,左
                    for each in group:
                        if each.control:
                            each.speed[0] -= 1
                        
                if event.key == K_s:  #s,下
                    for each in group:
                        if each.control:
                            each.speed[1] += 1
                        
                if event.key == K_d:  #d, 右
                    for each in group:
                        if each.control:
                            each.speed[0] += 1
        #在默认我们是不能感受到加速度的感觉的,
        #因为在默认情况下,无论你是简单的按一下按键或是按住按键不放,
        #Pygame都只为你发送一个键盘按下的事件
        #不过我们可以通过 key 模块的 set_repeat() 方法来设置是否重复响应按下某个按键
        # pygame.key.set_repeat(delay, interval)
        #--delay 参数制动第一次发送事件的延迟时间
        #--interval 参数指定重复发送事件的时间间隔
        #--如果不带任何参数,表示取消重复发送事件
        #为了使小球获得加速度的快感,我们设置按键的重复间隔为100毫秒,在while 循环前面设置。
            
                
        screen.blit(background, (0, 0))
        
        screen.blit(glass.glass_image, glass.glass_rect)

        #将小手光标画在Pygame 默认光标位置上
        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.mouse_rect.bottom - glass.mouse_rect.height:
            glass.mouse_rect.top = glass.mouse_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
            #如果小球的 control 属性为 True,就画绿球
            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)#还要把自己放进去

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


if __name__ == "__main__":
    main()

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

这里有两点需要注意的:

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

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

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

最终完整代码如下:

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
        #加多一个side表示速度的方向
        self.side = [choice([-1, 1]), choice([-1, 1])]#第一个元素表示水平方向,第二个元素表示垂直方向,使用random 的choice() 方法随机选择-1和1
        self.speed = speed
        #加多一个属性表示是否碰撞了
        self.collide = False
        #1、为每个小球设定一个不同的目标;
        self.target = target
        #5、小球应该添加一个 control 属性,用于记录当前的状态(绿色 -> 玩家控制 or 灰色 -> 随机移动)。
        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
    #3、为小球添加一个 check() 方法,用于判断鼠标在1秒钟内产生的事件数量是否匹配此目标;
    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)#设置Pygame 光标不可见
       
def main():
    pygame.init()

    grayball_image = "gray_ball.png"
    greenball_image = "green_ball.png"
    bg_image = "background.png"
    glass_image = "glass.png"
    mouse_image = "hand.png"

    running = True

    #添加背景音乐
    pygame.mixer.music.load('bg_music.ogg')
    pygame.mixer.music.set_volume(0.2)#设置音量    
    pygame.mixer.music.play()#播放

    #加载音效
    winner_sound = pygame.mixer.Sound("winner.wav")
    winner_sound.set_volume(0.2)
    loser_sound = pygame.mixer.Sound("loser.wav")
    loser_sound.set_volume(0.2)
    laugh_sound = pygame.mixer.Sound("laugh.wav")
    laugh_sound.set_volume(0.2)
    hole_sound = pygame.mixer.Sound("hole.wav")
    hole_sound.set_volume(0.2)

    #背景音乐会贯穿游戏的始终,背景音乐完整播放一次我们视为游戏的时间,
        
    #音乐播放完时,游戏结束
    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 - Python 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()

    # 创建五个小球
    for i in range(5):
        # 位置随机,速度随机
        position = randint(0, width-100), randint(0, height-100)
        #speed 初始化就只为正数了
        speed = [randint(1, 10), randint(1, 10)]
        ball = Ball(grayball_image, greenball_image, position, speed, bg_size, 5 * (i + 1)) #target 设为5-30,比较适中
        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)
    #2、创建一个motion 变量来记录每一秒钟产生事件数量;
    motion = 0
    #########################################
    #4.1、添加一个自定义事件,每一秒钟触发一次。
    MYTIMER = USEREVENT + 1   #自定义事件的知识点可以查看上一节课的末尾注解
    pygame.time.set_timer(MYTIMER, 1000)

    pygame.key.set_repeat(100, 100)
    
    clock = pygame.time.Clock()

    while running:
        for event in pygame.event.get():
            if event.type == QUIT:
                pygame.quit()
                sys.exit()
          
            elif event.type == GAMEOVER: #判断事件是否为我们自定义的GAMEOVER事件
                loser_sound.play()  
                pygame.time.delay(2000)#暂停2秒
                laugh_sound.play()
                running = False

            #4.2、 调用每个小球的 check() 检测 motion 的值是否匹配某一个小球的目标,并将motion重新初始化,以便记录下1秒鼠标事件数量;
            elif event.type == MYTIMER:
                if motion:
                    for each in group:
                        if each.check(motion):
                            each.speed = [0, 0]
                            each.control = True
                    motion = 0
            #需要计算一下motion
            elif event.type == MOUSEMOTION:
                motion += 1

            #用户通过WSAD按键移动绿色小球,使用+= 和 -= 而不是 +1 或者 -1,是为了体验加速度的感觉
            elif event.type == KEYDOWN:
                if event.key == K_w:  #W,上
                    for each in group:
                        if each.control:
                            each.speed[1] -= 1
                        
                if event.key == K_a:  #a,左
                    for each in group:
                        if each.control:
                            each.speed[0] -= 1
                        
                if event.key == K_s:  #s,下
                    for each in group:
                        if each.control:
                            each.speed[1] += 1
                        
                if event.key == K_d:  #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.remove(each) #不会被撞到
                                    #下面两行语句就是完成该小球第一个绘制,然后其它小球就在该小球上边了
                                    temp = balls.pop(balls.index(each)) #先从temp 中弹出
                                    balls.insert(0, temp)  #然后插入到第一个的位置,就会第一个画了
                                    hole.remove(i) #把这个黑洞去掉
                            if not hole: #所有洞补完,游戏结束
                                pygame.mixer.music.stop()  #停止背景音乐
                                winner.sound.play() #播放胜利音效
                                pygame.time.delay(3000) #播放音效需要时间,延迟3秒
                                #打印一张图片
                                msg = pygame.image.load("win.png").conmvert_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)

        #将小手光标画在Pygame 默认光标位置上
        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.mouse_rect.bottom - glass.mouse_rect.height:
            glass.mouse_rect.top = glass.mouse_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
            #如果小球的 control 属性为 True,就画绿球
            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()

还有一点需要补充一下:

就是我们平时在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()

 

  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值