今天我们来学习碰撞检测,大部分游戏都是需要做碰撞检测的,因为你需要知道小球是否发生了碰撞,子弹是否击中了目标,主角是否踩到了狗屎。
那应该如何实现呢?
说白了,它这个原理很简单,就是检测两个精灵之间是否存在重叠的部分,像我们上节课的小球,在图1的情况下,它们就没有产生重叠,也就是没有发生碰撞。
图1当碰撞发生的那一刹那,width = r1 + r2,如图2所示。
图2当它们产生重叠,产生交集的时候,width < r1 + r2,如图3所示:
图3所以我们判断两个小球是否发生碰撞,我们只要检测两个小球的圆心距是否小于等于它们两个半径之和。
我们先来尝试自己写碰撞检测的函数:
(我当然知道 sprite 模块里面有提供现成的,但是我提议大家学习任何东西之前我们要搞懂它的原理,懂得了它的原理,以后你学习别的语言、别的知识就会事半功倍了)
函数名叫做 collide_check()。
from pygame.locals import *
from random import *
import pygame
import math
import sys
class Ball(pygame.sprite.Sprite) : #继承动画精灵基类
def __init__ (self,imgae,position,speed,bg_size) :
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(imgae).convert_alpha()
self.rect = self.image.get_rect() #获得球的尺寸
self.rect.left , self.rect.top = position #将出现的位置赋给球
self.speed = speed #设置速度
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 #让他从右边界回来
if self.rect.bottom < 0: #图片的底已经超出边界的上面
self.rect.top = self.height #让他从底部回来
if self.rect.left > self.width: #图片的左边已经超出边界的右边
self.rect.right = 0 #让他从左边回来
if self.rect.top > self.height: #如果图片的顶部已经超出边界的底部
self.rect.bottom = 0 #让他从顶部回来
#判断碰撞检测函数
def collide_check(item,target):
col_balls = [] #添加碰撞小球
for each in target: #对 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
def main() :
pygame.init()
bg_image = r"D:\Code\Python\Pygame\pygame6:动画精灵\background.png"
ball_image = r"D:\Code\Python\Pygame\pygame6:动画精灵\gray_ball.png"
running = True #为了以后而已有多种方法退出程序
bg_size = width , height = 1024 , 681 #背景大小
screen = pygame.display.set_mode(bg_size) # 设置背景大小
background = pygame.image.load(bg_image).convert_alpha() #画背景
balls = []
# 创建五个小球
BALL_NUM = 5
for i in range (BALL_NUM) : #生成5个球
position = randint (0,width-100) , randint(0,height-100) #要减去100是因为球图片尺寸的大小为100,随机生成位置
speed = [ randint (-10,10) , randint(-10,10) ]
ball = Ball(ball_image,position,speed,bg_size) #生成球的对象
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)) #将背景画到screen上
for each in balls: #每个球进行移动并重新绘制
each.move()
screen.blit(each.image, each.rect)
for i in range (BALL_NUM) : #循环5个小球,分别判断这个小球有没有和另外四个小球发生碰撞
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) #最后不要忘记把这个小球放回原位
pygame.display.flip()
clock.tick(30)
if __name__ == "__main__":
main()
在这里我们可以看到还有一点的缺陷
这两个小球会不断的发生碰撞,然后一直脱离不了,原因视频中已经解释了,不难理解,所以不再描述。
解决方法其实也很简单:就是在产生小球的时候进行一次碰撞检测,如果碰撞了,那么就重新产生一个小球即可。
from pygame.locals import *
from random import *
import pygame
import math
import sys
class Ball(pygame.sprite.Sprite) : #继承动画精灵基类
def __init__ (self,imgae,position,speed,bg_size) :
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(imgae).convert_alpha()
self.rect = self.image.get_rect() #获得球的尺寸
self.rect.left , self.rect.top = position #将出现的位置赋给球
self.speed = speed #设置速度
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 #让他从右边界回来
if self.rect.bottom < 0: #图片的底已经超出边界的上面
self.rect.top = self.height #让他从底部回来
if self.rect.left > self.width: #图片的左边已经超出边界的右边
self.rect.right = 0 #让他从左边回来
if self.rect.top > self.height: #如果图片的顶部已经超出边界的底部
self.rect.bottom = 0 #让他从顶部回来
#判断碰撞检测函数
def collide_check(item,target):
col_balls = [] #添加碰撞小球
for each in target: #对 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
def main() :
pygame.init()
bg_image = r"D:\Code\Python\Pygame\pygame6:动画精灵\background.png"
ball_image = r"D:\Code\Python\Pygame\pygame6:动画精灵\gray_ball.png"
running = True #为了以后而已有多种方法退出程序
bg_size = width , height = 1024 , 681 #背景大小
screen = pygame.display.set_mode(bg_size) # 设置背景大小
background = pygame.image.load(bg_image).convert_alpha() #画背景
balls = []
# 创建五个小球
BALL_NUM = 5
for i in range (BALL_NUM) : #生成5个球
position = randint (0,width-100) , randint(0,height-100) #要减去100是因为球图片尺寸的大小为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) #将所有的球对象添加到类表中,方便管理
clock = pygame.time.Clock() #生成刷新帧率控制器
while running :
for event in pygame.event.get():
if event.type == QUIT:
sys.exit()
screen.blit(background, (0, 0)) #将背景画到screen上
for each in balls: #每个球进行移动并重新绘制
each.move()
screen.blit(each.image, each.rect)
for i in range (BALL_NUM) : #循环5个小球,分别判断这个小球有没有和另外四个小球发生碰撞
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) #最后不要忘记把这个小球放回原位
pygame.display.flip()
clock.tick(30)
if __name__ == "__main__":
main()
上面是自己写的碰撞检测。接下来来谈一下现成的。
为什么我们要谈一下 sprite 提供的现成的碰撞检测函数呢?不知道大家有没有注意到 我们写的 collide_check() 函数只是适用于圆与圆之间的碰撞检测,其它的多边形(如矩形、三角形)以及一些不规则的多边形,那怎么办,我们就不会得到相应的结果了,我们可以试图去为每一种情况写一个碰撞检测函数,也不是不可以。
但其实 Pygame 的 sprite 模块事实上已经提供了一个成熟的碰撞检测的函数供我们使用,这也是我们要将我们的类继承 sprite 模块的 Sprite 基类的原因。
sprite 模块提供了 spritecollide() 方法用于 检测某个精灵是否与指定的组中的其它精灵发生碰撞。(和我们写的 collide_check() 原理基本上类似)
spritecollide(sprite, group, dokill, collided = None)
第一个参数 sprite 是指定被检测的精灵;(就是我们写的里面的 item)
第二个参数 group 是指定一个组(就是我们写的里面的 target 列表),它是 sprite 的组,因此要使用 sprite.Group() 来生成;
第三个参数 dokill 是设置是否从组中删除检测到碰撞的精灵,设置为True,则删除;
第四个参数 collided 是指定一个回调函数,用于定制特殊的检测方法,如果第四个参数忽略的话,默认是检测精灵之间的 rect 属性。
(我们这里不能使用默认的 检测方法,看下图,图中的情况会被检测为碰撞,但其实两小球还没碰撞)
collide_circle(left, right) 方法适用于检测两个圆之间是否发生碰撞,left 和 right 参数分别是两个精灵,所以我们直接 使用
collided = pygame.sprite.collide_circle
另外,collide_circle()方法需要精灵有一个半径的属性,所以我们给 Ball 类增加一个 radius 属性。
代码如下:
from pygame.locals import *
from random import *
import pygame
import math
import sys
class Ball(pygame.sprite.Sprite) : #继承动画精灵基类
def __init__ (self,imgae,position,speed,bg_size) :
pygame.sprite.Sprite.__init__(self)
self.image = pygame.image.load(imgae).convert_alpha()
self.rect = self.image.get_rect() #获得球的尺寸
self.rect.left , self.rect.top = position #将出现的位置赋给球
self.speed = speed #设置速度
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 #让他从右边界回来
if self.rect.bottom < 0: #图片的底已经超出边界的上面
self.rect.top = self.height #让他从底部回来
if self.rect.left > self.width: #图片的左边已经超出边界的右边
self.rect.right = 0 #让他从左边回来
if self.rect.top > self.height: #如果图片的顶部已经超出边界的底部
self.rect.bottom = 0 #让他从顶部回来
def main() :
pygame.init()
bg_image = r"D:\Code\Python\Pygame\pygame6:动画精灵\background.png"
ball_image = r"D:\Code\Python\Pygame\pygame6:动画精灵\gray_ball.png"
running = True #为了以后而已有多种方法退出程序
bg_size = width , height = 1024 , 681 #背景大小
screen = pygame.display.set_mode(bg_size) # 设置背景大小
background = pygame.image.load(bg_image).convert_alpha() #画背景
balls = []
group = pygame.sprite.Group() #因为使用自带的函数需要使用自己带的组,所以这里我们创建一个
# 创建五个小球
BALL_NUM = 5
for i in range (BALL_NUM) : #生成5个球
position = randint (0,width-100) , randint(0,height-100) #要减去100是因为球图片尺寸的大小为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) #组有add()和remove()方法
clock = pygame.time.Clock() #生成刷新帧率控制器
while running :
for event in pygame.event.get():
if event.type == QUIT:
sys.exit()
screen.blit(background, (0, 0)) #将背景aaa画到screen上
for each in balls: #每个球进行移动并重新绘制
each.move()
screen.blit(each.image, each.rect)
for each in group : #循环5个小球,分别判断这个小球有没有和另外四个小球发生碰撞
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()