前言
我们上篇文章初步给大家介绍了外星人的需求分析,以及创建了第一个外星人,包括创建Alien类以及Alien实例,最后将我们创建的外星人出现在屏幕上。本文给大家接着介绍多个外星人的实现。
创建一群外星人
要绘制一群外星人,需要确定一行能容纳多少个外星人以及要绘制多少行外星人。我们将先计算外星人之间的水平距离,并创建一行外星人,并创建整群外星人。
1、确定一行可容纳外星人的数量
为确定一行可容纳外星人的个数,我们需要看看可用的水平空间有多大。屏幕宽度存储在ai_settings.screen_width
中,但需要在屏幕两边都留下一定的边距,把它设置为外星人的宽度。由于有两个边距,因此可用于放置外星人的水平空间为屏幕宽度减去外星人宽度的两倍,具体计算如下:
avaliable_space_x = ai_settings.screen_width - (2 * alien_width)
我们还需要在外星人之间流出一定的空间,即外星人宽度。因此,显示一个外星人所需的水平空间为外星人宽度的两倍;一个宽度为用于放置外星人,另一个宽度为外星人右边的空白区域。为确定一行可容纳多少个外星人,我们将可用空间除以外星人宽度的两倍:
numner_aliens_x = avaliable_space_x / (2 * alien_width)
我们将在创建外星人群时使用这些公式。这里需要我们注意的是:其实我们在程序开始执行的时候无需确定公式的正确性,而可以尝试直接运行程序,看看结果是否符合预期。即便是在最糟糕的情况下,也只是屏幕上显示的外星人太多或太少。我们可以根据在屏幕上看到的情况对公式进行相应的调整。
2、创建多行外星人
为创建一行外星人,首先在alien_invasion.py
中创建一个名为alines
的空编组,用于存储全部的外星人,再调用game_funcattion.py
中创建外星人群的函数:
import sys
import pygame
from settings import Settings
from ship import Ship
import game_functions as gf
from pygame.sprite import Group
from alien import Alien
def run_game():
# 初始化游戏并创建一个屏幕对象
pygame.init()
ai_settings = Settings()
screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height))
pygame.display.set_caption("Alian Invasion")
# 创建一艘飞船、一个子弹编组和一个外星人编组
ship = Ship(ai_settings, screen)
# 创建一组一个用于存储子弹的编组
bullets = Group()
aliens = Group()
# 创建外q星人群
# alien = Alien(ai_settings, screen)
gf.create_fleet(ai_settings, screen, aliens)
# 开始游戏的主循环
while True:
# 监视键盘和鼠标事件
gf.check_events(ai_settings, screen, ship, bullets)
ship.update()
gf.update_bullets(bullets)
gf.update_screen(ai_settings, screen, ship, aliens, bullets)
run_game()
由于我们不再在alien_invasion.py
中直接创建外星人,因此无需在这个文件中导入Alien
类。
首先我们创建了一个空编组,用于存储所有的外星人,接下里,调用稍后将编写的函数create_fleet()
,并将ai_settings
、对象screen
和空编组aliens
传递给它。然后,修改对update_screen()
的调用,让它能够访问外星人编组。
当然我们还需要修改ganme_function.py中的update_screen()
,并且我们还需要实现新函数create_fleet(),这个时候,我们就需要导入Alien类
:
import sys
import pygame
from bullet import Bullet
from alien import Alien
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:
# 创建一颗子弹,并将其加入到编组bullets中
fire_bullet(ai_settings, screen, ship, bullets)
elif event.key == pygame.K_q:
sys.exit()
def fire_bullet(ai_settings, screen, ship, bullets):
"""如果还没有到达限制,就发射一颗子弹"""
if len(bullets) < ai_settings.bullets_allowed:
new_bullet = Bullet(ai_settings, screen, ship)
bullets.add(new_bullet)
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_events(ai_settings, screen, ship, 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)
def update_screen(ai_settings, screen, ship, aliens, bullets):
"""更新屏幕上的图像,并切换到新屏幕"""
# 每次循环时都重绘屏幕
screen.fill(ai_settings.bg_color)
# 在飞船和外星人后面重绘所有子弹
for bullet in bullets.sprites():
bullet.draw_bullet()
ship.blitme()
aliens.draw(screen)
# 让最近绘制的屏幕可见
pygame.display.flip()
def update_bullets(bullets):
"""更新子弹的位置,并删除已消失的子弹"""
# 更新子弹的位置
bullets.update()
# 删除已消失的子弹
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
def create_fleet(ai_settings, screen, aliens):
"""创建外星人群"""
# 创建一个外星人,并计算一行可容纳多少个外星人
# 外星人间距为外星人宽度
alien = Alien(ai_settings, screen)
alien_width = alien.rect.width
avaliable_space_x = ai_settings.screen_width - 2 * alien_width
number_aliens_x = int(avaliable_space_x / (2 * alien_width))
# 创建第一行外星人
for alien_number in range(number_aliens_x):
# 创建一个外星人并将其加入当前行
alien = Alien(ai_settings, screen)
alien.x = alien_width + 2 * alien_width * alien_number
alien.rect.x = alien.x
aliens.add(alien)
这些代码在前面其实我们都给大家详细介绍过。我们对编组调用draw()
时,pygame自动绘制了编组的每个元素,绘制位置由元素的属性rect决定。在这里,aliens.draw(screen)
在屏幕上绘制编组中的每个外星人。
我们为了放置外星人,需要知道外星人的宽度和高度。因此在执行计算前,我们先创建一个外星人。这个外星人不是外星人群的成员,因此,没有将它加入到编组aliens
中。另外,我们从外星人的rect属性中获取外星人的宽度,并将这个值存储到alien_width
中,以免反复访问属性rect。最后,我们计算可用于放置外星人的水平空间,以及其中可容纳多少个外星人。
相比于前面介绍的工作,这里唯一的不同是使用了int
来确保计算得到的外星人数量为整数,因为我们不希望某个外星人只显示一部分,而且函数range()
也需要一个整数。函数int()将小数部分丢弃,相当于向下取整,其实这样做最大的好处就是可以流出一些空间,而不是挤满了外星人
。看起来很少不好看。
最后,我们编写一个循环,它从零数到要创建的外星人数。在这个循环的主题中,我们创建一个新的外星人,并通过设置x坐标将其加入当前行。将每个外星人都往右推出一个外星人的宽度。接下来,我们将外星人宽度乘以2,得到每个外星人占据的空间,这里当然也包含其空白的区域,再据此计算当前外星人在当前的位置。最后,我们将每个新创建的外星人添加到编组aliens
中。
我们将目前代码运行,其效果如下:
这一行外星人在屏幕上稍微偏向了左边,这其实是比较好的,因为我们将让外星人群往右移,触及屏幕边缘后稍微往下移,然后往左移,以此类推。我们将让外星人群不断这样移动,直到所有外星人都被击落或有外星人撞上飞船或抵达屏幕底端。这里还需要我们特别注意的是:根据读者自己选择的屏幕宽度以及在你的系统中,第一行外星人的位置以及外星人个数可能不一样。
3、重构create_fleet()
倘若我们创建了外星人群,也许应让create_fleet()
保持原样,但鉴于创建外星人的工作还未完成。我们稍微整理一下这个函数,下面是create_fleet()
和两个新函数:get_number_aliens_x()
和create_alien()
:
import sys
import pygame
from bullet import Bullet
from alien import Alien
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:
# 创建一颗子弹,并将其加入到编组bullets中
fire_bullet(ai_settings, screen, ship, bullets)
elif event.key == pygame.K_q:
sys.exit()
def fire_bullet(ai_settings, screen, ship, bullets):
"""如果还没有到达限制,就发射一颗子弹"""
if len(bullets) < ai_settings.bullets_allowed:
new_bullet = Bullet(ai_settings, screen, ship)
bullets.add(new_bullet)
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_events(ai_settings, screen, ship, 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)
def update_screen(ai_settings, screen, ship, aliens, bullets):
"""更新屏幕上的图像,并切换到新屏幕"""
# 每次循环时都重绘屏幕
screen.fill(ai_settings.bg_color)
# 在飞船和外星人后面重绘所有子弹
for bullet in bullets.sprites():
bullet.draw_bullet()
ship.blitme()
aliens.draw(screen)
# 让最近绘制的屏幕可见
pygame.display.flip()
def update_bullets(bullets):
"""更新子弹的位置,并删除已消失的子弹"""
# 更新子弹的位置
bullets.update()
# 删除已消失的子弹
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
def get_number_aliens_x(ai_settings, alien_width):
avaliable_space_x = ai_settings.screen_width - 2 * alien_width
number_aliens_x = int(avaliable_space_x / (2 * alien_width))
return number_aliens_x
def create_alien(ai_settings, screen, aliens, alien_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
aliens.add(alien)
def create_fleet(ai_settings, screen, aliens):
"""创建外星人群"""
alien = Alien(ai_settings, screen)
number_aliens_x = get_number_aliens_x(ai_settings, alien.rect.width)
# 创建第一行外星人
for alien_number in range(number_aliens_x):
# 创建一个外星人并将其加入当前行
create_alien(ai_settings, screen, aliens, alien_number)
函数get_number_aliens_x()
的代码都来自create_fleet()
,且未做任何修改。函数create_alien()
的代码也都是来自create_fleet()
,且未做任何修改,只是使用刚创建的外星人来获取外星人的宽度。我们将计算可用水平空间的代码替换为对get_number_aliens_x()
的调用,并删除了引用alien_width
的代码行,因为现在这是在create_alien()
中处理的。最后,我们调用create_alien()。通过这样的重构,将添加新行进而创建整群外星人就更容易了。
4、添加行
要创建外星人群,需要设计屏幕可容纳多少行,并对创建一行外星人的循环重复相应的次数。为计算可容纳的行数,我们这样计算可用垂直空间:将屏幕高度减去第一行外星人的上边距(外星人的高度)、飞船的高度以及最初外星人高度加上外星人间距(外星人高度的两倍):
avaliable_space_y = ai_settings.screen_height - 3 * alien_height - ship_height
这将在飞船上方留出一定空白的区域,给玩家留出射杀外星人的时间。
每行下方都要留出一定的空白区域,并将其设置为外星人的高度。为计算可容纳的行数,我们将可用垂直空间除以外星人高度的两倍(同样,如果这样计算不对,我们马上就能发现,继而将间距调整为合理的值)。
number_rows = available_space_y / (2 * alien_height)
知道可容纳的行数后,便可重复执行创建一行外星人的代码:
import sys
import pygame
from bullet import Bullet
from alien import Alien
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:
# 创建一颗子弹,并将其加入到编组bullets中
fire_bullet(ai_settings, screen, ship, bullets)
elif event.key == pygame.K_q:
sys.exit()
def fire_bullet(ai_settings, screen, ship, bullets):
"""如果还没有到达限制,就发射一颗子弹"""
if len(bullets) < ai_settings.bullets_allowed:
new_bullet = Bullet(ai_settings, screen, ship)
bullets.add(new_bullet)
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_events(ai_settings, screen, ship, 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)
def update_screen(ai_settings, screen, ship, aliens, bullets):
"""更新屏幕上的图像,并切换到新屏幕"""
# 每次循环时都重绘屏幕
screen.fill(ai_settings.bg_color)
# 在飞船和外星人后面重绘所有子弹
for bullet in bullets.sprites():
bullet.draw_bullet()
ship.blitme()
aliens.draw(screen)
# 让最近绘制的屏幕可见
pygame.display.flip()
def update_bullets(bullets):
"""更新子弹的位置,并删除已消失的子弹"""
# 更新子弹的位置
bullets.update()
# 删除已消失的子弹
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
def get_number_aliens_x(ai_settings, alien_width):
avaliable_space_x = ai_settings.screen_width - 2 * alien_width
number_aliens_x = int(avaliable_space_x / (2 * alien_width))
return number_aliens_x
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, ship, aliens):
"""创建外星人群"""
alien = Alien(ai_settings, screen)
number_aliens_x = get_number_aliens_x(ai_settings, alien.rect.width)
number_rows = get_number_rows(ai_settings, ship.rect.height, 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 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
为计算屏幕可容纳多少行外星人,我们在函数get_number_rows()
实现了前面计算available_space_y
和number_rows
的公式,这个函数与get_number_aliens_x()
类似,计算公式要用括号括起来,这样可将代码分成两行,以遵循每行不超过79字符的建议。这里使用了int(),因为我们不想创建不完整的外星人行。
为创建多行,我们使用两个嵌套在一起的循环:一个外部循环一个内部循环。其中的内部循环创建一行外星人,而外部循环从零数到要创建的外星人行数。Python将重复执行创建单行外星人代码,重复次数为number_rows。
为嵌套循环,我们编写了一个新的for循环,并缩进了要重复执行的代码。另外又调用create_alien()
时,传递了一个表示行号的实参,将每行都沿屏幕依次向下放置。
create_alien()
的定义需要一个用于存储行号的实参。在create_alien()
中,我们修改外星人的y坐标,并在第一行外星人上方留出与外星人等高的空白区域。相邻外星人的y坐标差外星人高度的两倍,因此,我们将外星人高度乘以2,再乘以行号。第一行的行号为0,因此,第一行的垂直位置不变,而其他行都沿屏幕依次向下放置。
在create_fleet()
的定义中,还新增了一个用于存储ship
对象的形参,因此,在alien_invasion.py
中调用create_fleet()
时,需要传递实参ship:
import sys
import pygame
from settings import Settings
from ship import Ship
import game_functions as gf
from pygame.sprite import Group
from alien import Alien
def run_game():
# 初始化游戏并创建一个屏幕对象
pygame.init()
ai_settings = Settings()
screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height))
pygame.display.set_caption("Alian Invasion")
# 创建一艘飞船、一个子弹编组和一个外星人编组
ship = Ship(ai_settings, screen)
# 创建一组一个用于存储子弹的编组
bullets = Group()
aliens = Group()
# 创建外星人群
# alien = Alien(ai_settings, screen)
gf.create_fleet(ai_settings, screen, ship, aliens)
# 开始游戏的主循环
while True:
# 监视键盘和鼠标事件
gf.check_events(ai_settings, screen, ship, bullets)
ship.update()
gf.update_bullets(bullets)
gf.update_screen(ai_settings, screen, ship, aliens, bullets)
run_game()
具体运行的效果如下:
总结
我们上篇文章初步给大家介绍了外星人的需求分析,以及创建了第一个外星人,包括创建Alien类以及Alien实例,最后将我们创建的外星人出现在屏幕上。本文给大家介绍多个外星人的实现。包括确定一行可容纳外星人的数量、创建多行外星人以及代码的重构和添加多行外星人。为了让大家更好的吸收项目所用到的知识点,我们每一篇文章只给大家实现《外星人入侵》的一个功能,所以,希望大家能够仔细阅读,认真跟着写代码,理解其中的深入含义,吧这个项目的价值发挥到最大。其实这个项目已经很典型,代码到处都是,但是,如果你只是简单的粘贴复制,对你知识的学习没有任何的价值,你还是得跟着过一遍,然后要知道每行代码的含义或者是用到了前面我们介绍的哪一块知识点,只有这样,这个项目才会发挥不一样的价值,希望大家认真学习,把基础知识打扎实一点。Python是一门注重实际操作的语言,它是众多编程语言中最简单,也是最好入门的。当你把这门语言学会了,再去学习java、go以及C语言就比较简单了。当然,Python也是一门热门语言,对于人工智能的实现有着很大的帮助,因此,值得大家花时间去学习。生命不息,奋斗不止,我们每天努力,好好学习,不断提高自己的能力,相信自己一定会学有所获。加油!!!临近中秋,提前祝大家中秋快乐!阖家欢乐!!!