Python游戏编程之旅(7):pygame中的冲突检测技术

上一个博客我们一起学习了pygame中的Sprite模块和如何加载动画

这次我们来一起学习pygame中的冲突检测技术。

pygame支持非常多的冲突检测技术,我们来一一的看一下他们是如何使用的:

一、精灵与精灵之间的冲突检测

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

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

举个例子:

1
2
3
4
5
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”

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

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

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

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

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

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

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

1
2
3
result = pygame.sprite.collide_circle(sprite_1,sprite_2)
if result:
    print “Collision occurred”

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

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

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

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

1
2
if pygame.sprite.collide_mask(sprite_1,sprite_2):
    print (“Collision occurred”)

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

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

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

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

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

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

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

好了大概常用的几种冲突检测函数我们已经了解完了,下面我们做一个小小的实例实际运用一下上面学到的知识。

二、冲突检测实例—吃苹果小游戏

先看一下效果图:

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

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

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

网盘下载地址:http://yunpan.cn/c3SttjmY2yYPk  访问密码 4a7b

github地址:https://github.com/XINCGer/Eat-apple-Game

1.模块化编程

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

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

1
import MyLibrary

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

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

2.高级行走动画

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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)

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

1
2
3
4
5
6
7
8
9
10
11
12
13
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

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

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

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

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

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

1
2
3
4
5
6
7
#检测玩家是否与食物冲突,是否吃到果实
        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);

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
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.columns1
        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
            elif player.X > 700: player.X = 700
            if 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()

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

转载自演道,想查看更及时的互联网产品技术热点文章请点击http://go2live.cn