本文目标

1,本章我们继续学习使用sprite木块,来实现我们游戏当中的碰撞检测

2,完成游戏实例:吃苹果小游戏

Pygame模块的Sprite碰撞检测

下面是几种常见的碰撞检测以及实现代码。

1.两个精灵之间的矩形检测

在只有两个精灵的时候我们可以使用pygame.sprite.collide_rect()函数来进行一对一的冲突检测。这个函数需要传递2个参数,并且每个参数都是需要继承自pygame.sprite.Sprite。

举个例子:

Python
spirte_1 = MySprite("sprite_1.png",200,200,1)
sprite_2 = MySprite("sprite_2.png",50,50,1)
result = pygame.sprite.collide_rect(sprite_1,sprite_2)
if result:
print("Collision occurred")


spirte_1=MySprite("sprite_1.png",200,200,1)
sprite_2=MySprite("sprite_2.png",50,50,1)
result=pygame.sprite.collide_rect(sprite_1,sprite_2)
ifresult:
print("Collision occurred")

MySprite使我们上个博客中创建的类,他继承自sprite。

Hint:这个函数还有一个非常有用的变体:pygame.sprite.collide_rect_ratio()。这个函数需要一个额外的浮点类型的参数。这个参数用来指定检测矩形的百分比。

有的时候我们希望冲突检测更精准一些的话,就可以收缩检测的区域,让矩形更小一些,就是通过这个参数控制的。使用方法如下:

Python
result = pygame.sprite.collide_rect_ratio( 0.5 )(sprite_1,sprite_2)
1
result=pygame.sprite.collide_rect_ratio(0.5)(sprite_1,sprite_2)

2.两个精灵之间的圆检测

矩形冲突检测并不适用于所有形状的精灵,因此pygame中还有个圆形冲突检测。pygame.sprite.collide_circle(),这个函数是基于每个精灵的半径值来进行检测的。

你可以自己指定半径,或者让函数自己计算半径。

Python
result = pygame.sprite.collide_circle(sprite_1,sprite_2)
if result:
print "Collision occurred"


result=pygame.sprite.collide_circle(sprite_1,sprite_2)
ifresult:
print"Collision occurred"

这个函数也有一个变体:pygame.sprite.collide_circle_ratio()。函数的功能和用法和上面的pygame.sprite.collide_rect_ratio()是类似的。

3.两个精灵之间的像素遮罩检测

如果矩形检测和圆形检测都不能满足我们的需求怎么办?别担心,pygame还为我们提供了一个更加精确的检测:pygame.sprite.collide_mask()。

这个函数接收两个精灵作为参数,返回值是一个bool变量。

Python
if pygame.sprite.collide_mask(sprite_1,sprite_2):
print ("Collision occurred")
1
2
ifpygame.sprite.collide_mask(sprite_1,sprite_2):
print("Collision occurred")
4.精灵和组之间的矩形冲突检测
Python
pygame.sprite.spritecollide(sprite,sprite_group,bool)。
1
pygame.sprite.spritecollide(sprite,sprite_group,bool)。

调用这个函数的时候,一个组中的所有精灵都会逐个地对另外一个单个精灵进行冲突检测,发生冲突的精灵会作为一个列表返回。

这个函数的第一个参数就是单个精灵,第二个参数是精灵组,第三个参数是一个bool值,最后这个参数起了很大的作用。当为True的时候,会删除组中所有冲突的精灵,False的时候不会删除冲突的精灵

Python
list_collide = pygame.sprite.spritecollide(sprite,sprite_group,False);
1
list_collide=pygame.sprite.spritecollide(sprite,sprite_group,False);

另外这个函数也有一个变体:pygame.sprite.spritecollideany()。这个函数在判断精灵组和单个精灵冲突的时候,会返回一个bool值。

5.精灵组之间的矩形冲突检测

Python
pygame.sprite.groupcollide()。
1
pygame.sprite.groupcollide()。

利用这个函数可以检测两个组之间的冲突,他返回一个字典。(键-值对)

