python实现外星人入侵——3.事件分析

前言

讲解完了所有的类,接下来就应该讲一下事件了。
在游戏的过程中,最重要的其实应当是在事件发生后进行反应并重新渲染

先贴game_function的代码:

import sys
import pygame
from bullet import bullet
from alien import Alien
from time import sleep
#退出游戏+飞船移动
def check_events(ai_settings,screen,stats,sb,play_button,ship,aliens,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)
        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,bullets,mouse_x,mouse_y)
#响应按下按键
def check_keydown_events(event,ai_settings,screen,ship,bullets):
    if event.key == pygame.K_RIGHT: #向右移动
        ship.moving_right = True
    elif event.key == pygame.K_LEFT:
        ship.moving_left = True
    elif event.key == pygame.K_SPACE:
        fire_bullet(ai_settings,screen,ship,bullets)
    elif event.key == pygame.K_q:
        sys.exit()
#响应抬起按键
def check_keyup_events(event,ship):
    if event.key == pygame.K_RIGHT:
        ship.moving_right = False
    elif event.key == pygame.K_LEFT:
        ship.moving_left = False

def check_play_button(ai_settings,screen,stats,sb,play_button,ship,aliens,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 = True
        stats.reset_stats()

        sb.prep_score()
        sb.prep_high_score()
        sb.prep_level()
        sb.prep_ships()

        aliens.empty()
        bullets.empty()

        create_fleet(ai_settings,screen,stats,ship,aliens)
        ship.center_ship()

        ai_settings.initialize_dynamic_settings()   #恢复

#刷新屏幕
def update_screen(ai_setting,screen,stats,sb,ship,aliens,bullets,play_button):
    screen.fill(ai_setting.bg_color)
    #绘制所有子弹
    for bullet in bullets:
        bullet.draw_bullet()
    #显示飞船
    ship.blitme()
    #显示外星人
    aliens.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,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,bullets)

def check_bullet_alien_collisions(ai_settings,screen,stats,sb,ship,aliens,bullets):
    #检测是否击中,击中则删除对应的子弹和外星人
    collisions = pygame.sprite.groupcollide(bullets,aliens,True,True)
    """笔记:
    这个方法是检测每一个子弹的rect和外星人的rect,返回值为一个字典,key为子弹,value为外星人。
    遍历两个编组,当两个rect重叠,那么方法在字典中增加一对键值,并且在原编组中删除
    这里的两个Boolean是对应子弹和外星人需要删除,如果是‘高能子弹’在击败外星人仍保留,
    那么我们就可以将第一个Boolean设置为false"""
    if collisions:
        for alien in collisions.values():
            #确保每一个外星人都会被算上分数,防止一个子弹对应多个外星人
            stats.score += ai_settings.alien_points
            sb.prep_score()
        check_high_score(stats,sb)
    if len(aliens) == 0:
        #提升一个等级
        stats.level += 1
        sb.prep_level()
        #删除子弹并新建一群外星人
        bullets.empty()
        ai_settings.increase_sp()
        create_fleet(ai_settings,screen,stats,ship,aliens)

def update_aliens(ai_settings,stats,screen,sb,ship,aliens,bullets):
    check_fleet_deges(ai_settings,aliens)
    aliens.update()
    check_aliens_bottom(ai_settings,stats,screen,sb,ship,aliens,bullets)

    #检测外星人和飞船的碰撞
    """方法接受两个实参,一个是单个对象,另一个是编组"""
    if pygame.sprite.spritecollideany(ship,aliens):
        ship_hit(ai_settings,stats,screen,sb,ship,aliens,bullets)

def fire_bullet(ai_settings,screen,ship,bullets):
    if len(bullets) < ai_settings.bullet_allowed:
        #创建一颗子弹,加到编组中
        new_bullet = bullet(ai_settings,screen,ship)
        bullets.add(new_bullet)

def get_number_alien_x(ai_settings,alien_width):
    """计算一行有多少个外星人"""
    available_space_x = ai_settings.screen_width - 2*alien_width
    number_aliens_x = int(available_space_x/(2*alien_width))
    return number_aliens_x

def 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_rows

