一、前期准备
前面已经实现了小球的运动,碰撞检测,背景和音效,下面需要实现通过摩擦面板来控制小球的移动
主要思想:
鼠标移动会不断地产生事件( 把鼠标的移动看作事件用motion来记录次数),检测一秒钟鼠标在玻璃面板内产生多少事件,然后判定产生的事件是否可以匹配到小球(这里小球的用我们传递的值target 来匹配鼠标产生的事件)
具体过程:
- 为每个小球设定一个不同的目标(即不用的target值用来匹配鼠标产生的motion)
- 创建一个motion 变量来记录鼠标每一秒钟产生的事件数量(即鼠标移动的次数)
- 为小球添加一个check()方法,用于判断鼠标在1秒钟时间内产生的事件数量是否匹配此小球对应的target值
- 添加一个自定义事件,每一秒钟触发1次。调用每个小球的check()检测motion的值是否匹配某一个小球的目标,并将motion重新初始化(检查完一组初始化一次),以便记录下1秒内的鼠标事件数量
- 小球应该添加一个 control 属性,用于记录当前的状态(绿色->玩家控制 or 灰色->随机移动)
- 通过检查 control 属性决定绘制什么颜色的小球(从而达到区分的效果)
二、代码
import pygame
import sys
from pygame.locals import *
from random import *
# pygame.sprite.Sprite 代表游戏对象的简单基类
class Ball(pygame.sprite.Sprite):
def __init__(self,grayball_image,greenball_image,position,speed,bg_size,target):
super(Ball, self).__init__() # 利用 super函数继承父类
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
# 每个小球都有一个对应的target,用来判断motion是否与该target匹配
self.target = target
self.contol = False # 判定小球的状态,是被控制,还是失控,从而执行接下来的操作
self.width,self.height = bg_size[0],bg_size[1]
# 由于采用 collide_circle()方法检测圆的碰撞,所以需要设置 radius 属性
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
# 判断鼠标在 1秒钟内产生的事件数是否匹配此目标
def check(self,motion):
# 限定匹配的范围,这个5可以自己设
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):
super(Glass, self).__init__()
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.center = self.glass_rect.center # 初始设置在面板中央
# 原始的圆形的鼠标设置不可见
pygame.mouse.set_visible(False)
def main():
pygame.init()
#调用的图像
grayball_image = "gray_ball.png"
greenball_imgae = "green_ball.png"
bg_image = "background.png"
glass_imgae = "glass.png"
mouse_imgae = "hand.png"
# 此处用running来控制循环,因为后面牵扯到背景音乐结束,游戏结束
running = True
# 添加背景音乐
pygame.mixer.music.load("bg_music.wav")
pygame.mixer.music.play()
# 添加音效
win_sound = pygame.mixer.Sound("dog.wav")
loser_sound = pygame.mixer.Sound("loser.wav")
laugh_sound = pygame.mixer.Sound("laugh.wav")
hole_sound = pygame.mixer.Sound("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")
background = pygame.image.load(bg_image).convert_alpha()
# 用来存放小球对象的列表,用 group 完全可以实现
group = pygame.sprite.Group() # 用于保存和管理精灵对象
# 创建五个小球,位置随机,速度随机
for i in range(5):
# 球的大小是 100 x 100 防止越出边界,所以要宽和高减 100 的范围以内
position = randint(0,width-100),randint(0,height-100)
speed = [randint(-10,10),randint(-10,10)]
# 5*(i+1) 设定执行控制小球的最小事件数
ball = Ball(grayball_image,greenball_imgae,position,speed,bg_size,5*(i+1))
# 创建小球时需要检测,防止球与球之间发生重叠
# while pygame.sprite.spritecollide(ball,group,False):
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)
# 新建一个球后,将其加入检测组中
group.add(ball)
#创建 glass 面板
glass = Glass(glass_imgae,mouse_imgae,bg_size)
motion = 0 # 记录鼠标每一秒产生的事件数量
# 添加一个自定义的事件,第二个自定义事件所以 加1
Myevent = USEREVENT +1
# 限定发生这个事件的间隔 1000毫秒
pygame.time.set_timer(Myevent,1000)
clock = pygame.time.Clock() # 设置帧率
while running:
for event in pygame.event.get():
if event.type == QUIT:
sys.exit()
# 游戏结束事件,播放音乐
elif event.type == Gameover:
loser_sound.play()
pygame.time.delay(2000) # 延迟2000毫秒
laugh_sound.play()
running = False
# 检测自定义事件
elif event.type == Myevent:
if motion:
for each in group: # 检测是否有匹配鼠标运动事件的小球
if each.check(motion):
each.speed= [0,0] #匹配成功,速度置零,从而实现控制
each.contol = True
#检测一组后,motion置为0 ,防止过大,跳过检测
motion = 0
# 检测鼠标的事件
elif event.type == MOUSEMOTION:
motion +=1 # 只要鼠标移动,motion就加1
# 注意画的面板顺序,先画背景,再画面板,否则会被盖住
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 group:
each.move()
# 通过control 来判定小球是否被控制,绿色代表控制,灰色代表随机移动
if each.contol:
# 画绿色小球
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) #最高每秒不超过 30 次
if __name__ == '__main__':
main()