文章目录
🚴大家好!我是近视的脚踏实地,虽然近视,但是脚踏实地。这一篇继续要完善飞机大战的游戏,这篇主要完成的内容是完成玩家飞机和各类型敌机的完美碰撞检测
(一)为每一个类添加撞击时的不同效果
当敌我两机发生碰撞时,两方应该是玉石俱焚的,下面为每一个类添加撞击时的不同效果图:👇
❤小型敌机enemy1:
❤中型敌机enemy2:
❤大型敌机enemy3:
❤玩家飞机me:
1️⃣为每一个类加载毁灭的图片
下面就先为每一个类将这些画面,图画给加载进去
❤myplane.py👇
那么这里就是先创建了一个self.destroy_images = [] 列表来存所有玩家飞机毁坏的图片,接着用extend() 方法将每一个图片添加进去
❤enemy.py — class SmallEnemy👇
❤enemy.py — class MidEnemy👇
❤enemy.py — class BigEnemy👇
2️⃣为每一个增加active属性
接下来要给每一个类添加active属性,这个属性表示"我"还活着,也就是说当该属性的值为True的时候,我们就让飞机显示正常的飞行状态,而当这个属性为False的时候,表示这个飞机已经遇难了,那我们就依次来显示它毁灭的画面.
然后显示完毁灭画面之后,调用这个reset()方法重新初始化,那调用reset()之后它又重新作为一个新生的敌机出现,那的这个时候的active又变为True,所以reset()方法需要把这个属性变为True,然后我们在main函数检测到碰撞的时候把它变为False,
❤enemy.py — class SmallEnemy👇
❤enemy.py — class MidEnemy👇
❤enemy.py — class BigEnemy👇
❤myplane.py👇
在所有敌机的类中的reset方法设置active属性为True
(二)修改main模块的代码,完成碰撞检测功能
首先先来个中弹即将毁灭的几张图片索引,因为每种飞机毁灭都有几种状态,上面我们已经都把他们加载进各自的类里边并存放到他们对应的列表中了,定义这个索引,等等下面判断active的状态为False的时候来切换不同的毁灭状态时就可以用到
1️⃣绘制大型飞机时,先来检测它的 active
代码解析:如果大型敌机毁灭的时候,首先我们就播放一下死亡之歌enemy3_down_sound ,接着if not(delay % 3): 每隔3帧,就是能被3整除的时候,就来播放1帧的这个毁灭的画面,设置这个延迟是为了更好地能看到效果,不然咻的一下就过去了就看不到,所以这里也用了延迟的小技巧,然后依次播放剩下的毁灭图片就好了,那绘制这个毁灭画面就需要索引他的destroy_images[] 列表。
那么screen.blit(each.destroy_images[e3_destroy_index], each.rect) 就是每一个飞机对象调用他的当前索引值索引得到的毁灭图片,接下来就应该将这个e3_destroy_index是索引值向下一个。
那么就是 e3_destroy_index = (e3_destroy_index + 1) % 6 ,因为大型敌机毁灭图片一共有6张图片,所以这里%6,那么这个表达式得到的值就永远是0-5之间,当e3_destroy_index=5,在加1等于6,6再%6就等于0.刚好切换会第一张毁灭的图片。
接着if e3_destroy_index == 0: 来判断索引值是不是等于0,是的话就each.reset() ,这里是这样的,比如该开始进来的时候 e3_destroy_index的值是0,,那么就把第一张毁灭的图片画出来,画出来之后呢,索引值就加1,1%6这个值就变成1了,不等于0,接着继续播放第二张,以此类推,播放第三张,第四张,第五张,第六张,到第六张的时候,也就是索引值变成了5,在+1变成6,6%6等于0,就满足这个条件,说明他一轮过去了,就去执行reset方法复活,重新回到界面的上方
2️⃣绘制中小型、和玩家飞机时,先来检测它的 active
接下来中型和小型也是类似的,直接copy,注意把相应的变量名改正过来就好了,还有下面是%4,因为他们只有4张毁灭的图片,还又就是绘制玩家飞机时,如果玩家挂了,这里还没实现到生命数量,就先随便来个打印顶替一下,然后把running设置为False退出循环,其他的内容这里就不多赘述了
3️⃣完成飞机之间的碰撞检测代码
那么首先要知道的是一旦玩家飞机碰撞到敌机,导致的结果必将是敌我同归于尽,下面开始搞代码
首先就是在绘制玩家飞机之前来检测一下玩家飞机是否被撞,那么之前讲碰撞检测的时候讲过是调用sprite模块的spritecollide方法,那么就是enemies_down = pygame.sprite.spritecollide(me,enemies,False) ,第一参数me就需要检测的精灵,是否跟第二个参数enemies一组精灵里面的任何一个精灵发生碰撞,第三个参数是设置是否从组中删除检测到碰撞的精灵,如果设置为True的话,如果发生碰撞,他会把组中跟他产生碰撞的精灵给删除掉,那么这里是设置为False的,那就是如果发生了碰撞,就会把跟me发生碰撞的精灵以一个列表的形式返回给enemies_down 变量,
那么接着 if enemies_down: 就是如果检测到enemies_down 里面有精灵,那说明玩家飞机已挂,就把active设置为False。
接着 迭代enemies_down里边的每一个元素,把他们的每一个active都改为False
下面来运行测试,效果如下:
可以看到碰撞检测是实现了,下面修改一下代码,让飞机无敌,再看看碰撞检测有啥问题
那么现在就可以看到了,两个飞机并没有真正接触,敌机就挂掉了,这是因为我们使用了普通的spritecollide这个函数,之前说过,这个函数默认情况下,他是以图片的矩形区域作为检测的范围,而我们飞机的矩形区域那就大了,他事实上就是白色框框那里,透明的,如下图👇 ,所以就是当碰到矩形那个范围,就认为已经发生了碰撞
那其实sprite模块里边有个叫做collide_mask 的函数我们可以利用,这个函数要求检测对象拥有一个叫做mask 的属性,那么这个mark就是用于指定检测的范围,那么关于 mask,Pygame 还专门写了一个 mask 模块,其中有一个叫做 from_surface() 的函数,可以将 Surface 对象中非透明的部分标记为 mask 并返回。比如这个上图这个飞机,矩形框就是透明的,飞机是非透明的,所以调用了就会把非透明的部分就是飞机标记为mark并返回
那么接下来就去#myplane.py的类中增加一个mask属性,让他等于pygame模块中的mask模块,参数把飞机的图片传进去就好了,事实上这个form_surface(self.image1) 就是讲Surface对象也就是我们的图片,把非透明的部分设置为mask,标记为mask然后返回,所以运用这个方法就是检测非透明部分是否发生碰撞。然后敌机也要记得去加上
还有就是要去spritecollide 把第四个参数补上,第四个参数是指定一个回调函数,他是用于定制特殊的检测方法,如果说第四个参数忽略的话,那么是默认检测精灵的之间的 rect (矩形)属性
下面再来运行看看效果
那这样我们就实现了完美的碰撞检测了!!然后就是在大部分时间段,大家编写游戏开发的时候,一般都是用这个方法来检测碰撞的
4️⃣纠正一个小BUG
刚才的代码有一个比较明显的BUG,导致部分音效无法正常播放,这里只是演示一小段,而且我的录屏也播放不了声音,哈哈哈,就是待会有大飞机噜噜噜的开出来的时候,还有小飞机,中飞机,一块撞的话,肯定有几个音效是播放不了的。
因为无论是大飞机、中飞机还是小飞机,我们的毁灭都是检测他们的active,如果为False的话,就播放声音,然后画一张他们挂掉的图片,这样就一帧过去了,然后第二帧播放声音,然后if not(delay % 3): 进不去,第三帧,播放声音,诶,进去了,那么画第二张他毁灭的图片。
那么问题就来了,就是说你要画完它所有毁灭的图片,需要好多帧,但是这里你却重复地播放了很多次他毁灭的音效,那么一个飞机挂掉,只需要播放他挂掉了这么一次音效就可以了,而不用播放,他他他…挂挂挂.吊吊吊…了了了这样子一个音效,你要同时播放多个,那么导致的直接后果就是你把所有音效的通道都给占满了,因为pygame默认情况下只有八条音效的通道,那么这里"他他他…挂挂挂.吊吊吊…了了了" 就把通道占满了,其他就没了,播放不了,所以现在要做的就是,让他播放一次就好 了
那么这里就是如上图修改就可以解决了,第一帧刚进来的时候,e3_destroy_index是0,然后就播放一次音效,然后第一张毁灭图片画出来,e3_destroy_index +1 再次进来 不符合,那他就不播放了,直接到下面画第二张毁灭图,到最后他再次等于0 的时候,他就reset(),又活过来了,就又绘制正常的飞机了。然后把下面中型。小型,玩家飞机都以这种形式来改就可以了
那其实上一篇有个地方也有bug的,这里-50就是,也就是说-50,-49,-48他都要播放一次,那还得了,那么就是他从上到下走了多少像素,就播放了多少次,然后一次还要几秒钟,那这样子相当于卡住了八个通道,全部卡住了,全部播放这个声音了,所以这样是不对的,如下修改,就是当他移动到-50的位置,就给个参数-1,就是表示循环播放这个音效,然后就是当他挂掉的时候就不播放了,
当然,如果你嫌默认的8个音效通道太少的话,可以使用 mixer 模块的 set_num_channels() 方法来设置通道数多一点。
5️⃣附上这部分的源码
mian.py
import pygame
import sys
import traceback
import myplane
import enemy
from pygame.locals import *
pygame.init()
pygame.mixer.init()
bg_size = width,height = 480,700
screen = pygame.display.set_mode(bg_size)
pygame.display.set_caption("飞机大战 -- Monster ZF")
background = pygame.image.load("images/background.png").convert()
# 载入游戏音乐
pygame.mixer.music.load("sound/game_music.ogg")
pygame.mixer.music.set_volume(0.2)
bullet_sound = pygame.mixer.Sound("sound/bullet.wav")
bullet_sound.set_volume(0.2)
bomb_sound = pygame.mixer.Sound("sound/use_bomb.wav")
bomb_sound.set_volume(0.2)
supply_sound = pygame.mixer.Sound("sound/supply.wav")
supply_sound.set_volume(0.2)
get_bomb_sound = pygame.mixer.Sound("sound/get_bomb.wav")
get_bomb_sound.set_volume(0.2)
get_bullet_sound = pygame.mixer.Sound("sound/get_bullet.wav")
get_bullet_sound.set_volume(0.2)
upgrade_sound = pygame.mixer.Sound("sound/upgrade.wav")
upgrade_sound.set_volume(0.2)
enemy3_fly_sound = pygame.mixer.Sound("sound/enemy3_flying.wav")
enemy3_fly_sound.set_volume(0.2)
enemy1_down_sound = pygame.mixer.Sound("sound/enemy1_down.wav"