def create_alien(ai_settings,screen,aliens,alien_number,row_number):
    """创建一个外星人并放置在编组中"""
    alien = Alien(ai_settings,screen)
    alien_width = alien.rect.width
    alien.x = alien_width + 2*alien_width*alien_number
    alien.rect.x = alien.x
    alien.rect.y = alien.rect.height + 2*alien.rect.height*row_number
    aliens.add(alien)

def create_fleet(ai_settings,screen,stats,ship,aliens):
    """创建外星人群"""
    alien = Alien(ai_settings,screen)#第一个对象是用来计算相关数据的
    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):
            create_alien(ai_settings,screen,aliens,alien_number,row_number)

def check_fleet_deges(ai_settings,aliens):
    """有外星人得到边界时"""
    for alien in aliens.sprites():
        if alien.check_edge():
            change_fleet_direction(ai_settings,aliens)
            break

def change_fleet_direction(ai_settings,aliens):
    """将外星人整体下调,并改变方向"""
    for alien in aliens.sprites():
        alien.rect.y += ai_settings.fleet_drop_speed
    ai_settings.fleet_direction *= -1

def ship_hit(ai_settings,stats,screen,sb,ship,aliens,bullets):
    """响应外星人撞到船"""
    if stats.ships_left > 0:
        stats.ships_left -= 1
        #更新记分牌
        sb.prep_ships()
        #这里的逻辑是撞到了外星人就需要重新开始,将下一个飞船固定在中间并重新生成外星人
        bullets.empty()
        aliens.empty()
        

        create_fleet(ai_settings,screen,stats,ship,aliens)
        ship.center_ship()
        sleep(0.5)
    else:
        pygame.mouse.set_visible(True)
        stats.game_active = False

def check_aliens_bottom(ai_settings,stats,screen,sb,ship,aliens,bullets):
    """检查是否有外星人到达底端"""
    screen_rect = screen.get_rect()
    for alien in aliens.sprites():
        if alien.rect.bottom >= screen_rect.bottom:
            #按照和飞船撞到外星人一样处理
            ship_hit(ai_settings,stats,screen,sb,ship,aliens,bullets)
            break
    
def check_high_score(stats,sb):
    """检查是否出现最高分"""
    if stats.score > stats.high_score:
        stats.high_score = stats.score
        sb.prep_high_score()

准备

我们的外星人生成逻辑是这样的:
距离飞船两个外星人的距离,距离顶端一个外星人的距离
一行中每隔一个外星人的宽度生成一个外星人,同样是两边各留一个外星人的距离

create_fleet函数为创建外星人群:
首先创建了一个外星人案例,用来计算行数和列数,调用get_number_alien_x和get_number_rows计算;
随后双重for循环,调用创建一个外星人的函数creat_alien,分别确定当前x、y坐标,然后放在编组中。
没看错,还记得之前提到的精灵类吗,其中的Group编组就是专门对付这样多个对象的结构,还有很多方便的方法可以使用。

键鼠事件

需要管理的事件:

  • 按下左右键时,我们需要让飞船向对应方向移动(通过控制ship类中的方向Boolean变量)
  • 松开左右键,停止移动(将Boolean改为false)
  • 按下键盘,如果是"q"需要退出程序,如果是空格需要进行开火
  • 按下鼠标,如果是关闭窗口,需要退出程序;如果是在play按钮上,则需要开始游戏

先使用check_events函数检测所有的事件,然后进行筛选。
pygame.event.get()方法返回一个事件列表,我们遍历这个列表。
其中pygame.QUIT就是关闭窗口的事件,而sys.exit方法是用来结束的。

keydown和keyup则是键盘按下和抬起,调用的check_keydown/keyup_events()方法都还好。

play按钮事件

对于点击事件,check_play_button则涉及到了很多内容。
函数的目的是判断是否play按钮被点击,其中被点击的可能为第一次运行或者再来一局。

首先使用pygame.mouse.get_pos方法得到鼠标点击事件的位置,传入函数中;
在函数中对坐标进行判断,这里使用的是rect的collidepoint方法,传入一组xy就能判断坐标在不在矩形中。
当play按钮被点击,将会按顺序发生下列事件:

  • 隐藏光标,pygame.mouse.set_visible(Flase)
  • 将统计信息中的游戏状态设置为真,然后调用重置方法,将统计数据重置
  • 在数据重置后,调用记分牌的方法,将新数据进行渲染。
  • 调用编组empty方法,清空外星人和子弹(防止是再来一局)
  • 创建外星人群,将飞船调整到初始位置
  • 将设置中的数据恢复。
