当前位置: 代码迷 >> 综合 >> py实现外星人入侵(二次开发)——4.特殊外星人(高得分、大子弹和盾牌外星人)
  详细解决方案

py实现外星人入侵(二次开发)——4.特殊外星人(高得分、大子弹和盾牌外星人)

热度:73   发布时间:2023-12-12 23:48:59.0

前言

之前的游戏都是正常的外星人,这次我们添加了不一样的种类:

  • 击中得分*10的高得分外星人
  • 击中获得一个大子弹的外星人,大子弹的宽度更大,并且击中敌人不会消失,不计入总子弹数
  • 获得了一个盾牌的外星人,能抵御一次子弹攻击

当然了,为了提高可玩性,特殊外星人的左右移动速度是正常的1.5倍,下落距离是正常的2倍,并且另外特殊外星人出现的概率较低。

尝试贴图——失败

最开始是准备直接在外星人的图片上贴一个单词上去,因为实在是懒得自己做图片,还需要重新拆出来几个分组,但是在我往上贴图的过程中,却变成了这样:
在这里插入图片描述
直接把之前的图片挡住了。
经过测试,单词的效果和之前的外星人相同,能触发事件,所以结案了:
孩子的外星人图片被单词图片顶掉了
单词部分:

def write(self):#设置字号颜色self.text_color = (0x49,0xa7,0xda)self.font = pygame.font.SysFont(None,48)#设置文字if self.if_bullet:self.msg = "buttle"elif self.if_more:self.msg = "more"#制作文字并做成方框self.image =self.font.render(self.msg,True,self.text_color,(0x23,0xd7,0x31))self.image_rect = self.image.get_rect()#只是实现了暂时的对齐,后面还需要调整self.image_rect.centerx = self.rect.centerxself.image_rect.centery = self.rect.centery

可以看到我根本没写blit,但是就能显示,最后找到了aliens.draw()方法
aliens是存放了我所有外星人精灵(sprite)的编组(group),这个方法相当于是对每一个对象都调用了一次blit方法,那么就是说明我的alien图片被替换掉了……

无语子。

特殊外星人事件处理——失败

本来想着文字就文字吧,因为网上的文本显示都是这样的,使用render做成一个图片,然后贴上去。
所以我想的是在check_bullet_alien_collisions函数中处理,因为该函数本身就是处理外星人和子弹碰撞的事件。
在这里插入图片描述
然后,运行直接报错,说list对象没有if_more属性,然后我尝试打印了一下collisions:
在这里插入图片描述
完犊子。
这里再提一下pygame.sprite.groupcollide函数:
输入两个精灵的编组,判断元素是否相交,并对相交的一对进行处理,并返回一个字典。
我之前以为字典的内容其实就是相交的精灵对象,然后一打印,居然是这玩意,心态炸了。

定稿