游戏实例—吃苹果小游戏

先看一下效果图:


游戏开始会在屏幕上随机生成一些苹果,玩家通过上下左右方向键来控制人物去吃苹果。

吃到一个苹果,能量条就会增长一些,直到吃完所有的苹果,游戏结束。

【源代码+素材下载地址】

1.模块化编程

这个游戏会使用到我们上个博客创建的MySprite类,为了让这个类变的更具有可重用性,我们将它做成一个模块。

只要将类的实现代码放进一个单独的py,然后在使用的时候引入他就可以了。比如我们将这个单独的py取名为:

MyLibrary.py
Python
import MyLibrary
1
importMyLibrary

这样在使用这个模块里面的函数和类的时候我们只需要这样做:MyLibrary.fun()。但是这样看起来也不是很方便的说,因此我们使用import的变体:

Python
from MyLibrary import *
#将文件中的所有内容引入


fromMyLibraryimport*
#将文件中的所有内容引入

2.高级行走动画

通过效果图,我们可以看到程序里面用到了高级的行走动画,人物一共有上下左右四个方向的行走动画。

实际上这个精灵序列图里面一共有8个方向的行走动画,为了简便,我们只是使用了其中的四方向,如图:


通过行的数目就可以来方便的区分,动画是向左走还是向右走的。现在说起来可能有点比较难以理解,看完下面的代码就比较好理解了。我们还为Mysprite这个类增加了一个velocity属性,以便精灵可以根据其方向来移动。

Python
class MySprite(pygame.sprite.Sprite):
def __init__(self):
pygame.sprite.Sprite.__init__(self)
self.master_image = None
self.frame = 0
self.old_frame = -1
self.frame_width = 1
self.frame_height = 1
self.first_frame = 0
self.last_frame = 0
self.columns = 1
self.last_time = 0
self.direction = 0
#新增了velocity属性,他是一个point
self.velocity = Point(0.0,0.0)


classMySprite(pygame.sprite.Sprite):
def__init__(self):
pygame.sprite.Sprite.__init__(self)
self.master_image=None
self.frame=0
self.old_frame=-1
self.frame_width=1
self.frame_height=1
self.first_frame=0
self.last_frame=0
self.columns=1
self.last_time=0
self.direction=0
#新增了velocity属性,他是一个point
self.velocity=Point(0.0,0.0)

当按UP键的时候,将方向设置为0(向上),按DOWN键的时候,将方向设置为4(向下),按LEFT键,将方向设置为6(向左),按RIGHT键,将方向设置为2(向右)

Python
if keys[K_ESCAPE]: sys.exit()
elif keys[K_UP] or keys[K_w]:
player.direction = 0
player_moving = True
elif keys[K_RIGHT] or keys[K_d]:
player.direction = 2
player_moving = True
elif keys[K_DOWN] or keys[K_s]:
player.direction = 4
player_moving = True
elif keys[K_LEFT] or keys[K_a]:
player.direction = 6
player_moving = True


ifkeys[K_ESCAPE]:sys.exit()
elifkeys[K_UP]orkeys[K_w]:
player.direction=0
player_moving=True
elifkeys[K_RIGHT]orkeys[K_d]:
player.direction=2
player_moving=True
elifkeys[K_DOWN]orkeys[K_s]:
player.direction=4
player_moving=True
elifkeys[K_LEFT]orkeys[K_a]:
player.direction=6
player_moving=True

这个方向就是我们之前说的用来决定使用动画帧的范围方法。并且还有一个player_moving变量,在按键按下的时候将它置为True,也就是按键按下的时候才会有行走动画,否则人物将会是静止的。

3.判断人物与苹果的冲突

为了获得更精准的冲突,我们组合使用了不同的冲突函数。

首先用pygame.sprite.spritecollideany来判断玩家是否与任意的苹果产生了碰撞,如果产生碰撞,则再使用pygame.sprite.collide_circle_ratio缩小检测范围做一次检测,