开火事件

在按下空格键,我们将调用fire函数。
首先别忘了我们有屏幕上最多子弹数限制(3枚),先判断子弹数量有没有超了,如果没有就创建一个子弹对象,并加入到编组中。

外星人更新

我们知道外星人的逻辑是不断左右移动(在alien类中的方法),如果碰到左右边界就向下移动10个像素;
如果碰到飞船或者是底端,则减少一条命或者结束游戏。

update_aliens函数:
首先检查外星人是否碰到左右边界,使用check_fleet_deges方法。
遍历每一个成员,如果有碰到的就调用change_fleet_direction方法,
改变外星人的移动方向(ai_setting中),并将每一个成员都向下移动10个像素。

然后调用alien的update方法,实现外星人的横向移动。

随后检查外星人是否接触到底部,使用check_aliens_bottom函数。
遍历每一个成员,如果和底端接触,则调用ship_hit函数(函数是处理飞船撞到外星人的,后面会提到)
这里属于偷了个懒,因为两者的处理结果相同。

最后使用pygame.sprite.spritecollodeany方法,传入单独对象和一个编组,用于判断是否有接触,如果有接触则调用ship_hit函数进行处理。
现在该提一下这个ship_hit函数了,目的是处理失败事件。
首先,不管是飞船碰到外星人还是外星人到底端,我们都需要将生命数减一,如果没有了直接结束游戏。
第一种情况:
将生命数减一,同时调用prep_ship更新记分牌;
将场上的外星人和子弹全部清空;
重新创建一波外星人并将飞船居中,挂起0.5秒继续游戏。

如果是要结束游戏,需要显示鼠标并将游戏状态设置为结束。

子弹更新

update_bullets函数。
首先调用bullet中的update方法,实现子弹的向上移动。
随后我们需要删除所有跑到屏幕之外的子弹,保证这些子弹不会占用子弹名额,并且防止卡顿。
最后判断是否有子弹和外星人发生碰撞,使用check_bullet_alien_collisions函数。

在check_bullet_alien_collisions函数中,我们使用了pygame.sprite.groupcollide方法:
参数:
传入两个编组,两个布尔型变量,这两个布尔型变量控制在判断碰撞之后是否删除对象。
这里的两个true代表在判断外星人和子弹相遇,就需要子弹和外星人都删除。
如果我们需要一个超能子弹,能连续穿过外星人,那么就需要将第一个Boolean设置为假。
返回值:
该函数的返回值是一个字典,key为子弹对象,value是外星人对象。
原理:
通过检测编组中每一个对象的rect和另一个编组对象的rect,如果发生重叠就在字典中添加一对键值,并且在原编组中删除

利用上述方法,我们将遍历字典中的每一个外星人,将分数添加到统计信息类stats。
在处理完分数之后,我们就需要检测最高分了:
使用check_high_score函数,通过判断统计类stats的状态来更新最高分和记分牌

最后别忘了需要检测外星人是否被消灭干净,在调用groupcollide方法之后,我们计算一下外星人编组的长度,如果长度为零,我们需要进行如下操作:

  1. 等级提升,并且使用prep_level渲染
  2. 删除屏幕上剩下的子弹
  3. 重新创建外星人
  4. 使用设置中的increase_sp方法加大难度

屏幕刷新

要明白,上述的函数除了计分板是在数据发生改变之后立即更新的,项外星人、子弹、飞船这些都是只修改了属性并没有渲染,所以我们采用了另外的函数进行统一处理。
update_screen函数:

  • 背景颜色填充(fill方法)
  • 绘制所有子弹
  • 显示飞船(指能发射子弹的那个,不是表示生命数的)
  • 显示外星人
  • 显示得分
  • 判断游戏状态,如果为停止则显示按钮
  • pygame.display.flip(),用于更新的方法

这里的子弹和外星人渲染方式是不一样的,虽然大家都是编组类型,但是因为外星人有图片而子弹是矩形框。

总结

虽然洋洋洒洒写了好多的函数,但是我们总结一下主要的部分,其实就是这样的:
在这里插入图片描述
(图片我在系列第一篇博客中发了xmind文件)
这样我们的事件就介绍完了,接下来的环节是介绍一下主函数的部分。

python实现外星人入侵——2

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值