前言
讲解完了所有的类,接下来就应该讲一下事件了。
在游戏的过程中,最重要的其实应当是在事件发生后进行反应并重新渲染。
先贴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方法之后,我们计算一下外星人编组的长度,如果长度为零,我们需要进行如下操作:
- 等级提升,并且使用prep_level渲染
- 删除屏幕上剩下的子弹
- 重新创建外星人
- 使用设置中的increase_sp方法加大难度
屏幕刷新
要明白,上述的函数除了计分板是在数据发生改变之后立即更新的,项外星人、子弹、飞船这些都是只修改了属性并没有渲染,所以我们采用了另外的函数进行统一处理。
update_screen函数:
- 背景颜色填充(fill方法)
- 绘制所有子弹
- 显示飞船(指能发射子弹的那个,不是表示生命数的)
- 显示外星人
- 显示得分
- 判断游戏状态,如果为停止则显示按钮
- pygame.display.flip(),用于更新的方法
这里的子弹和外星人渲染方式是不一样的,虽然大家都是编组类型,但是因为外星人有图片而子弹是矩形框。
总结
虽然洋洋洒洒写了好多的函数,但是我们总结一下主要的部分,其实就是这样的:
(图片我在系列第一篇博客中发了xmind文件)
这样我们的事件就介绍完了,接下来的环节是介绍一下主函数的部分。