看看到底是哪个苹果和人物产生了冲突,然后将产生碰撞的果实从精灵组中移除(remove)。

Python
#检测玩家是否与食物冲突,是否吃到果实
attacker = None
attacker = pygame.sprite.spritecollideany(player, food_group)
if attacker != None:
if pygame.sprite.collide_circle_ratio(0.65)(player,attacker):
player_health +=2;
food_group.remove(attacker);


#检测玩家是否与食物冲突,是否吃到果实
attacker=None
attacker=pygame.sprite.spritecollideany(player,food_group)
ifattacker!=None:
ifpygame.sprite.collide_circle_ratio(0.65)(player,attacker):
player_health+=2;
food_group.remove(attacker);

吃了果实以后,能量值会增加,然后我们通过绘制一个矩形的能量条来反映给用户。

好了最后上一下全部的源代码(不包含MyLibrary模块):

Python
import itertools, sys, time, random, math, pygame
from pygame.locals import *
from MyLibrary import *
def calc_velocity(direction, vel=1.0):
velocity = Point(0,0)
if direction == 0: #上
velocity.y = -vel
elif direction == 2: #右
velocity.x = vel
elif direction == 4: #下
velocity.y = vel
elif direction == 6: #左
velocity.x = -vel
return velocity
pygame.init()
screen = pygame.display.set_mode((800,600))
pygame.display.set_caption("吃苹果")
font = pygame.font.Font(None, 36)
timer = pygame.time.Clock()
#创建精灵组
player_group = pygame.sprite.Group()
food_group = pygame.sprite.Group()
#初始化玩家精灵组
player = MySprite()
player.load("farmer walk.png", 96, 96, 8)
player.position = 80, 80
player.direction = 4
player_group.add(player)
#初始化food精灵组
for n in range(1,50):
food = MySprite();
food.load("food_low.png", 35, 35, 1)
food.position = random.randint(0,780),random.randint(0,580)
food_group.add(food)
game_over = False
player_moving = False
player_health = 0
while True:
timer.tick(30)
ticks = pygame.time.get_ticks()
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
sys.exit()
keys = pygame.key.get_pressed()
if keys[K_ESCAPE]: sys.exit()
elif keys[K_UP] or keys[K_w]:
player.direction = 0
player_moving = True
elif keys[K_RIGHT] or keys[K_d]:
player.direction = 2
player_moving = True
elif keys[K_DOWN] or keys[K_s]:
player.direction = 4
player_moving = True
elif keys[K_LEFT] or keys[K_a]:
player.direction = 6
player_moving = True
else:
player_moving = False
if not game_over:
#根据角色的不同方向,使用不同的动画帧
player.first_frame = player.direction * player.columns
player.last_frame = player.first_frame + player.columns-1
if player.frame < player.first_frame:
player.frame = player.first_frame
if not player_moving:
#当停止按键(即人物停止移动的时候),停止更新动画帧
player.frame = player.first_frame = player.last_frame
else:
player.velocity = calc_velocity(player.direction, 1.5)
player.velocity.x *= 1.5
player.velocity.y *= 1.5
#更新玩家精灵组
player_group.update(ticks, 50)
#移动玩家
if player_moving:
player.X += player.velocity.x
player.Y += player.velocity.y
if player.X < 0: player.X = 0
elif player.X > 700: player.X = 700
if player.Y < 0: player.Y = 0
elif player.Y > 500: player.Y = 500
#检测玩家是否与食物冲突,是否吃到果实
attacker = None
attacker = pygame.sprite.spritecollideany(player, food_group)
if attacker != None:
if pygame.sprite.collide_circle_ratio(0.65)(player,attacker):
player_health +=2;
food_group.remove(attacker);
if player_health > 100: player_health = 100
#更新食物精灵组
food_group.update(ticks, 50)
if len(food_group) == 0:
game_over = True
#清屏
screen.fill((50,50,100))
#绘制精灵
food_group.draw(screen)
player_group.draw(screen)
#绘制玩家血量条
pygame.draw.rect(screen, (50,150,50,180), Rect(300,570,player_health*2,25))
pygame.draw.rect(screen, (100,200,100,180), Rect(300,570,200,25), 2)
if game_over:
print_text(font, 300, 100, "G A M E O V E R")
pygame.display.update()


