@yanglt7
2018-12-12T14:06:50.000000Z
字数 5376
阅读 1584
Pygame
Pygame 的 sprite 模块提供了一个动画精灵的基类,游戏中的小球就是通过继承它而创建出来的精灵。
例 9-1
import pygame
import sys
from pygame.locals import *
from random import *
class Ball(pygame.sprite.Sprite): # 球类继承自 Sprite 类
def __init__(self, image, position, speed):
pygame.sprite.Sprite.__init__(self) # 初始化动画精灵
self.image = pygame.image.load(image).convert_alpha()
self.rect = self.image.get_rect()
self.rect.left, self.rect_top = position # 将小球放在指定位置
self.speed = speed
def main():
pygame.init()
ball_image = "grey_ball.png"
bg_image = "bg.png"
running = True
bg_size = width, height = 640, 480 # 根据背景图片指定游戏界面尺寸
screen = pygame.display.set_mode(bg_size)
pygame.display.set_caption("Play the ball - FishC Demo")
background = pygame.image.load(bg_image).convert_alpha()
balls = [] # 用来存放小球对象的列表
# 创建 5 个小球
BALL_NUM = 5
for i in range(BALL_NUM):
# 位置随机,速度随机
position = randint(0, width - 100), randint(0, height - 100)
speed = [randint(-10, 10), randint(-10, 10)]
ball = Ball(ball_image, position, speed)
balls.append(ball)
clock = pygame.time.Clock()
while running:
for event in pygame.event.get():
if event.type == QUIT:
sys.exit()
screen.blit(background, (0, 0))
for each in balls:
screen.blit(each.image, each.rect)
pygame.display.flip()
clock.tick(30)
if __name__ == "__main__":
main()
在 Ball 类中添加 move() 方法,然后在绘制每个小球前先调用一次 move() 移动到新的位置。
例 9-2
...
def move(self):
self.rect = self.rect.move(self.speed)
...
for each in balls:
each.move()
screen.blit(each.image, each.rect)
...
for each in balls:
each.move()
screen.blit(each.image, each.rect)
...
如果小球从页面的上方穿过,会从下方出现,同样,如果小球从左边进入会从右边出来。
...
class Ball(pygame.sprite.Sprite):
# 增加一个背景尺寸的参数
def __init__(self, image, position, speed, bg_size):
...
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
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
...
例 10-1
检测各小球之间是否发生碰撞,一旦发生便修改小球的移动方向。
...
def collide_check(item, target):
col_balls = []
for each in 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
...
# 先让所有小球移动一步
for each in balls:
each.move()
screen.blit(each.image, each.rect)
# 检测各个小球之间是否发生碰撞
for i in range(BALL_NUM):
# 先将要检测的小球拿出来
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)
...
虽然实现了碰撞检测,但有时会出现小球卡住的现象。
原因是:当小球在诞生的位置恰好有其他小球,因此检测到两个小球发生碰撞,速度取反,但如果两个小球相互覆盖的范围大于移动一次的距离,那就会出现卡住的现象(反向移动后取反,又变成相向移动,速度不变的情况下是死循环)。
解决方案:在小球诞生的时候立刻检查该位置是否有其他小球,有点话修改新生小球的位置。
例 10-2
...
# 创建 5 个小球
BALL_NUM = 5
for i in range(BALL_NUM):
# 位置随机,速度随机
position = randint(0, width - 100), randint(0, height - 100)
speed = [randint(-10, 10), randint(-10, 10)]
ball = Ball(ball_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)
...
不过这个 collide_check 只适用于圆与圆间的碰撞检测,如果是其他多边形或不规则图形,那么就得不到相应的效果了。
Pygame 的 sprite 模块已经提供了碰撞检测的函数。
sprite 模块提供了一个 spritecollide() 函数,用于检测某个精灵是否与指定组中的其他精灵碰撞。
spritecollide(sprite, group, dokill, collided = None)
例 11-1
...
# 用来存放小球对象的列表
balls = []
group = pygame.sprite.Group()
# 创建 5 个小球
BALL_NUM = 5
for i in range(BALL_NUM):
# 位置随机,速度随机
position = randint(0, width - 100), randint(0, height - 100)
speed = [randint(-10, 10), randint(-10, 10)]
ball = Ball(ball_image, position, speed, bg_size)
# 检测新诞生的球是否会卡住其他球
while pygame.sprite.spritecollide(ball, group, False):
ball.rect.left, ball.rect.top = randint(0, width-100), randint(0, height-100)
balls.append(ball)
group.add(ball)
...
for each in balls:
each.move()
screen.blit(each.image, each.rect)
for each in group:
# 先从组中移除当前球
group.remove(each)
# 判断当前球与其他球是否相撞
if pygame.sprite.spritecollide(each, group, False):
each.speed[0] = -each.speed[0]
each.speed[1] = -each.speed[1]
# 将当前球添加回组中
group.add(each)
...
有时候小球还没碰撞就弹开了。因为上面的代码没有设置 spritecollide() 函数的第四个参数,默认这个参数是 None,表示检测精灵的 rect 属性是否重叠。
由于小球的背景是透明的,所以看上去好像没有发生碰撞就弹开了(其实对应的 rect 已经重叠了)。因此,需要实现圆形的碰撞检测,需要指定 spritecollide() 函数的最后一个参数。
例 11-2
spritecollide() 函数的最后一个参数是指定一个回调函数,用于定制特殊的检测方法。而 sprite 模块中正好有一个 collide_circle() 函数用于检测两个圆之间是否发生碰撞。
注意:这个函数需要精灵对象中必须有一个 radius(半径)属性才行。
class Ball(pygame.sprite.Sprite):
def __init__(self, image, position, speed, bg_size):
...
self.radius = self.rect.width / 2
...
# 创建 5 个小球
BALL_NUM = 5
for i in range(BALL_NUM):
# 位置随机,速度随机
position = randint(0, width - 100), randint(0, height - 100)
speed = [randint(-10, 10), randint(-10, 10)]
ball = Ball(ball_image, position, speed, bg_size)
# 检测新诞生的球是否会卡住其他球
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)
...
for each in group:
# 先从组中移除当前球
group.remove(each)
# 判断当前球与其他球是否相撞
if pygame.sprite.spritecollide(each, group, False):
each.speed[0] = -each.speed[0]
each.speed[1] = -each.speed[1]
# 将当前球添加回组中
group.add(each)
...
摘自《零基础入门学习Python》