13.1 回顾项目
在开发后续程序前回顾当前项目进度。
明确后续开发的目标,做好计划尽量详尽
清理前期代码,需要重构的重构好。
13.2 创建第一个外星人
1、创建Alien类
注意点:screen和settings两个形参,一般在创建角色类的时候都会设定。
self.x = float(self.rect.x)将位置变成浮点型,也是一般操作,用于精确调整位置。
2、创建Alien实例
3、让外星人出现在屏幕上
在game_functions.py中update_screen()方法中调用alien的blitme()方法。
13.3 创建一群外星人
1、确定一行可容纳多少个外星人
两边留白,外星人之间留空,用屏幕宽度减去两边留白再除以两倍外星人宽度
2、创建多行外星人
由于不在alien_invasion.py文件中直接创建外星人,因此无需再这个文件中导入Alien类。同样类比Bullet类,并未在主程序中导入,而是在game_functions.py中导入。
创建一个aliens的空编组,用于存储所有外星人
在game_function中的update_screen()中修改aliens.draw(screen),对编组用draw()方法时,Pygame自动绘制每个元素。
3、创建外星人群
在game_functions.py中创建creat_fleet()函数。文件中导入Alien类。
在creat_fleet()函数中一共创建了两次alien实例(alien=Alien(screen,settings))。第一次在for循环外面,用于获取rect属性计算一行空间中放置几个外星人。这次的实例创建不属于真实的外星人,不会编入aliens编组中。第二次创建alien实例在for循环内,即for循环遍历时,每次创建一个alien实例,并设置位置,再编入编组中。
aliens = Group(),是创建一个编组group的aliens的实例。同一文件中还有bullets的编组实例。注意Group()怎么用,是一个类的实例化。
4、重构create_fleet()
创建get_number_aliens_x()函数和create_alien()函数。将create_fleet()函数中的相关代码转移过去。
5、添加行
计算可以放置外星人的垂直空间,屏幕高度减去一个留白空间,一个外星人空间,一个间隔空间,一个飞船空间。
空间除以两倍外星人空间,为能够设置的行数。
为创建多行,我们使用两个嵌套循环,一个外部循环和一个内部循环,其中的内部循环创建一行外星人,而外部循环从0数到要创建的外星人行数。
注意:gf.create_fleet()函数一定要放在主程序while循环的外面,不然会重复的生成元素侵占内存。导致游戏无法正常运行。
13.4 让外星人群移动
1、向右移动外星人
在alien模块中创建update()方法。设置向右移动。移动速度因子在settings模块中进行确定。
在while循环中更新每个外星人的位置。先更新子弹,再更新外星人位置,后续要检查是否打掉外星人。
2、创建表示外星人移动方向的设置
在settings模块中创建self.fleet_drop_speed和self.fleet_direction。第一个设定向下移动的速度,第二个为移动方向,值为-1或者1,1代表右,-1代表左,很多情况会有这种操作。
3、检查外星人是否撞到了屏幕边缘
在alien模块中创建check_edge的方法,如果超出最右或超出最左,就返回True。
在alien模块中的update()方法中,位置移动乘以fleet_direction。这样如果调整方向直接从1变成-1,或者从-1变成1.
4、向下移动外星人群并改变移动方向
在game_function模块中进行修改。为什么在这里修改,不在alien模块中修改,为什么选择这个板块,搞不太懂。
编写check_fleet_edges()和change_fleet_direcion()方法。
change_fleet_direction方法中遍历整个外星人编组中的外星人向下移动,并改变方向。调用进check_fleet_edges()方法中。
check_fleet_edges()方法中遍历外星人编组中每个外星人,有存在符合check_edges()方法为True的,即运行change_fleet_direction。并且break跳出循环,后续的元素不再遍历,即只要编组中有一个被检查到碰到边缘,即整体舰队下移改变方向,后续是否还有其他触及边缘的不做检查。
13.5 射杀外星人
射杀外星人需要检测碰撞,即游戏元素重叠。将使用sprite.groupcollide()检测两个成员的碰撞。
1、检测子弹与外星人的碰撞
为实时知道是否发生碰撞,所以在子弹更新(bullet update)之后马上进行碰撞检测。
在gf模块中update_bullets()中新建collisions = pygame.sprite.groupcollide(bullets,aliens,True,True),实参解释如下:
groupcollide()遍历所有的bullets编组和aliens编组,即第一,第二实参。如果无碰撞,返回空字典,如果有碰撞,以第一实参的元素为键,第二实参的元素为值的键值对返回字典。第三个布尔值True为发生碰撞后删除相关键元素,第四个布尔值True为发生碰撞后删除相关值元素。
2、为测试创建大子弹
没什么内容,为测试不同的情况,修改一些参数,如修改子弹的大小,速度等测试外星人为空时的状态。
3、生成新的外星人群
在bullets_update里继续更新,如aliens为空,则bullets.empty()剩余的子弹,并且创建新的fleet。(但是感觉create_fleet()在bullets的函数里调用有点乱。)
4、提高子弹的速度
由于bullets_update()内容增加,pygame每次需要做很多工作,所以可能会影响子弹速度。(what??really??我感觉不应该啊。)
5、重构bullets_update()
简单重构,将检测bullets和aliens相撞的代码重构为一个函数。在bullets_update()中调用。
13.6 结束游戏
1、检测外星人和飞船碰撞
方法spritecollideany()接受两个实参:一个精灵和一个编组。它检查编组是否有成员与精灵发生了碰撞。并在找到与精灵发生碰撞的成员后就停止遍历编组。这里它遍历编组aliens,并返回它找到的第一个与飞船发生了碰撞的外星人,如果没有发生碰撞将返回一个none。 ---- 这个逻辑要清楚,这个方法的运行方式。
2、响应外星人和飞船碰撞
创建Gamestats类,保存在game_stats.py中。注意,这个类中的创建属性和方法。因为我们只创建一次Gamestats实例,但是每次游戏都要重置一下统计信息,所以重置信息放在reset_stats()方法中,而不在初始化中直接初始化。所以__init__()中的形参只有settings。而reset_stats()方法,因为在初始化中self.句点表达调用,所以它的方法下面的属性也用slef.句点生成,注意,没有在初始化方法中设置,而是在下面的方法中设置而且采用self.句点表达。
通过上述的表达方式,对于reset的设置,都可以在settings中进行配置,而只需要在GameStats类中实例化一次就可以。
在game_function中创建ship_hit的响应。ship_left减1,清空aliens,bullets,重新创建fleet,ship调整至底部中央,sleep一会儿。完成后在屏幕中刷新。
在ship模块中创建ship_center的方法,但是为什么这里要创建一个方法,不能直接做一个属性在ship_hit中调用呢?在整个游戏中,我们只创建了一个ship,并没有重新创建。
3、有外星人到达屏幕底端
注意break的位置。另外在ship_hit()中,ship.center_ship()中间有问题,为什么不能用self.rect.centerx,一定要用float之后的self.centerx。
4、游戏结束
设置一个游戏不运行的布尔值,当飞船小于1的时候,体现为false。后续用于游戏结束显示
13.7 确定应运行游戏的哪些部分
在主循环中任何时候都需要check_events,任何时候都需要刷新屏幕。这些是不以游戏是否运行为影响的,其他游戏内容可以用if条件进行选择运行。