目录
- 安装Pygame
- 创建Pygame窗口以及响应用户输入
- 设置背景色
- 创建设置类
- 添加飞船图像
- 创建ship类
- 重构:模块game_functions
- 函数check_events()
- 函数update_screen()
- 驾驶飞船
- 响应按键
- 允许不断移动
- 左右移动
- 调整飞船的速度
- 限制飞船的活动范围
- 重构check_events()
- 射击
- 添加子弹设置
- 创建bullet类
- 将子弹存储到编组中
- 开火
- 删除已经消失的子弹
- 限制子弹的数量
- 创建函数update_bullets()
- 创建函数fire_bullet()
- 外星人
- 创建Alien类
- 创建Alien实例
- 让外星人出现在屏幕上
- 创建一群外星人
- 确定一行可容纳多少个外星人
- 创建多行外星人
- 重构create_fleet()
- 添加行
- 让外星人群移动
- 向右移动的外星人
- 创建表示外星人移动方向的设置
- 检查外星人是否撞到屏幕边缘
- 向下移动外星人群并改变移动方向
- 射杀外星人
- 检测外星人与子弹的碰撞
- 生成新的外星人群
- 重构update_bullets()
- 结束游戏
- 检测外星人和飞船碰撞
- 响应外星人和飞船碰撞
- 有外星人到达屏幕底部
- 游戏结束
- 确定应运行游戏的那些部分
- 后记
安装Pygame
终端输入:
pip install Pygame
#开始游戏项目
创建Pygame窗口以及响应用户输入
首先:创建一个空的Pygame窗口。
import sys
import pygame
def run_game():
pygame.init()
screen = pygame.display.set_mode((1200, 800))
pygame.display.set_caption("Alien Invasion")
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
pygame.display.flip()
run_game()
理解:
- pygame.init()初始化背景设置,让Pygame能够正确地工作。
- Pygame.display.set_mode()来创建一个名为screen的显示窗口。每经过一次循环都将自动重绘这个surface(游戏中的元素,外星人,飞船等。)
- while循环包含一个事件循环以及管理屏幕更新的代码。事件是用户玩游戏时的操作,如按键或移动鼠标。为让程序响应事件,我们编写一个事件循环,用于监听事件,并根据发生的事件执行相应的任务。
- Pygame.QUIT,用于检测当用户单击关闭按钮时,调用sys.exit()来退出程序。
- pygame.display.flip(),命令pygame让最近绘制的屏幕可见。在这里,它在每次执行while循环时都绘制一个空屏幕,并擦去旧屏幕,使只有新屏幕可见。在我们移动游戏元素时,pygame.display.flip()将不断更新屏幕,以显示元素的新位置,并在原来的位置隐藏元素,从而营造平滑移动的效果。
设置背景色
alien_invasion.py
import sys
import pygame
def run_game():
pygame.init()
screen = pygame.display.set_mode((1200, 800))
pygame.display.set_caption("Alien Invasion")
bg_color = (230,230,230)
# 开始游戏主循环
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
screen.fill(bg_color)
# 让最近绘制的屏幕可见
pygame.display.flip()
run_game()
添加了一个screen.fill(bg_color),用于填充颜色
创建设置类
每次给游戏添加新功能时,通常也将引入一些新设置。下面编写一个名为settings的模块,其中包含一个名为settings的类,用于将所有设置存储在一个地方,以免在代码中到处添加设置。
这样我们就能传递一个设置对象,而不是众多不同的设置。
另外,这让函数调用更简单,且在项目增大时修改游戏的外观更容易:要修改游戏,只需要修改settings.py中的一些值。
settings.py
class Settings():
def __init__(self):
self.screen_width = 1200
self.screen_height = 800
self.bg_color = (230, 230, 230)
修改原来的alien_invasion.py文件为:
alien_invasion.py
import sys
import pygame
from settings import Settings
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("Alien Invasion")
# 开始游戏主循环
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
screen.fill(ai_settings.bg_color)
# 让最近绘制的屏幕可见
pygame.display.flip()
run_game()
添加飞船图像
在网站http://pixabay.com/中下载飞船图像,使用.bmp结尾的图像,(这样的图片不用下载图像库便可以使用)。
大多图片为.jpg,.png,.gif格式。但可以使用Photoshop,GIMP和paint等工具将其转换为位图。
在项目的文件夹下创建一个images的文件夹,并将图片ship.bmp放进去。
创建ship类
选择用于表示飞船的图像后,需要将其显示到屏幕上。我们将创建一个名为ship的模块,其中包含ship类,它负责管理飞船的大部分行为。
ship.py
import pygame
class Ship():
def __init__(self, screen):
self.screen = screen
self.image = pygame.image.load('images/ship.png')
self.rect = self.image.get_rect()
self.screen_rect = screen.get_rect()
self.rect.centerx = self.screen_rect.centerx
self.rect.bottom = self.screen_rect.bottom
def blitme(self):
self.screen.blit(self.image, self.rect)
重构:模块game_functions
在大型项目中,经常需要在添加新代码前重构既有代码。重构旨在简化既有代码的结构,使其更容易扩展。
在本节中,我们将创建一个名为game_functions的新模块,它将存储大量让游戏运行的函数。通过创建模块game_functions,可避免alien_invasion.py太长,使其逻辑混乱。
函数check_events()
我们首先把管理事件的代码移到一个名为check_events()的函数中,以简化run_game()并隔离事件管理循环。通过隔离事件循环,可将事件管理与游戏的其他方面(如更新屏幕)分离。
将check_events()放在一个名为game_functions的模块中。
import sys
import pygame
def check_events():
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
更改alien_invasions.py为:
import sys
import pygame
from settings import Settings
from ship import Ship
import game_functions as gf
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("Alien Invasion")
pygame.display.set_caption('Alien Invasion')
ship = Ship(screen)
# 开始游戏主循环
while True:
gf.check_events()
screen.fill(ai_settings.bg_color)
ship.blitme()
# 让最近绘制的屏幕可见
pygame.display.flip()
run_game()
把循环部分更改了即可。
函数update_screen()
为了进一步简化run_game(),下面将更新屏幕的代码移到一个名为update_screen()的函数中,并将这个函数放在模块game_functions.py中:
def update_screen(ai_settings, screen, ship):
screen.fill(ai_settings.bg_color)
ship.blitme()
pygame.display.flip()
再将alien_invasion.py中的部分替换即可:
while True:
gf.check_events()
gf.update_screen(ai_settings, screen, ship)
驾驶飞船
下面让玩家可以左右移动飞船。为此,我们将编写代码,在用户按左或右箭头时做出响应。
我们先专注于向右移动。
响应按键
每当用户按键时,都将在pygame中注册一个事件。事件是通过方法pygame.event.get()获取的,因此在函数check_events()中,我们需要指定要检查那些类型的事件。
每次按键都被注册为一个KEYDOWN事件。
检测到KEYDOWN事件时,我们需要检查按下的键是否为特定的键。例如,如果按下的时右箭头,就增大飞船的rect.centrx值。
在game_functions.py中添加以下函数:
def check_events(ship):
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN: # ①
if event.key == pygame.K_PIGHT: # ②
ship.rect.centerx += 1
解释:①处用于在pygame检测到KEYDOWN事件时作出响应。
②处在检测到按下为右箭头时将飞船向右移动。
修改对应的alien_invasion.py模块:传参ship
while True:
gf.check_events(ship)
gf.update_screen(ai_settings, screen, ship)
允许不断移动
玩家按住右箭头不放时,我们希望飞船不断地向右移动,直到玩家松开为止。
思路:
- 检测pygame.KEYUP事件,以便玩家松开右箭头时我们能够知道这一点;然后,我们将结合使用KEYDOWN和KEYUP事件,以及一个名为moving_right的标志来实现持续移动。
- 飞船不动时,标志moving_right将为False。玩家按下右箭头时,我们将这个标志设为True,而玩家松开时,我们将这个标志重新设置为Falsh
飞船的属性由ship类控制,因此我们给这个类添加一个名为moving_right的属性和一个名为update()的方法。方法update()检查标志moving_right的状态,如果这个标志为True,就调整飞船的位置。
import pygame
class Ship():
--snip--
def __init__(self, screen):
self.moving_right = False
def update(self):
if self.moving_right:
self.rect.centerx += 1
接下来更改game_functions.py中的check_events()函数,让玩家按下右箭头时将moving_right设置为True,并在玩家松开时将moving_right设置为false:
def check_events(ship):
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
ship.moving_right = True
elif event.type == pygame.KEYUP:
if event.key == pygame.K_RIGHT:
ship.moving_right = False
最后,在alien_invasion.py中的while循环,以便每次执行循环时都要调用飞船的方法update()。
while True:
gf.check_events(ship)
ship.update()
gf.update_screen(ai_settings, screen, ship)
左右移动
飞船能够不断地向右移动后,添加向左移动地逻辑很容易。我们将再次修改ship类和函数check_events().
ship.py
import pygame
class Ship():
def __init__(self, screen):
--snip--
self.moving_right = False
self.moving_left = False
def update(self):
if self.moving_right:
self.rect.centerx += 1
elif self.moving_left:
self.rect.centerx -= 1
--snip--
game_functions.py # 中的check_events()修改。
def check_events(ship):
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
if event.key == pygame.K_RIGHT:
ship.moving_right = True
elif event.key == pygame.K_LEFT:
ship.moving_left = True
elif event.type == pygame.KEYUP:
if event.key == pygame.K_RIGHT:
ship.moving_right = False
if event.key == pygame.K_LEFT:
ship.moving_left = False
调整飞船的速度
接下来我们将进一步优化飞船的移动方式:调整飞船的速度;限制飞船的移动距离,以免它移到屏幕外面去。
每次执行while循环时,飞船最多移动1像素,但我们可以在Settings类中添加属性ship_speed_factor,用于控制飞船的速度。我们将根据这个属性决定飞船在每次循环时最多移动多少距离。
class Settings():
def __init__(self):
--snip--
self.ship_speed_factor = 1.5
通过将速度设置为小数值,可在后面加快游戏的节奏时更细致地控制飞船的速度。然而,rect的centerx等属性只能存储整数值,因此我们需要对ship类作修改:
import pygame
class Ship():
def __init__(self, ai_settings, screen):
--snip--
self.center = float(self.rect.centerx)
self.moving_right = False
self.moving_left = False
def update(self):
if self.moving_right:
self.center += self.ai_settings.ship_speed_factor
elif self.moving_left:
self.center -= self.ai_settings.ship_speed_factor
self.rect.centerx = self.center
def blitme(self):
--snip--
最后更改alien_invasion.py
def run_game():
--snip--
ship = Ship(ai_settings, screen)
--snip--
限制飞船的活动范围
当玩家按住箭头的时间足够长,飞船将移动到屏幕外面,消失得无影无踪。下面来修复这种问题。为此,我们将修改ship类的方法update():
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("Alien Invasion")
ship = Ship(ai_settings, screen)
重构check_events()
随着游戏的开发,函数check_events()将越来越长,我们将其部分代码放在两个函数中:一个处理KEYDOWN事件,一个处理KEYUP事件。
import sys
import pygame
def check_keydown_events(event, ship):
if event.key == pygame.K_RIGHT:
ship.moving_right = True
elif event.key == pygame.K_LEFT:
ship.moving_left = True
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(ship):
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
check_keydown_events(event, ship)
elif event.type == pygame.KEYUP:
check_keyup_events(event, ship)
def update_screen(ai_settings, screen, ship):
screen.fill(ai_settings.bg_color)
ship.blitme()
pygame.display.flip()
射击
下面来添加射击设置。我们将编写玩家按空格键时发射子弹(小矩形)的代码。子弹将在屏幕中向上穿行,抵达屏幕边缘后消失。
添加子弹设置
class Settings():
def __init__(self):
--snip--
# 子弹设置
self.bullet_speed_factor = 1
self.bullet_width = 3
self.bullet_height = 15
self.bullet_color = 60, 60, 60
创建bullet类
创建存储Bullet类的文件bullet.py。
import pygame
from pygame.sprite import Sprite
class Bullet(Sprite):
def __init__(self, ai_settings, screen, ship):
super(Bullet, self).__init__()
self.screen = screen
self.rect = pygame.Rect(0, 0, ai_settings.bullet_width, ai_settings.bullet_height)
self.rect.centerx = ship.rect.centerx
self.rect.top = ship.rect.top
self.y = float(self.rect.y)
self.color = ai_settings.bullet_color
self.speed_factor = ai_settings.bullet_speed_factor
def update(self):
self.y -= self.speed_factor
self.rect.y = self.y
def draw_bullet(self):
pygame.draw.rect(self.screen, self.color, self.rect)
将子弹存储到编组中
定义Bullet类和必要的设置后,就可以编写代码了,在玩家每次按空格键时都发射出一发子弹。首先,我们将在alien_invasion.py中创建一个编组(group),用于存储所有有效的子弹,以便能够管理发射出去的子弹。
这个编组将是pygame.sprite.Group类的一个实例;pygame.sprite.Group类,类似于列表,但提供了有助于开发游戏的额外功能。我们将在主循环中,使用这个编组在屏幕上绘制子弹,以及更新子弹的位置。
import sys
import pygame
from settings import Settings
from ship import Ship
import game_functions as gf
from pygame.sprite import Group
def run_game():
--snip--
bullets = Group()
# 开始游戏主循环
while True:
gf.check_events(ship)
ship.update()
bullets.update()
gf.update_screen(ai_settings, screen, ship)
run_game()
开火
在game_functions.py中,我们需要修改check_keydown_events(),以便在玩家按空格键时发射一颗子弹。我们无需修改check_keyup_events(),因为玩家松开空格键时什么都不会发生。我们还需要修改update_screen(),确保在调用flip()前在屏幕上重新绘制每颗子弹:
import sys
import pygame
from bullet import Bullet
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:
new_bullet = Bullet(ai_settings, screen, ship)
bullets.add(new_bullet)
--snip--
def check_events(ai_settings, screen, ship, bullets):
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
--snip--
elif event.type == pygame.KEYDOWN:
check_keydown_events(event, ai_settings, screen, ship, bullets)
def update_screen(ai_settings, screen, ship, bullets):
screen.fill(ai_settings.bg_color)
for bullet in bullets.sprites():
bullet.draw_bullet()
ship.blitme()
pygame.display.flip()
删除已经消失的子弹
当前,子弹抵达屏幕顶端后消失,这仅仅是因为Pygame无法在屏幕外面绘制它们。这些子弹实际上依旧存在。这是个问题,因为它们会继续消耗内存。
为此,我们要检测这样的条件,即表示子弹的rect的bottom属性为零,它表明子弹已穿过屏幕顶端。
# alien_invasion.py
while True:
gf.check_events(ai_settings, screen, ship, bullets)
ship.update()
bullets.update()
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
print(len(bullets))
gf.update_screen(ai_settings, screen, ship, bullets)
在for循环中,不应从列表或编组中删除条目,因此必须遍历编组中的副本。我们使用了方法copy()来设置for循环,这让我们能够在循环中修改bullets。
限制子弹的数量
我们可以对可同时出现在屏幕上的子弹数量进行限制,以鼓励玩家有目的的射击。
首先,在settings.py中存储允许的最大子弹数量。
class Settings():
def __init__(self):
--snip--
# 允许的子弹数量
self.bullets_allowed = 3
在创建新子弹前检查未消失得子弹数量是否小于该设置。
# game_functions.py
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:
if len(bullets) < ai_settings.bullets_allowed: # 判断是否满足子弹数量的限制
new_bullet = Bullet(ai_settings, screen, ship)
bullets.add(new_bullet)
创建函数update_bullets()
编写并检查子弹管理代码后,可将其移到模块game_functions中,让主程序文件alien_invasion.py尽可能的简单。
创建一个名为update_bullets()的函数:
# game_functions.py 底部增加
def update_bullets(bullets):
bullets.update()
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullets)
更改alien_invasion.py中的while循环:
# game_invasion.py
while True:
gf.check_events(ai_settings, screen, ship, bullets)
ship.update()
bullets.update()
gf.update_bullets(bullets)
gf.update_screen(ai_settings, screen, ship, bullets)
创建函数fire_bullet()
将发射子弹的代码移到一个独立的函数中,这样,在check_keydown_events()中只需要使用一行代码来发射子弹,让elif代码块简单。
# game_functions.py
import sys
import pygame
from bullet import Bullet
def check_keydown_events(event, ai_settings, screen, ship, bullets):
--snip--
elif event.key == pygame.K_SPACE:
fire_bullet(ai_settings, screen, ship, bullets)
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)
外星人
接下来,我们将在游戏中添加外星人。首先,我们在屏幕边缘附近添加一个外星人,然后生成一群外星人向两边和下面移动,并删除被子弹击中的外星人。最后,我们将显示玩家拥有的飞船属性,并在玩家的飞船用完后结束游戏。
在给项目添加新功能前,还应审核既有代码。每进一个阶段,通常项目都会更复杂,因此最好度混乱或低效的代码进行审计。
由于每次测试时,都要点击关闭来关闭游戏,很麻烦,下面添加一个结束游戏的快捷键Q:
# game_functions.py
def check_keydown_events(event, ai_settings, screen, ship, bullets):
--snip--
elif event.key == pygame.K_q:
sys.exit()
创建Alien类
# alien.py
import pygame
from pygame.sprite import Sprite
class Alien(Sprite):
def __init__(self, ai_settings, screen):
super(Alien, self).__init__()
self.screen = screen
self.ai_settings = ai_settings
self.image = pygame.load('images/alien.png')
self.rect = self.image.get_rect()
self.rect.x = self.rect.width
self.rect.y = self.rect.height
self.x = float(self.rect.x)
def blitme(self):
self.screen.blit(self.image, self.rect)
创建Alien实例
在alien_invasion.py中创建一个Alien实例:
# alien_invasion.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("Alien Invasion")
ship = Ship(ai_settings, screen)
bullets = Group()
alien = Alien(ai_settings, screen)
让外星人出现在屏幕上
在update_screen()中调用方法blitme()。
# game_functions.py
def update_screen(ai_settings, screen, ship, alien, bullets):
screen.fill(ai_settings.bg_color)
for bullet in bullets.sprites():
bullet.draw_bullet()
ship.blitme()
alien.blitme() # 显示外星人
pygame.display.flip()
创建一群外星人
首先确认一行可以容纳多少个外星人。
确定一行可容纳多少个外星人
为确定一行可容纳多少外星人,我们可以看看可用的水平空间有多大。并在屏幕左右各留出一个外星人的距离;最后用剩下的距离除以一个外星人的距离。
公式:
available_space_x=ai_settings.screen_width-(2* alien_width)
number_aliens_x=available_space_x / (2* alien_width)
创建多行外星人
为创建一行外星人,首先在alien_invasion.py中创建一个名为aliens的空编组,用于存储全部外星人,再调用game_functions.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():
--snip--
bullets = Group()
aliens = Group()
# alien = Alien(ai_settings, screen)
gf.create_fleet(ai_settings, screen, aliens)
# 开始游戏主循环
--snip--
修改update_screen():
# game_functions.py
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) # 添加
# alien.blitme()
pygame.display.flip()
增加新函数:create_fleet(),放在game_functions.py
def create_fleet(ai_settings, screen, aliens):
alien = Alien(ai_settings, screen)
alien_width = alien.rect.width
available_space_x = ai_settings.screen_width - 2 * alien_width
number_aliens_x = int(available_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)
重构create_fleet()
下面是create_fleet()和两个新函数,get_number_aliens_x()和create_alien():
# game_function.py
def get_number_aliens_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 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)
添加行
# game_functions.py
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):
--snip--
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)
让外星人群移动
下面来让外星人移动,在屏幕上向右移动,撞到屏幕边缘后向下移动一定的距离,再沿反方向移动。
向右移动的外星人
为移动外星人,我们将使用方法alien.py中的方法update(),且对外星人群中的每个外星人都调用它。
首先在settings.py中添加控制外星人的速度
# settings.py
class Settings():
def __init__(self):
--snip--
# 外星人设置
self.alien_speed_factor = 1
alien.py中添加update()函数:
alien.py
def update(self):
self.x += self.ai_settings.alien_speed_factor
self.rect.x = self.x
更新while循环中的更新子弹和飞船的方法
alien_invasion.py
while True:
gf.check_events(ai_settings, screen, ship, bullets)
ship.update()
bullets.update()
gf.update_bullets(bullets)
gf.update_aliens(aliens)
gf.update_screen(ai_settings, screen, ship, aliens, bullets)
最后在game.functons.py末尾添加新函数:update_aliens()
game_functions.py
def update_aliens(aliens):
aliens.update()
创建表示外星人移动方向的设置
下面来创建让外星人撞到屏幕右边缘后向下移动,再向左移动的设置。
settings.py
class Settings():
def __init__(self):
--snip--
# 外星人设置
self.alien_speed_factor = 1
self.fleet_drop_speed = 10
self.fleet_direction = 1
检查外星人是否撞到屏幕边缘
现在需要编写一个方法来检查是否有外星人撞到屏幕边缘,还需要修改update(),以让每个外星人都沿正确的方法移动。
alien.py
def check_edges(self):
screen_rect = self.screen.get_rect()
if self.rect.right >= screen_rect.right:
return True
elif self.rect.left <= 0:
return True
def update(self):
self.x += (self.ai_settings.alien_speed_factor * self.ai_settings.fleet_direction)
self.rect.x = self.x
向下移动外星人群并改变移动方向
game_functions.py
def update_aliens(ai_settings, aliens):
check_fleet_edges(ai_settings, aliens)
aliens.update()
def check_fleet_edges(ai_settings, aliens):
for alien in aliens.sprites():
if alien.check_edges():
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
更改alien_invasion.py
alien_invasion.py
while True:
gf.check_events(ai_settings, screen, ship, bullets)
ship.update()
bullets.update()
gf.update_bullets(bullets)
gf.update_aliens(ai_settings, aliens)
gf.update_screen(ai_settings, screen, ship, aliens, bullets)
射杀外星人
我们创建了飞船和外星人群,但子弹射击中外星人时,将穿过外星人,因为我们还没有检查碰撞。我们将使用sprite.groupcollide()检测编组成员之间的碰撞。
检测外星人与子弹的碰撞
方法sprite.groupcollide()将每颗子弹的rect同每个外星人的rect进行比较,并返回一个字典,其中包含发生了碰撞的子弹和外星人。
在函数update_bullets()中,使用下面的代码来检测碰撞:
# game_functions.py
def update_bullets(aliens,bullets):
bullets.update()
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullets)
print(len(bullets))
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
update_bullets()的调用传递了实参,aliens:
alien_functions.py
while True:
gf.check_events(ai_settings, screen, ship, bullets)
ship.update()
bullets.update()
gf.update_bullets(aliens, bullets)
gf.update_aliens(ai_settings, aliens)
gf.update_screen(ai_settings, screen, ship, aliens, bullets)
生成新的外星人群
这个游戏的一个重要特点就是外星人无穷无尽,一个外星人被消灭后,又会出现一群新的外星人。
首先就要检查编组aliens是否为空。如果为空,就调用create_fleet()。
game_functions.py
def update_bullets(ai_settings, screen, ship, aliens, bullets):
--snip--
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
if len(aliens) == 0:
bullets.empty()
create_fleet(ai_settings, screen, ship, aliens)
更改alien_invasion.py
while True:
gf.check_events(ai_settings, screen, ship, bullets)
ship.update()
bullets.update()
gf.update_bullets(ai_settings, screen, ship, aliens, bullets)
gf.update_aliens(ai_settings, aliens)
gf.update_screen(ai_settings, screen, ship, aliens, bullets)
重构update_bullets()
game_functions.py
def check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets):
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
if len(aliens) == 0:
bullets.empty()
create_fleet(ai_settings, screen, ship, aliens)
def update_bullets(ai_settings, screen, ship, aliens, bullets):
bullets.update()
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullets)
print(len(bullets))
check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets)
结束游戏
检测外星人和飞船碰撞
game_functions.py
def update_aliens(ai_settings, ship, aliens):
check_fleet_edges(ai_settings, aliens)
aliens.update()
if pygame.sprite.spritecollideany(ship, aliens):
print("ship hit !!!")
将ship传给update_aliens():
# alien_invasion.py
while True:
gf.check_events(ai_settings, screen, ship, bullets)
ship.update()
bullets.update()
gf.update_bullets(ai_settings, screen, ship, aliens, bullets)
gf.update_aliens(ai_settings, ship, aliens) # 加入ship
gf.update_screen(ai_settings, screen, ship, aliens, bullets)
响应外星人和飞船碰撞
通过跟踪游戏的统计信息来记录飞船被撞了多少次。(有助于计分)
编写一个用于跟踪游戏统计信息的新类:GameStarts放在game_starts.py中
class GameStats():
def __init__(self, ai_settings):
self.ai_settings = ai_settings
self.reset_stats()
def reset_stats(self):
self.ships_left = self.ai_settings.ship_limit
飞船数存储在settings.py中:
class Settings():
def __init__(self):
--snip--
# 飞船设置
self.ship_speed_factor = 1.5
self.ship_limit = 3
在alien_invasion.py中创建一个GameStats实例:
--snip--
from alien import Alien
from game_stats import GameStats
def run_game():
--snip--
pygame.display.set_caption("Alien Invasion")
stats = GameStats(ai_settings)
--snip--
while True:
--snip--
gf.update_bullets(ai_settings, screen, ship, aliens, bullets)
gf.update_aliens(ai_settings, stats, screen, ship, aliens, bullets)
--snip--
run_game()
最后将实现这些功能的大部分代码放到函数ship.hit()中:
# game_functions.py
def ship_hit(ai_settings, stats, screen, ship, aliens, bullets):
stats.ships_left -= 1
aliens.empty()
bullets.empty()
create_fleet(ai_settings, screen, ship, aliens)
ship.center_ship()
sleep(0.5)
def update_aliens(ai_settings, stats, screen, ship, aliens, bullets):
check_fleet_edges(ai_settings, aliens)
aliens.update()
if pygame.sprite.spritecollideany(ship, aliens):
ship_hit(ai_settings, stats, screen, ship, aliens, bullets)
将center_ship()方法,添加到ship.py末尾:
def center_ship(self):
self.center = self.screen_rect.centerx
有外星人到达屏幕底部
如果有外星人到达屏幕底端,我们想有外星人撞到飞船那样做出反应。
创建一个新函数,check_aliens_bottom():
# game_functions.py
def update_aliens(ai_settings, stats, screen, ship, aliens, bullets):
--snip--
check_aliens_bottom(ai_settings, stats, screen, ship, aliens, bullets)
def check_aliens_bottom(ai_settings, stats, screen, 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, ship, aliens, bullets)
break
游戏结束
ships_left会不断变成更小的负数。下面在GameStats中添加一个作为标志的属性game_active,以便玩家在用完飞船后结束游戏。
# game_stats.py
class GameStats():
def __init__(self, ai_settings):
--snip--
self.game_active = True
在玩家所有飞船用完后,将game_active设置为False:
# game_functions.py
def ship_hit(ai_settings, stats, screen, ship, aliens, bullets):
if stats.ships_left > 0:
stats.ships_left -= 1
sleep(0.5)
else:
stats.game_active = False
--snip--
确定应运行游戏的那些部分
# alien_invasion.py
while True:
gf.check_events(ai_settings, screen, ship, bullets)
if stats.game_active:
ship.update()
gf.update_bullets(ai_settings, screen, ship, aliens, bullets)
gf.update_aliens(ai_settings, stats, screen, ship, aliens, bullets)
bullets.update()
gf.update_screen(ai_settings, screen, ship, aliens, bullets)
后记
如果有机会,下次再完善计分和开始结束让玩家可以持续玩这个游戏的功能。