importitertools,sys,time,random,math,pygame
frompygame.localsimport*
fromMyLibraryimport*
defcalc_velocity(direction,vel=1.0):
velocity=Point(0,0)
ifdirection==0:#上
velocity.y=-vel
elifdirection==2:#右
velocity.x=vel
elifdirection==4:#下
velocity.y=vel
elifdirection==6:#左
velocity.x=-vel
returnvelocity
pygame.init()
screen=pygame.display.set_mode((800,600))
pygame.display.set_caption("吃苹果")
font=pygame.font.Font(None,36)
timer=pygame.time.Clock()
#创建精灵组
player_group=pygame.sprite.Group()
food_group=pygame.sprite.Group()
#初始化玩家精灵组
player=MySprite()
player.load("farmer walk.png",96,96,8)
player.position=80,80
player.direction=4
player_group.add(player)
#初始化food精灵组
forninrange(1,50):
food=MySprite();
food.load("food_low.png",35,35,1)
food.position=random.randint(0,780),random.randint(0,580)
food_group.add(food)
game_over=False
player_moving=False
player_health=0
whileTrue:
timer.tick(30)
ticks=pygame.time.get_ticks()
foreventinpygame.event.get():
ifevent.type==QUIT:
pygame.quit()
sys.exit()
keys=pygame.key.get_pressed()
ifkeys[K_ESCAPE]:sys.exit()
elifkeys[K_UP]orkeys[K_w]:
player.direction=0
player_moving=True
elifkeys[K_RIGHT]orkeys[K_d]:
player.direction=2
player_moving=True
elifkeys[K_DOWN]orkeys[K_s]:
player.direction=4
player_moving=True
elifkeys[K_LEFT]orkeys[K_a]:
player.direction=6
player_moving=True

else:

player_moving=False
ifnotgame_over:
#根据角色的不同方向,使用不同的动画帧
player.first_frame=player.direction*player.columns
player.last_frame=player.first_frame+player.columns-1
ifplayer.frame
player.frame=player.first_frame
ifnotplayer_moving:
#当停止按键(即人物停止移动的时候),停止更新动画帧
player.frame=player.first_frame=player.last_frame
else:
player.velocity=calc_velocity(player.direction,1.5)
player.velocity.x*=1.5
player.velocity.y*=1.5
#更新玩家精灵组
player_group.update(ticks,50)
#移动玩家
ifplayer_moving:
player.X+=player.velocity.x
player.Y+=player.velocity.y
ifplayer.X<0:player.X=0
elifplayer.X>700:player.X=700
ifplayer.Y<0:player.Y=0
elifplayer.Y>500:player.Y=500
#检测玩家是否与食物冲突,是否吃到果实
attacker=None
attacker=pygame.sprite.spritecollideany(player,food_group)
ifattacker!=None:
ifpygame.sprite.collide_circle_ratio(0.65)(player,attacker):
player_health+=2;
food_group.remove(attacker);
ifplayer_health>100:player_health=100
#更新食物精灵组
food_group.update(ticks,50)
iflen(food_group)==0:
game_over=True
#清屏
screen.fill((50,50,100))
#绘制精灵
food_group.draw(screen)
player_group.draw(screen)
#绘制玩家血量条
pygame.draw.rect(screen,(50,150,50,180),Rect(300,570,player_health*2,25))
pygame.draw.rect(screen,(100,200,100,180),Rect(300,570,200,25),2)
ifgame_over:
print_text(font,300,100,"G A M E   O V E R")
pygame.display.update()

在下个博客里面我们将一起学习在游戏里面常用的一些数据结构: 数据,列表,元组,队列,栈。