我的思路是这样的,我们已知的冲突函数是没法把删除调的内容找出来,然后判断是哪种外星人,所以我们就干脆对每一种外星人都创建一个编组,缺点就是需要将之前的处理外星人事件函数任务几乎翻了四倍。
对于每一种,我都给出了一个专门的图像,放个图给大家乐一乐:
(不过还是要注意一点,外星人图片的大小是相同的,所以不影响后期的计算行数列数)
在这里插入图片描述
有植物大战僵尸文字版那味了(狗头
在这里插入图片描述
首先是在setting.py中添加了这样的设置:

self.special_speed_factor = 1.5 #特殊外星人的左右移动速度为正常1.5倍
self.special_score_facteor = 10 #特殊外星人得分为十倍

然后我们在alien_invasion.py中创建了一万个编组:
在这里插入图片描述
添加了特殊外星人,就需要记录我们拥有了多少颗大子弹,这里我将记录变量添加到stats中,和得分等数据一起:
在这里插入图片描述
(因为游戏开始大子弹数量也需要清零,所以将其放在方法中,方便以后调用)
因为还需要我们在屏幕上显示我们当前拥有的大子弹数目,所以我选择在显示飞船(声明)个数的下方、和等级齐平的位置显示,这就需要修改我们的记分牌:
学着等级的样子,我添加了更新大子弹个数的函数:

def prep_big_bullet(self):"""渲染大子弹的个数,数字形式"""self.bullet_img = self.font.render(str(self.stats.big_bullet),True,self.text_color,self.ai_settings.bg_color)# 将个数放在飞船下面self.bullet_rect = self.bullet_img.get_rect()self.bullet_rect.left = self.screen_rect.leftself.bullet_rect.bottom = self.level_rect.bottom

同时在渲染和初始化函数中进行了添加:
在这里插入图片描述
最后记得在修改了stats.big_bullet之后,调一下prep_big_bullet进行刷新就好了,不然是不会改变的。

上述部分都是边角,或者是可以直接copy魔改的部分,下面的事件处理和外星人类修改才是大头。

外星人类的修改

之前本来想创建子类的,但是后面想了一下,我们只需要创建一个通用外星人,然后放在不同的编组中,就能对应不同的外星人
说干就干。
首先我们添加了三个特殊外星人标识:
在这里插入图片描述
因为不同的外星人对应着不同的图像,所以我们还不能直接加载,我选择创建了一个方法:
(只需要记得给完标识符就调用方法即可,不给标识符就会出错)

    def loading(self):    # 加载外星人图像,并设置rect属性if self.if_bullet:self.image = pygame.image.load('./alien/image/alien_bullet.bmp')elif self.if_more:self.image = pygame.image.load('./alien/image/alien_more.bmp')elif self.if_shield:self.image = pygame.image.load('./alien/image/alien_shield.bmp')else:self.image = pygame.image.load('./alien/image/alien.bmp')self.rect = self.image.get_rect()# 开始每一个外星人都在屏幕的左上角self.rect.x = self.rect.widthself.rect.y = self.rect.height# 用来存储准确的位置,用其更新rect.xself.x = float(self.rect.x)

随后,在update方法中,添加了对不同种类的外星人有不同的左右移动速度(调用ai_setting)

成果长这样:

import pygame
from pygame.sprite import Spriteclass Alien(Sprite):"""单个外星人的类"""def __init__(self, ai_settings, screen):"""初始化外星人并设置起始位置"""super(Alien, self).__init__()self.screen = screenself.ai_settings = ai_settings# 移动方向,1表示向右,-1向左self.fleet_direction = 1# 特殊外星人self.if_bullet = Falseself.if_more = Falseself.if_shield = Falsedef loading(self):    # 加载外星人图像,并设置rect属性if self.if_bullet:self.image = pygame.image.load('./alien/image/alien_bullet.bmp')elif self.if_more:self.image = pygame.image.load('./alien/image/alien_more.bmp')elif self.if_shield:self.image = pygame.image.load('./alien/image/alien_shield.bmp')else:self.image = pygame.image.load('./alien/image/alien.bmp')self.rect = self.image.get_rect()# 开始每一个外星人都在屏幕的左上角self.rect.x = self.rect.widthself.rect.y = self.rect.height# 用来存储准确的位置,用其更新rect.xself.x = float(self.rect.x)def update(self):"""移动外星人"""if self.if_bullet or self.if_more or self.if_shield:self.x += self.ai_settings.alien_speed_factor * self.fleet_direction * self.ai_settings.special_speed_factorelse:self.x += self.ai_settings.alien_speed_factor * self.fleet_directionself.rect.x = self.xdef check_edge(self):"""如果碰到边界,返回true"""screen_rect = self.screen.get_rect()if self.rect.right >= screen_rect.right:return Trueelif self.rect.left <= 0:return True
事件函数的修改

可以这么说吧,基本上所有的函数都需要进行修改,但是有很多函数修改的原因是因为我们需要调用的函数需要的参数变多了,导致我们不得不修改。
先上代码,然后我们将重要的部分讲一下,剩下的函数跟着添加参数就完了。

import sys
import pygame
from bullet import bullet
from alien import Alien
from time import sleep
from random import randomdef check_events(ai_settings,screen,stats,sb,play_button,ship,aliens,aliens_bullet,aliens_more,aliens_shield,bullets):"""响应键鼠事件"""for event in pygame.event.get():if event.type == pygame.QUIT:sys.exit()elif event.type == pygame.KEYDOWN:          #表示按下按键check_keydown_events(event,ai_settings,screen,ship,bullets,stats)elif event.type == pygame.KEYUP:check_keyup_events(event,ship)elif event.type == pygame.MOUSEBUTTONDOWN:  #按下鼠标mouse_x,mouse_y = pygame.mouse.get_pos()check_play_button(ai_settings,screen,stats,sb,play_button,ship,aliens,aliens_bullet,aliens_more,aliens_shield,bullets,mouse_x,mouse_y)
#响应按下按键
def check_keydown_events(event,ai_settings,screen,ship,bullets,stats):if event.key == pygame.K_RIGHT: #向右移动ship.moving_right = Trueelif event.key == pygame.K_LEFT:ship.moving_left = Trueelif event.key == pygame.K_SPACE:fire_bullet(ai_settings,screen,ship,bullets,stats)elif event.key == pygame.K_q:sys.exit()
#响应抬起按键
def check_keyup_events(event,ship):if event.key == pygame.K_RIGHT:ship.moving_right = Falseelif event.key == pygame.K_LEFT:ship.moving_left = False
#响应点击play按钮
def check_play_button(ai_settings,screen,stats,sb,play_button,ship,aliens,aliens_bullet,aliens_more,aliens_shield,bullets,mouse_x,mouse_y):"""在玩家点击play开启游戏"""button_clicked = play_button.rect.collidepoint(mouse_x,mouse_y)  #能检测是否点击在play方块处if button_clicked and not stats.game_active:#隐藏光标pygame.mouse.set_visible(False)#重置游戏stats.game_active = Truestats.reset_stats()sb.prep_image()aliens.empty()bullets.empty()create_fleet(ai_settings,screen,stats,ship,aliens,aliens_bullet,aliens_more,aliens_shield)ship.center_ship()ai_settings.initialize_dynamic_settings()   #恢复#刷新屏幕
def update_screen(ai_setting,screen,stats,sb,ship,aliens,aliens_bullet,aliens_more,aliens_shield,bullets,play_button):screen.fill(ai_setting.bg_color)#绘制所有子弹for bullet in bullets:bullet.draw_bullet()#显示飞船ship.blitme()#显示外星人"""这个draw方法就相当于是对每一个用一下blit"""aliens.draw(screen)aliens_bullet.draw(screen)aliens_more.draw(screen)aliens_shield.draw(screen)#显示得分sb.show_score()#如果游戏为停止状态,显示按钮if not stats.game_active:play_button.draw_button()#让最近绘制的屏幕可见pygame.display.flip()def update_bullets(ai_settings,screen,stats,sb,ship,aliens,aliens_bullet,aliens_more,aliens_shield,bullets):bullets.update()"""更新子弹位置并删除已经消失的子弹"""for bullet in bullets:if bullet.rect.bottom <= 0:bullets.remove(bullet)check_bullet_alien_collisions(ai_settings,screen,stats,sb,ship,aliens,aliens_bullet,aliens_more,aliens_shield,bullets)def check_bullet_alien_collisions(ai_settings,screen,stats,sb,ship,aliens,aliens_bullet,aliens_more,aliens_shield,bullets):#检测是否击中外星人,击中则删除对应的子弹和外星人check_bullet_alien_collisions_part(ai_settings,screen,stats,sb,ship,aliens,bullets,"")check_bullet_alien_collisions_part(ai_settings,screen,stats,sb,ship,aliens_more,bullets,"more")check_bullet_alien_collisions_part(ai_settings,screen,stats,sb,ship,aliens_bullet,bullets,"bullet")check_high_score(stats,sb)#检测是否击中"盾牌"外星人for alien in aliens_shield:"""方法:精灵&编组,第三个参数是删掉编组中的元素,能进行删除"""collide_list = pygame.sprite.spritecollide(alien,bullets,True)if(collide_list):#删掉之前的盾牌外星人,换成一个普通的外星人alien_normal = Alien(ai_settings,screen)alien_normal.loading()alien_normal.fleet_direction = alien.fleet_directionalien_normal.x = alien.x    # 如果这个不给,就会刷新在屏幕左边alien_normal.rect.centerx = alien.rect.centerxalien_normal.rect.centery = alien.rect.centeryaliens.add(alien_normal)aliens_shield.remove(alien)#外星人为空if not len(aliens) and not len(aliens_bullet) and not len(aliens_more) and not len(aliens_shield):start_new_level(ai_settings,screen,stats,ship,aliens,aliens_bullet,aliens_more,aliens_shield,sb,bullets)def check_bullet_alien_collisions_part(ai_settings,screen,stats,sb,ship,aliens,bullets,different):#对每一种外星人都适用(除了子弹)collisions = pygame.sprite.groupcollide(bullets,aliens,True,True)"""笔记:这个方法是检测每一个子弹的rect和外星人的rect,返回值为一个字典,key为子弹,value为外星人。遍历两个编组,当两个rect重叠,那么方法在字典中增加一对键值,并且在原编组中删除这里的两个Boolean是对应子弹和外星人需要删除,如果是‘高能子弹’在击败外星人仍保留,那么我们就可以将第一个Boolean设置为false"""if collisions:for alien in collisions.values():if different == "more":stats.score += ai_settings.alien_points * ai_settings.special_score_facteorelse:stats.score += ai_settings.alien_pointssb.prep_score()Sound = pygame.mixer.Sound(ai_settings.boom_music)Sound.set_volume(0.3)Sound.play()if different == "bullet":stats.big_bullet += 1sb.prep_big_bullet()def start_new_level(ai_settings,screen,stats,ship,aliens,aliens_bullet,aliens_more,aliens_shield,sb,bullets):#提升一个等级stats.level += 1sb.prep_level()#删除子弹并新建一群外星人bullets.empty()ai_settings.increase_sp()create_fleet(ai_settings,screen,stats,ship,aliens,aliens_bullet,aliens_more,aliens_shield)def update_aliens(ai_settings,screen,stats,sb,ship,aliens,aliens_bullet,aliens_more,aliens_shield,bullets):check_fleet_deges(ai_settings, aliens, aliens_bullet, aliens_more, aliens_shield)aliens.update()aliens_bullet.update()aliens_more.update()aliens_shield.update()check_aliens_bottom(ai_settings,screen,stats,sb,ship,aliens,aliens_bullet, aliens_more, aliens_shield, bullets)#检测外星人和飞船的碰撞"""方法接受两个实参,一个是单个对象,另一个是编组,简单判断"""intersect_normal = pygame.sprite.spritecollideany(ship,aliens)intersect_bullet = pygame.sprite.spritecollideany(ship,aliens_bullet)intersect_more = pygame.sprite.spritecollideany(ship,aliens_more)intersect_shield = pygame.sprite.spritecollideany(ship,aliens_shield)if intersect_normal or intersect_bullet or intersect_more or intersect_shield:ship_hit(ai_settings,screen,stats,sb,ship,aliens,aliens_bullet,aliens_more,aliens_shield,bullets)def fire_bullet(ai_settings,screen,ship,bullets,stats):if len(bullets) < ai_settings.bullet_allowed:#创建一颗子弹,加到编组中new_bullet = bullet(ai_settings,screen,ship)bullets.add(new_bullet)fire_music(ai_settings.bullet_music,stats)def fire_music(path,stats):sound = pygame.mixer.Sound(path)if(stats.level > 10):sound.set_volume(1)else:sound.set_volume(0.1*stats.level)sound.play()def get_number_alien_x(ai_settings,alien_width):"""计算一行有多少个外星人"""available_space_x = ai_settings.screen_width - 2*alien_widthnumber_aliens_x = int(available_space_x/(2*alien_width))return number_aliens_xdef get_number_rows(ai_settings,ship_height,alien_height):"""计算能容纳多少行外星人"""#意思是在上边留一个高度,下边距离飞船两个的距离,防止贴脸available_space_y = (ai_settings.screen_height-3*alien_height-ship_height)  number_rows = int(available_space_y/(2*alien_height))return number_rowsdef create_alien(ai_settings,screen,aliens,aliens_bullet,aliens_more,aliens_shield,alien_number,row_number,if_bullet = False,if_more = False,if_shield = False):"""创建一个外星人并放置在编组中"""alien = Alien(ai_settings,screen)a = random()if a > 0.5:alien.fleet_direction *= -1#特殊外星人判断if if_bullet:alien.if_bullet = Trueelif if_more:alien.if_more = Trueelif if_shield:alien.if_shield = Truealien.loading()alien_width = alien.rect.widthalien.x = alien_width + 2*alien_width*alien_numberalien.rect.x = alien.xalien.rect.y = alien.rect.height + 2*alien.rect.height*row_numberif if_bullet:aliens_bullet.add(alien)elif if_more:aliens_more.add(alien)elif if_shield:aliens_shield.add(alien)else:aliens.add(alien)def create_fleet(ai_settings,screen,stats,ship,aliens,aliens_bullet,aliens_more,aliens_shield):"""创建外星人群"""alien = Alien(ai_settings,screen)#第一个对象是用来计算相关数据的alien.loading()number_aliens_x = get_number_alien_x(ai_settings,alien.rect.width)number_rows = get_number_rows(ai_settings,ship.rect.width,alien.rect.height)for row_number in range(number_rows):for alien_number in range(number_aliens_x):a = random()if a > 0.3:if a > 0.95:#含有子弹create_alien(ai_settings,screen,aliens,aliens_bullet,aliens_more,aliens_shield,alien_number,row_number,if_bullet=True)elif a < 0.35:#高得分create_alien(ai_settings,screen,aliens,aliens_bullet,aliens_more,aliens_shield,alien_number,row_number,if_more=True)elif  0.35<a<0.40:#有盾牌create_alien(ai_settings,screen,aliens,aliens_bullet,aliens_more,aliens_shield,alien_number,row_number,if_shield=True)else:create_alien(ai_settings,screen,aliens,aliens_bullet,aliens_more,aliens_shield,alien_number,row_number)def check_fleet_deges(ai_settings,aliens,aliens_bullet,aliens_more,aliens_shield):"""有外星人得到边界时"""for alien in aliens.sprites():if alien.check_edge():change_fleet_direction(ai_settings,alien)for alien in aliens_bullet.sprites():if alien.check_edge():change_fleet_direction(ai_settings,alien)for alien in aliens_more.sprites():if alien.check_edge():change_fleet_direction(ai_settings,alien)for alien in aliens_shield.sprites():if alien.check_edge():change_fleet_direction(ai_settings,alien)def change_fleet_direction(ai_settings,alien):"""将外星人下调,并改变方向"""if alien.if_bullet or alien.if_more or alien.if_shield:alien.rect.y += ai_settings.special_drop_speedelse:alien.rect.y += ai_settings.fleet_drop_speedalien.fleet_direction *= -1def ship_hit(ai_settings,screen,stats,sb,ship,aliens,aliens_bullet,aliens_more,aliens_shield,bullets):"""响应外星人撞到船"""if stats.ships_left > 0:stats.ships_left -= 1#更新记分牌sb.prep_ships()#这里的逻辑是撞到了外星人就需要重新开始,将下一个飞船固定在中间并重新生成外星人bullets.empty()aliens.empty()aliens_bullet.empty()aliens_more.empty()aliens_shield.empty()        create_fleet(ai_settings,screen,stats,ship,aliens,aliens_bullet,aliens_more,aliens_shield)ship.center_ship()sleep(0.5)else:with open("./alien/"+stats.max_name,"w") as max_num:highest = str(stats.high_score)max_num.write(highest)            pygame.mouse.set_visible(True)stats.game_active = Falsedef check_aliens_bottom(ai_settings,screen,stats,sb,ship,aliens,aliens_bullet, aliens_more, aliens_shield, bullets):"""检查是否有外星人到达底端"""screen_rect = screen.get_rect()for alien in aliens.sprites():if alien.rect.bottom >= screen_rect.bottom:#按照和飞船撞到外星人一样处理ship_hit(ai_settings,screen,stats,sb,ship,aliens,bullets)breakfor alien in aliens_bullet.sprites():if alien.rect.bottom >= screen_rect.bottom:#按照和飞船撞到外星人一样处理ship_hit(ai_settings,screen,stats,sb,ship,aliens,bullets)breakfor alien in aliens_more.sprites():if alien.rect.bottom >= screen_rect.bottom:#按照和飞船撞到外星人一样处理ship_hit(ai_settings,screen,stats,sb,ship,aliens,bullets)breakfor alien in aliens_shield.sprites():if alien.rect.bottom >= screen_rect.bottom:#按照和飞船撞到外星人一样处理ship_hit(ai_settings,screen,stats,sb,ship,aliens,bullets)breakdef check_high_score(stats,sb):"""检查是否出现最高分"""if stats.score > stats.high_score:stats.high_score = stats.scoresb.prep_high_score()
分析:

先给出直接是一改四的情况:(真要写还是建议自己去一个个函数翻一遍,比我这样稳)

  1. update_screen函数,之前有一个使用Group的draw方法更新外星人,这里我们需要对四种外星人都更新了。(draw方法相当于对每一个精灵对象都调用一次blit)
  2. check_bullet_alien_collisions函数,是处理子弹击中外星人事件的函数,因为这部分比较长所以我重构了一下,下面的check_bullet_alien_collisions_part函数就是主体,对每一种外星人都有效同时还有一个字符串进行类型区分。(注意一下这部分不包括盾牌,我们在后面会提到)
  3. update_alien函数中有一个检查是否有外星人碰到左右边界以及控制外星人的左右移动,还有后面的外星人碰到飞船,都是四倍的快乐
  4. check_fleet_deges中检测外星人到达底部
  5. ship_hit方法,处理结束这一批的函数

剩下的情况,就是之前的逻辑需要修改的了。

首先就是处理子弹&外星人碰撞函数,其他的外星人都是碰过了就完了,但是盾牌外星人却不行,这里我采取的是盾牌外星人被击中会变成普通外星人
如果还是之前的方法,即直接对两个编组进行处理,那么我们就没法得到被击中的盾牌的位置,所以我采取的是使用单个精灵对象和子弹编组的碰撞判断:
在这里插入图片描述
方法中,第一个参数是单个精灵对象,第二个参数是编组,第三个参数是判断编组中和精灵相遇后会不会消失(这里的True表示会消失),但不会管精灵的问题。
就有一点像水果忍者里面的刀,总不能切完水果刀断了吧,那还打个鬼。

在判断碰到之后,我们在原处设一个普通alien对象,然后将参数对齐,将原来的盾牌删除。
在这里插入图片描述
(centerx是控制位置的,但是按照我们的逻辑,centerx是跟着x走的,所以我们如果不修改x的值,外星人就会出现在屏幕的左边)

在创建外星人整体和创建单个外星人的函数中,只需要记得以下几点:

  1. 先给标识符再调用loading方法,最后添加到对应的编组中即可
  2. 外星人整体按照概率进行分类,传入不同的标识符即可

最后我们在主文件调用函数中修改:
在这里插入图片描述
就这样,我们实现了特殊外星人和相关数据的处理,还差一个大子弹的问题,这个交给下期处理。
最后看一眼我们的成果:
在这里插入图片描述
一言难尽

另外如果是使用蓝牙鼠标的读者,运行程序注意一下,鼠标经常会接触不良,接收器部分贼烫,也是无语了。

  相关解决方案