文章目录
背景部分
创建Pygame窗口以及响应用户输入
# invasion.py
import sys
import pygame
def run_game():
# 初始化游戏并创建一个屏幕对象
pygame.init()
screen = pygame.display.set_mode((1200, 700))
pygame.display.set_caption("Thunder")
# 开始游戏的主循环
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
# 让最近绘制的屏幕可见
pygame.display.flip()
run_game()
- 首先我们导入模块
pygame
和sys
。sys用于退出游戏 - 游戏以函数
run_game()
开头。 pygame.init()
用于初始化游戏背景。pygame.display.set_mode()
用于创建一个名为screen的显示窗口。实参(1200,700)是一个元组,指定游戏窗口的尺寸。- 对象screen是一个surface。在Pygame中,surface是屏幕的一部分,用于显示游戏元素(比如外星人、飞船),游戏中每个元素都是一个surface。激活游戏的动画循环后,每经过一次循环都将重新绘制这个surface。
- 为访问Pygame侦听到的时间,我们使用方法
pygame.event.get()
。所有的键盘和鼠标事件都将促使for循环运行。比如玩家点击窗口的关闭按钮时,将检测到pygame.QUIT事件,我们就调用sys.exit()
来退出游戏。 pygame.display.flip()
命令Pygame让最近绘制的屏幕可见。它在每次执行while循环时都会绘制一个空屏幕,并擦去旧屏幕。
绘制背景色
# 在while循环中加入如下语句:
screen.fill((230,230,230))
Pygame中,颜色是以RGB值表示的。
创建设置类
#settings.py
class Settings():
"""存储游戏所有设置的类"""
def __init__(self):
"""初始化游戏的设置"""
# 屏幕设置
self.screen_width = 1200
self.screen_length = 700
self.bg_color = (230, 230, 230)
将所有游戏的设置存储在这个类中, 则invasion.py可修改:
import sys
import pygame
from settings import Settings
def run_game():
# 初始化游戏并创建一个屏幕对象
pygame.init()
sett = Settings()
screen = pygame.display.set_mode(
(sett.screen_length, sett.screen_width)
)
pygame.display.set_caption("Thunder")
# 开始游戏的主循环
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
screen.fill(sett.bg_color)
# 让最近绘制的屏幕可见
pygame.display.flip()
run_game()
飞船部分
添加飞船图像
就选用书配套的素材吧
创建Ship类
import pygame
class Ship():
def __init__(self, screen):
self.screen = screen
self.image = pygame.image.load("ship.bmp")
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)
在屏幕上绘制飞船
在invasion.py中创建飞船对象,并调用其方法blitme():
import sys
import pygame
from settings import Settings
from ship import Ship
def run_game():
# 初始化游戏并创建一个屏幕对象
pygame.init()
sett = Settings()
screen = pygame.display.set_mode(
(sett.screen_width, sett.screen_length)
)
pygame.display.set_caption("Thunder")
ship = Ship(screen)
# 开始游戏的主循环
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
screen.fill(sett.bg_color)
ship.blitme()
# 让最近绘制的屏幕可见
pygame.display.flip()
run_game()
运行后结果:
重构:game_function模块
函数check_events()
我们把管理事件的代码移到一个名为check_events()的函数里,以简化run_game()
#game_function.py
import sys
import pygame
def check_events():
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
函数update_screen()
为进一步简化run_game(),将更新屏幕的代码移到一个名为update_screen()的函数里,并将函数定义放在game_function中
def update_screen(sett, screen, ship):
screen.fill(sett.bg_color)
ship.blitme()
# 让最近绘制的屏幕可见
pygame.display.flip()
飞船移动部分
响应按键
每当用户按键时,都在Pygame里注册一个事件。事件都是通过方法pygame.event.get()
获取的,因此在函数check_events()中,我们需要制定检查哪些类型的事件。
每次按键都被注册一个KEYDOWN
事件。检测到该事件后,我们需要检查是否按下了特定的键,执行特定的操作。比如按下右键后,要让飞船向右移动。
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.rect.centerx += 1
我们在参数列表里加入了ship,因为需要能够访问到飞船内部的属性。
允许不断移动
玩家按住→键是希望飞船不停移动,直到松开为止。
我们可以让游戏检测pygame.KEYUP
事件,然后结合KEYUP和KEYDOWN事件实现持续移动。
# ship.py
def __init__(self, screen):
...
self.right_move = False
def update(self):
if self.right_move:
self.rect.centerx += 1
在飞船的类内初始化时多添了一个属性移动标志变量right_move
多添了一个方法update()
,用于检查该标志变量,实现飞船属性更新:这个变量为True时,飞船就会向右移动。
而这个标志变量会因KEYDOWN变为True,因KEYUP变为False,以此来实现持续移动
同时,要在invasion.py的while循环里调用update()方法:
# invasion.py
while True:
gf.check_events(ship)
ship.update()
gf.update_screen(sett, screen, ship)
左右移动
只需照着向右移动就能做出向左移动
调整飞行速度
每次执行while循环,飞船最多移动1像素。但可以在settings模块里加入属性ship_speed
,用于控制飞船的速度。
# settings.py
class Settings():
"""存储游戏所有设置的类"""
def __init__(self):
"""初始化游戏的设置"""
# 屏幕设置
self.screen_width = 1200
self.screen_length = 700
self.bg_color = (230, 230, 230)
self.ship_speed = 0.7
同时在ship.py中修改:
class Ship():
def __init__(self, sett, screen):
...
self.center = float(self.rect.centerx)
...
...
def update(self):
if self.right_move:
self.center += sett.ship_speed
if self.left_move:
self.center -= sett.ship_speed
self.rect.centerx = self.center
- 在__init__()的形参中加入了setting类的sett,让飞船的方法update()可以获取其速度设置。
- rect只存储整数,所以我们新建一个属性
center
,用flota()
将rect.centerx
转化成小数存储到center
中。更新center之后,再根据它来更新控制飞船位置的rect.centerx(虽然centerx只存储self.center的整数部分,但对于显示飞船而言问题不大。)
限制飞船活动范围
为了防止飞船飞出屏幕外,我们在飞船位置变更前添加if语句判断飞船是否将飞出框外。
# ship.py
def update(self):
if self.right_move and self.rect.right < self.screen_rect.right:
self.center += self.sett.ship_speed
if self.left_move and self.rect.left > 0:
self.center -= self.sett.ship_speed
如果rect的左/右边缘没有触及屏幕左/右边缘,才可以移动。
重构check_event()
# game_function.py
def check_keydown(event, ship):
if event.key == pygame.K_RIGHT:
ship.right_move = True
elif event.key == pygame.K_LEFT:
ship.left_move = True
def check_keyup(event, ship):
if event.key == pygame.K_RIGHT:
ship.right_move = False
elif event.key == pygame.K_LEFT:
ship.left_move = 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(event, ship)
elif event.type == pygame.KEYUP:
check_keyup(event, ship)
子弹部分
添加子弹设置
在setting.py中添加新类Bullet所需的值:
def __init__(self):
"""初始化游戏的设置"""
# 屏幕设置
...
# 子弹设置
self.bullet_speed = 1
self.bullet_width = 3
self.bullet_height = 15
self.bullet_color = (60, 60, 60)
创建Bullet类
# bullet.py
import pygame
from pygame.sprite import Sprite
class Bullet(Sprite):
"""一个对飞船的子弹管理的类"""
def __init__(self, sett, screen, ship):
"""在飞船处创建一个子弹对象"""
super(Bullet, self).__init__()
self.screen = screen
self.rect = pygame.Rect(0, 0, sett.bullet_width, sett.bullet_height)
self.rect.centerx = ship.rect.centerx
self.rect.top = ship.rect.top
self.y = float(self.rect.y)
self.color = sett.bullet_color
self.speed_factor = sett.bullet_speed
子弹并非基于图像,因此我们必须使用pygame.Rect()
类从空白开始创建一个矩形。创建这个类的实例时,必须提供矩形左上角的x坐标和y坐标,还有宽度和高度。我们先在(0,0)处创建一个矩形,并在接下来放在正确的位置,这个位置取决于飞船的位置。
接下来编写update()
和draw_bullet
方法
def update(self):
"""向上移动子弹"""
self.y -= self.speed
self.rect.y = self.y
def draw_bullet(self):
pygame.draw.rect(self.screen, self.color, self.rect)
将子弹存到编组中
在玩家每次按下空格时都射出一发子弹。首先我们在invasion.py中创建一个编组(Group)用于存储所有子弹,以便能够管理发射出去的子弹。
这个编组是pygame.sprite.Group类的一个实例;Group类 类似于列表,但提供了有助于游戏开发的功能。在主循环中,我们使用这个编组在屏幕上绘制子弹,更新每一个子弹的位置。
import sys
import pygame
import game_function as gf
from pygame.sprite import Group
from settings import Settings
from ship import Ship
def run_game():
# 初始化游戏并创建一个屏幕对象
...
bullets = Group()
# 开始游戏的主循环
while True:
gf.check_events(sett, screen, ship, bullets)
ship.update()
bullets.update()
gf.update_screen(sett, screen, ship, bullets)
run_game()
我们将bullets作为实参传递给了check_events()和update_screen()。在check_event()中我们要用空格处理bullets;在update_screen中则要更新绘制到屏幕上的bullets。
当你对编组调用update()时,编组将自动对每一个"精灵"调用update(),即对每一个子弹。
开火
因为只有在按下空格键时飞船才会开火,所以我们只需修改check_keydown_events()
而不用修改keyup
# game_function.py
from bullet import Bullet
def check_keydown(event, sett, screen, ship, bullets):
if event.key == pygame.K_RIGHT:
ship.right_move = True
elif event.key == pygame.K_LEFT:
ship.left_move = True
elif event.key == pygame.K_SPACE:
new_bullet = Bullet(sett, screen, ship)
bullets.add(new_bullet)
def check_events(sett, screen, ship, bullets):
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
check_keydown(event, sett, screen, ship, bullets)
elif event.type == pygame.KEYUP:
check_keyup(event, ship)
def update_screen(sett, screen, ship, bullets):
screen.fill(sett.bg_color)
ship.blitme()
for bullet in bullets:
bullet.draw_bullet()
# 让最近绘制的屏幕可见
pygame.display.flip()
删除已经消失的子弹
我们需要将已经飞出屏幕的子弹删除,减少内存负担。
为此,我们需要在每次更新子弹位置后,检测rect的bottom属性小于0的子弹,并删除它们。
# invasion.py
while True:
gf.check_events(sett, 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(sett, screen, ship, bullets)
- 在for循环中,不应从列表或编组中删除条目,因此必须是遍历编组的副本,故需要调用方法
copy()
,返回一个编组的副本。 - 输出编组的长度,即有效子弹的数量,是为了显示子弹的数量,核实已消失的子弹确实被删除了。
子弹效果如图:
限制子弹数量
多数同类型游戏里面都会有对子弹数量的限制,鼓励玩家有目标地射击。
我们在此限制子弹最大数量为4.
首先在Setting类里设置允许的最大子弹数:
#setting.py
class Settings():
"""存储游戏所有设置的类"""
def __init__(self):
"""初始化游戏的设置"""
# 屏幕设置
...
# 子弹设置
...
self.bullet_allowed = 4
在check_keydown_event()中检测到空格前,添加if语句判断子弹数量(群组长度)是否已经超过最大限制。
# game_function.py
def check_keydown(event, sett, screen, ship, bullets):
if event.key == pygame.K_RIGHT:
ship.right_move = True
elif event.key == pygame.K_LEFT:
ship.left_move = True
elif event.key == pygame.K_SPACE:
if len(bullets) < sett.bullet_allowed:
new_bullet = Bullet(sett, screen, ship)
bullets.add(new_bullet)
重构bullet函数
我们可以把子弹更新函数和删除子弹的代码写进一个函数update_bullet()
里:
# game_function.py
def update_bullet(bullets):
bullets.update()
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
print(len(bullets))
故主循环里的代码可简化:
# invasion.py
while True:
gf.check_events(sett, screen, ship, bullets)
ship.update()
gf.update_bullet(bullets)
gf.update_screen(sett, screen, ship, bullets)
同时,把检查子弹数量是否超额的代码已经添加新子弹的代码整合进一个fire_bullet()
函数里:
# game_function.py
def fire_bullet(sett, screen, ship, bullets):
if len(bullets) < sett.bullet_allowed:
new_bullet = Bullet(sett, screen, ship)
bullets.add(new_bullet)
def check_keydown(event, sett, screen, ship, bullets):
if event.key == pygame.K_RIGHT:
ship.right_move = True
elif event.key == pygame.K_LEFT:
ship.left_move = True
elif event.key == pygame.K_SPACE:
fire_bullet(sett, screen, ship, bullets)
外星人部分
创建Alien类
# alien.py
import pygame
from pygame.sprite import Sprite
class Alien(Sprite):
"""单个外星人的类"""
def __init__(self, sett, screen):
super().__init__()
self.screen = screen
self.setting = sett
# 加载外星人图像,设置rect属性
self.image = pygame.image.load('alien.bmp')
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实例
# invasion.py
def run_game():
# 初始化游戏并创建一个屏幕对象
...
alien = Alien(sett, screen)
...
# 开始游戏的主循环
while True:
...
gf.update_screen(sett, screen, ship, alien, bullets)
# update_screen里调用 alien.blitme()
run_game()
创建一群外星人
确定一行可以容纳多少外星人
我们要根据屏幕水平宽度确定一行可容纳多少外星人。我们要在屏幕两边留下边距,把它设置为外星人图像的宽度。所以放置外星人的水平空间为:
available_space_x = sett.screen_width - (2 * alien_width)
外星人之间还得留下空间,设置为一个外星人的宽度。因此一行可容纳的外星人数量:
number_aliens_x = available_space_x / (2 * alien_width)
创建多行外星人
为创建一行外星人,首先在invasion.py中创建一个名为aliens的空编组,用于存储全部外星人,再调用game_function.py中的创建外星人群的函数:
# invasion.py
...
ship = Ship(sett, screen)
bullets = Group()
aliens = Group()
aliens = Group()
gf.create_fleet(sett, screen, aliens)
# 开始游戏的主循环
while True:
...
gf.update_screen(sett, screen, ship, aliens, bullets)
# game_function.py
def create_fleet(sett, screen, aliens):
"""创建外星人群"""
# 创建一个外星人,并计算一行可容纳多少外星人
alien = Alien(sett, screen)
alien_width = alien.rect.width
available_space_x = sett.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(sett, screen)
alien.x = alien_width + 2 * alien_width * alien_number
alien.rect.x = alien.x
aliens.add(alien)
def update_screen(sett, screen, ship, aliens, bullets):
...
...
aliens.draw(screen)
...
# 让最近绘制的屏幕可见
...
效果如图:
重构create_fleet()
为create_fleet()新添两个函数create_alien()
和get_number_aliens_x()
# game_function.py
def get_number_aliens_x(sett, alien_width):
available_space_x = sett.screen_width - 2 * alien_width
number_aliens_x = int(available_space_x / (2 * alien_width))
return number_aliens_x
def create_alien(sett, screen, aliens, alien_width, alien_number):
# 创建一个外星人并加入群组
alien = Alien(sett, screen)
alien.x = alien_width + 2 * alien_width * alien_number
alien.rect.x = alien.x
aliens.add(alien)
def create_fleet(sett, screen, aliens):
"""创建外星人群"""
# 创建一个外星人,并计算一行可容纳多少外星人
alien = Alien(sett, screen)
alien_width = alien.rect.width
number_aliens_x = get_number_aliens_x(sett, alien_width)
# 创建第一行外星人
for alien_number in range(number_aliens_x):
create_alien(sett, screen, aliens, alien_width, alien_number)
添加行
要创建外星人群,需要计算屏幕可容纳多少行,并对创建一行外星人的循环重复相应的次数。为计算可容纳的行数,我们将屏幕高度减去第一行的外星人的上边距(外星人高度)、飞船的高度以及最初外星人高度加上外星人边距:
available_space_y = sett.screen_height - 3*alien_height - ship_height
这样可以给飞船上方留出一定空白区域。
每行下方都要留出一定的空白区域,并将其设置为外星人的高度。为计算可容纳的行数,我们将可用垂直空间除以外星人高度的两倍:
number_rows = available_space_y /(2 * alien_height)
# game_funtion.py
def get_nuber_rows(sett, alien_height, ship_height):
available_space_y = sett.screen_length - 3 * alien_height - ship_height
number_rows = int(available_space_y / (2 * alien_height))
return number_rows
def create_alien(sett, screen, aliens, alien_width, alien_number, row_number):
# 创建一个外星人并加入群组
...
alien.rect.y = alien.rect.height + 2 * alien.rect.height * row_number
...
def create_fleet(sett, screen, ship, aliens):
"""创建外星人群"""
# 创建一个外星人,并计算一行可容纳多少外星人
...
number_rows = get_nuber_rows(sett, alien_height, ship.rect.height)
# 创建第一行外星人
for row_number in range(number_rows):
for alien_number in range(number_aliens_x):
create_alien(sett, screen, aliens, alien_width, alien_number, row_number)
移动外星人
让外星人向右移动
为移动外星人,我们将使用alien.py中的方法update(),且对外星人群中的每个外星人都调用它。
首先添加一个外星人移动速度的设置:
# setting.py
class Settings():
"""存储游戏所有设置的类"""
def __init__(self):
"""初始化游戏的设置"""
# 屏幕设置
...
# 子弹设置
...
self.alien_speed = 1
然后在Alien类里实现update():
# alien.py
def update(self):
self.x += self.setting.alien_speed
self.rect.x = self.x
接着在game_function.py里编写update_aliens()
# game_function.py
def update_aliens(aliens):
aliens.update()
aliens编组将自动对每一个外星人调用update()。
在主循环里调用update_aliens(aliens):
while True:
gf.check_events(sett, screen, ship, bullets)
ship.update()
gf.update_bullet(bullets)
gf.update_aliens(aliens)
gf.update_screen(sett, screen, ship, aliens, bullets)
创建表示外星人移动方向的设置
让外星人在撞到屏幕右边缘后会向下移动,再向左移动,代码如下:
# setting.py
self.alien_speed = 1
self.alien_drop_speed = 10
# 下降速度
self.fleet_direction = 1
# 1表示向右,-1表示向左, 可以直接作为速度的系数用于坐标运算
检查外星人是否撞到边缘
检查外星人是否撞到边缘,为类Alien编写方法check_edges()
:
# 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 <= screen_rect.left:
return True
同时修改update():
def update(self):
self.x += self.setting.alien_speed * self.setting.fleet_direction
self.rect.x = self.x
向下移动并改变移动方向
有一个外星人到达屏幕边缘时,需要将整群外星人下移并转向。所以我们需要对game_function.py做大修改,因为我们需要检查每一个外星人是不是已经到了边缘。为此我们编写check_fleet_edge()
和change_fleet_dir()
# game_function.py
def change_fleet_dir(sett, aliens):
"""将整群外星人下移"""
for alien in aliens:
alien.rect.y += sett.alien_drop_speed
sett.fleet_direction *= -1
def check_fleet_edges(sett, aliens):
"""有一个外星人到达边缘"""
for alien in aliens.sprites():
if alien.check_edges():
change_fleet_dir(sett, aliens)
break
def update_aliens(sett, aliens):
check_fleet_edges(sett, aliens)
aliens.update()
同时修改主循环中update_aliens()的参数:
射杀外星人
检测子弹与外星人的碰撞
子弹击中外星人时,我们要让外星人消失。为此我们需要在更新子弹位置后判断其是否碰撞。
我们用sprite.groupcollide()
方法来检测两个群组的成员是否有碰撞。
它将每颗子弹的rect同每个外星人的rect进行比较,并返回一个字典,其中包含发生碰撞的子弹和外星人。在这个字典中,每个键都是一颗子弹,而对应的值都是被击中的外星人。(这个字典在之后计分要用到)
# game_function.py
def update_bullets(aliens, bullets):
...
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
这行代码先遍历每颗子弹再遍历每个外星人,每当有两者rect重叠,它就在返回的字典中添加一对键值对。最后两个实参告诉pygame删除发生碰撞的子弹和外星人(第一个true表示子弹会被删除,如果改为false则子弹碰撞时不会被删除,而是一直飞到屏幕外)
接着要在invasion.py中的update_bullets()参数中添加aliens。
生成新的外星人群
当一个外星人群被消灭后,应该再出现另一群外星人。
我们先检查编组aliens是否为空,如果为空,就调用create_fleet()。我们将在update_bullets()中进行这个检查,因为外星人都是在这里被消灭的
# game_function.py
def update_bullets(sett, screen, ship, aliens, bullets):
...
if len(aliens) == 0:
create_fleet(sett, screen, ship, aliens)
同时要修改invasion.py中update_bullets的参数。
重构update_bullets()
# game_function.py
def check_bullet_collision(sett, screen, ship, bullets, aliens):
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
if len(aliens) == 0:
create_fleet(sett, screen, ship, aliens)
def update_bullet(sett, screen, ship, bullets, aliens):
bullets.update()
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
print(len(bullets))
check_bullet_collision(sett, screen, ship, bullets, aliens)
结束游戏
需要添加失败事件:外星人撞到飞船,或者有外星人降到屏幕底端,飞船将会被摧毁,玩家用光生命树后游戏结束。
检测飞船与外星人碰撞
# game_function.py
def update_aliens(sett, ship, aliens):
check_fleet_edges(sett, aliens)
aliens.update()
if pygame.sprite.spritecollideany(ship, aliens):
print("shit!")
方法spritecollideany()
接受两个实参:一个精灵和一个编组。它检查编组是否有其他成员与精灵发生了碰撞,并在找到与精灵发生碰撞的成员后停止遍历,返回True. 如果没有碰撞则返回None。
响应外星人与飞船碰撞
飞船与外星人碰撞后:飞船生命-1、全屏外星人和子弹清空并暂停一段时间后出现新的外星人群。
寻找编写一个用于跟踪游戏统计信息的新类–Gamestats, 并将其保存为文件stats. py :
# stats.py
class Gamestats():
def __init__(self, sett):
self.sett = sett
self.reset_stats()
self.active = False
def reset_stats(self):
self.life = 1
同时在invasion.py中创建一个名为stats的实例
sett = Settings()
stats = Gamestats(sett)
接着编写飞船碰撞时的响应:
# game_function.py
def ship_hit(sett, stats, screen, ship, aliens, bullets):
stats.life -= 1
aliens.empty()
bullets.empty()
create_fleet(sett, screen, ship, aliens)
ship.center = screen.get_rect().centerx
# 将飞船调整至中心位置
sleep(0.5)
def update_aliens(sett, stats, screen, ship, aliens, bullets):
check_fleet_edges(sett, aliens)
aliens.update()
if pygame.sprite.spritecollideany(ship, aliens):
ship_hit(sett, stats, screen, ship, aliens, bullets)
同时要在invasion.py中修改update_aliens()参数列表
有外星人到达底部
为此我们写一个函数check_alien_bottom()
# game_function.py
def alien_bottom(sett, stats, screen, ship, aliens, bullets):
screen_rect = screen.get_rect()
for alien in aliens:
if alien.rect.bottom >= screen_rect.bottom:
ship_hit(sett, stats, screen, ship, aliens, bullets)
def update_aliens(sett, stats, screen, ship, aliens, bullets):
check_fleet_edges(sett, aliens)
aliens.update()
if pygame.sprite.spritecollideany(ship, aliens) or alien_bottom(sett, stats, screen, ship, aliens, bullets):
ship_hit(sett, stats, screen, ship, aliens, bullets)
游戏结束
当life减为0后,游戏结束。我们在GameStats里添加一个作为标志的属性active,以便在玩家的飞船用完后结束游戏:
# stats.py
class Gamestats():
def __init__(self, sett):
self.sett = sett
self.life = 3
self.active = True
def reset_stats(self):
self.life = 3
self.active = True
当玩家的生命减为0时,该变量变为false
# game_function.py
def ship_hit(sett, stats, screen, ship, aliens, bullets):
stats.life -= 1
if stats.life>0:
aliens.empty()
bullets.empty()
create_fleet(sett, screen, ship, aliens)
ship.center = screen.get_rect().centerx
sleep(0.5)
else:
stats.active = False
添加PLAY按钮
添加PLAY按钮,让程序开始时处于非活动状态,则要修改stats.py中的代码
class Gamestats():
def __init__(self, sett):
self.sett = sett
self.life = 3
self.active = False
def reset_stats(self):
self.life = 3
创建Button类
由于pygame没有内置创建按钮的方法,所以我们创建一个Button类
# button.py
import pygame.font
class Button():
def __init__(self, sett, screen, msg):
self.screen = screen
self.screen_rect = screen.get_rect()
self.width, self.height = 200, 50
self.button_color = (0, 255, 0)
self.text_color = (255, 255, 255)
self.font = pygame.font.SysFont(None, 48)
# 指定字体字号来渲染文字
self.rect = pygame.Rect(0, 0, self.width, self.height)
self.rect.center = self.screen_rect.center
# 将字符串渲染成图像
self.prep_msg(msg)
def prep_msg(self, msg):
"""将字符串渲染成图像"""
# 第二个布尔参数是反锯齿开关
self.msg_image = self.font.render(msg, True, self.text_color, self.button_color)
self.msg_image_rect = self.msg_image.get_rect()
self.msg_image_rect.center = self.rect.center
def draw_button(self):
self.screen.fill(self.button_color, self.rect)
self.screen.blit(self.msg_image, self.msg_image_rect)
在屏幕上绘制按钮
只需要一个Play按钮,故我们直接在invasion.py中创建
...
from button import Button
def run_game():
# 初始化游戏并创建一个屏幕对象
...
pygame.display.set_caption("Thunder")
play_button = Button(sett, screen, 'PLAY')
...
# 开始游戏的主循环
while True:
...
gf.update_screen(sett, screen, stats, ship, aliens, bullets, play_button)
run_game()
接着修改game_function.py的update_screen,以便在游戏处于非活动状态时显示按钮
def update_screen(sett, screen, stats, ship, aliens, bullets, button):
...
if not stats.active:
button.draw_button()
# 让最近绘制的屏幕可见
pygame.display.flip()
一定要把draw放在flip前面,这样才能让绘制完所有其他元素之后再绘制按钮,然后切换到新屏幕。
开始游戏
在按下按钮时开始新游戏,需要对鼠标事件进行监视。
在game_function.py中添加如下代码:
def check_play(stats, button, mouseX, mouseY):
if button.rect.collidepoint(mouseX, mouseY):
stats.active = True
def check_events(sett, screen, stats, button, ship, bullets):
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
check_keydown(event, sett, screen, ship, bullets)
elif event.type == pygame.KEYUP:
check_keyup(event, ship)
elif event.type == pygame.MOUSEBUTTONDOWN:
mouse_X, mouse_Y = pygame.mouse.get_pos()
check_play(stats, button, mouse_X, mouse_Y)
重置游戏
游戏结束后,会再显示PLAY按钮。每次单击它都应该重置整个游戏,重置统计信息,删除现有的外星人和子弹,创建新的外星人,让飞船居中。
def check_play(sett, screen, stats, button, ship, aliens, bullets, mouseX, mouseY):
if button.rect.collidepoint(mouseX, mouseY):
stats.reset_stats()
stats.active = True
aliens.empty()
bullets.empty()
create_fleet(sett, screen, ship, aliens)
ship.center = screen.get_rect().centerx
def check_events(sett, screen, stats, button, ship, aliens, bullets):
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
elif event.type == pygame.KEYDOWN:
check_keydown(event, sett, screen, ship, bullets)
elif event.type == pygame.KEYUP:
check_keyup(event, ship)
elif event.type == pygame.MOUSEBUTTONDOWN:
mouse_X, mouse_Y = pygame.mouse.get_pos()
check_play(sett, screen, stats, button, ship, aliens, bullets, mouse_X, mouse_Y)
将Play按钮切换到非活动状态
有一个问题是,即使在游戏活动状态,按钮图形不会显示,但是点击其原来的位置依然会重置游戏。所以要在监视鼠标事件时添加一个if条件:
def check_play(sett, screen, stats, button, ship, aliens, bullets, mouseX, mouseY):
if button.rect.collidepoint(mouseX, mouseY) and not stats.active:
...
隐藏光标
在点击Play后的游戏活动状态,鼠标光标应该被隐藏
def check_play(sett, screen, stats, button, ship, aliens, bullets, mouseX, mouseY):
if button.rect.collidepoint(mouseX, mouseY) and not stats.active:
pygame.mouse.set_visible(False)
stats.reset_stats()
stats.active = True
aliens.empty()
bullets.empty()
create_fleet(sett, screen, ship, aliens)
ship.center = screen.get_rect().centerx
并在游戏结束后重现它:
def ship_hit(sett, stats, screen, ship, aliens, bullets):
stats.life -= 1
if stats.life > 0:
...
else:
pygame.mouse.set_visible(True)
stats.active = False
提高难度
随着游戏的进行,游戏的难度应当得到提升。
修改速度设置
我们要通过提高游戏整体速度来提升难度,所以飞船、子弹、外星人的速度是在变的。为此我们可以将settting里的设置属性分为静态和动态两部分。
class Settings():
"""存储游戏所有设置的类"""
def __init__(self):
"""初始化游戏的设置"""
# 屏幕设置
self.screen_width = 1200
self.screen_length = 700
self.bg_color = (230, 230, 230)
# 子弹设置
self.bullet_width = 3
self.bullet_height = 15
self.bullet_color = (60, 60, 60)
self.bullet_allowed = 4
self.speed_scale = 1.1
# 速度提升率
self.init_dynamic()
def init_dynamic(self):
self.alien_speed = 1
self.alien_drop_speed = 10
# 下降速度
self.fleet_direction = 1
# 1表示向右,-1表示向左, 可以直接作为速度的系数用于坐标运算
self.bullet_speed = 2
self.ship_speed = 1.5
接着编写提升速度的方法increase_speed()
def increase_speed(self):
self.alien_speed *= self.speed_scale
self.bullet_speed *= self.speed_scale
self.ship_speed *= self.speed_scale
并在每消灭一群外星人时调用一次这个方法:
def check_bullet_collision(sett, screen, ship, bullets, aliens):
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
if len(aliens) == 0:
create_fleet(sett, screen, ship, aliens)
sett.increase_speed()
重置速度
每次开始新游戏时,速度都要重置一次
def check_play(sett, screen, stats, button, ship, aliens, bullets, mouseX, mouseY):
if button.rect.collidepoint(mouseX, mouseY) and not stats.active:
...
sett.init_dynamic()
记分
在stats类里添加一个属性记录得分
class Gamestats():
def __init__(self, sett):
self.sett = sett
self.reset_stats()
self.active = False
def reset_stats(self):
self.life = 1
self.score = 0
显示得分
为了在屏幕上显示得分,我们首先创建一个新类scoreboard:
import pygame.font
class ScoreBoard():
def __init__(self, sett, screen, stats):
self.screen = screen
self.screen_rect = screen.get_rect()
self.sett = sett
self.stats = stats
self.text_color = (30, 30, 30)
self.font = pygame.font.SysFont(None, 48)
self.prep_score()
def prep_score(self):
score_str = str(self.stats.score)
self.score_image = self.font.render(score_str, True, self.text_color, self.sett.bg_color)
self.score_rect = self.score_image.get_rect()
self.score_rect.right = self.screen_rect.right - 20
self.score_rect.top = 20
def show_score(self):
self.screen.blit(self.score_image, self.score_rect)
创建记分牌实例
...
from scoreboard import ScoreBoard
def run_game():
# 初始化游戏并创建一个屏幕对象
...
sb = ScoreBoard(sett, screen, stats)
...
# 开始游戏的主循环
while True:
...
gf.update_screen(sett, screen, stats, sb, ship, aliens, bullets, play_button)
run_game()
同时要在update_screen()中调用show_score()
得分
击杀外星人后要增加分数。只需要检查子弹击中外星人时返回的字典(collision)即可.
我们现在setting中设置一个外星人的得分。
def __init__(self):
"""初始化游戏的设置"""
...
self.alien_score = 50
接着在check_bullet_collision()
中检查字典。(这个字典的键是一颗子弹,值是被这颗子弹击中的外星人列表)
def check_bullet_collision(sett, screen, stats, sb, ship, bullets, aliens):
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
...
if collisions:
for aliens in collisions.key():
stats.score += sett.alien_score * len(aliens)
sb.prep_score()
主循环中要修改update_bullets()的参数
提高点数
随着游戏难度提升,一个外星人的得分应当提高。
所以在setting中增加一个得分提升的幅度属性
在游戏难度提升时,即速度提高时,修改setting的属性alien_score
(因为alien_score会变动,所以要把这个属性分类为动态,使其在动态初始化方法中被赋初值)
class Settings():
"""存储游戏所有设置的类"""
def __init__(self):
"""初始化游戏的设置"""
...
self.init_dynamic()
def init_dynamic(self):
...
self.alien_score = 50
def increase_speed(self):
...
self.alien_score = int(self.alien_score * self.score_scale)
将得分圆整
大部分游戏都会将游戏得分显示为10的整倍数。我们可以把得分圆整。
# scoreboard.py
def prep_score(self):
rounded_score = int(round(self.stats.score, -1))
score_str = "{:,}".format(rounded_score)
...
round()
的第二个参数为精确到的小数位。
第二个参数为负数,则round()
将圆整到最近的10、100、1000等整倍数。
"{:,}".format(rounded_score)
为一个字符串格式设置指令,它让Python将数值转换成字符串时在其中插入逗号。
最高分
我们在stats中增加一个属性最高分,并将其展示在屏幕顶端中央。
但是为了让数据保存,这个最高分存储在外部文件中,所以每次都需要从外部文件读入:
class Gamestats():
def __init__(self, sett):
self.sett = sett
self.reset_stats()
with open("highScore.txt", 'r') as hs:
self.high_score = int(hs.read())
每当一场游戏结束后,都要更新最高分:
def ship_hit(sett, stats, screen, ship, aliens, bullets):
stats.life -= 1
if stats.life > 0:
...
else:
pygame.mouse.set_visible(True)
stats.active = False
if stats.score > stats.high_score:
stats.high_score = stats.score
每次关闭前都要在外部文件更新最高分:
def check_events(sett, screen, stats, button, ship, aliens, bullets):
for event in pygame.event.get():
if event.type == pygame.QUIT:
with open("highScore.txt", 'w') as hs:
hs.write(str(stats.high_score))
sys.exit()
...
接着要在最顶端显示最高分:
# scoreboard.py
class ScoreBoard():
def __init__(self, sett, screen, stats):
...
self.prep_score()
self.prep_high()
def prep_score(self):
...
def prep_high(self):
high_score_str = "{:,}". format(self.stats.high_score)
print(high_score_str)
self.high_score_image = self.font.render(high_score_str, True, self.text_color, self.sett.bg_color)
self.high_score_rect = self.high_score_image.get_rect()
self.high_score_rect.centerx = self.screen_rect.centerx
self.high_score_rect.top = self.screen_rect.top
def show_score(self):
self.screen.blit(self.score_image, self.score_rect)
self.screen.blit(self.high_score_image, self.high_score_rect)
同时,在更新过最高分之后调用prep_high()
:
def ship_hit(sett, stats, screen, ship, sb, aliens, bullets):
stats.life -= 1
if stats.life > 0:
...
else:
pygame.mouse.set_visible(True)
stats.active = False
if stats.score > stats.high_score:
stats.high_score = stats.score
sb.prep_high()
用到ship_hit()的地方都要修改参数
奖励子弹
我们新增一个机制,当玩家分数达到一定程度后,我们将接下来的3发子弹的宽度提高100倍,提高消灭外星人的效率。
首先,现在stats.py中设置奖励标准:
class Gamestats():
def __init__(self, sett):
...
def reset_stats(self):
...
self.award_level = 1 # 奖励等级
self.bullet_award = False # 奖励状态
self.award_b = 0 # 已用奖励子弹数量
self.award_score = 1500 # 奖励分数标准
因为外星人的分数会随着游戏难度增加而增加,所以奖励分数标准应该在每一次奖励后增加。所以我们在setting.py中增加一个属性award_score_scale
:
class Settings():
"""存储游戏所有设置的类"""
def __init__(self):
"""初始化游戏的设置"""
# 屏幕设置
self.screen_width = 1200
self.screen_length = 700
self.bg_color = (230, 230, 230)
# 子弹设置
self.bullet_width = 3
self.bullet_height = 15
self.bullet_color = (60, 60, 60)
self.bullet_allowed = 4
self.speed_scale = 1.1
# 速度提升率
self.score_scale = 1.5
self.award_score_scale = 1.4
# 分数提升率
接着,在game_funciton.py中编写判断函数:
def award(sett, stats):
if stats.score >= stats.award_level * stats.award_score:
stats. bullet_award = True
sett.bullet_width = 300
stats.award_level += 1
stats.award_b = 0
stats.award_score *= sett.award_score_scale
每此奖励完之后,奖励等级(award_level)要提升,奖励分数标准(award_score)要提升,已用奖励子弹数(award_b)清零。
然后要让奖励状态在三发子弹后变回False。因为子弹是在按下空格后发射,所以我们可以在检测空格事件的函数中实现:
def award_check(sett, stats):
if stats.bullet_award:
if stats.award_b == 3:
stats.bullet_award = False
sett.bullet_width = 3
stats.award_b += 1
def check_keydown(event, sett, screen, stats, ship, bullets):
if event.key == pygame.K_RIGHT:
ship.right_move = True
elif event.key == pygame.K_LEFT:
ship.left_move = True
elif event.key == pygame.K_SPACE:
if stats.bullet_award:
award_check(sett, stats)
fire_bullet(sett, screen, ship, bullets)
因为奖励状态是随着得分转变的,所以我们在得分的函数里调用award()
,即check_bullet_collision():
def check_bullet_collision(sett, screen, stats, sb, ship, bullets, aliens):
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
if len(aliens) == 0:
create_fleet(sett, screen, ship, aliens)
sett.increase_speed()
if collisions:
for aliens in collisions.values():
stats.score += sett.alien_score * len(aliens)
sb.prep_score()
award(sett, stats)
左上角显示剩余生命
最后,我们来显示玩家还剩多少艘飞船,但用的是图形而不是数字。
首先,需要让Ship继承Sprite,以便创建飞船编组:
import pygame
from pygame.sprite import Sprite
class Ship(Sprite):
def __init__(self, sett, screen):
...
super().__init__()
接着在scoreboard. py 中,创建一个可供显示的飞船编组。
def show_score(self):
self.screen.blit(self.score_image, self.score_rect)
self.screen.blit(self.high_score_image, self.high_score_rect)
self.ships.draw(self.screen)
def prep_ship(self):
"""显示剩余飞船"""
self.ships = Group()
for ship_num in range(self.stats.life):
ship = Ship(self.sett, self.screen)
ship.rect.x = 10 + ship_num * ship.rect.width
ship.rect.y = 10
self.ships.add(ship)
要在游戏开始时显示这个剩余生命,所以我们在开始新游戏时调用prep_ships()。这个将在check_play()中进行:
def check_play(sett, screen, stats, button, ship, sb, aliens, bullets, mouseX, mouseY):
if button.rect.collidepoint(mouseX, mouseY) and not stats.active:
...
sb.prep_score()
sb.prep_ship()
同时,当损失生命值时,左上角的生命牌需要更新,要在ship_hit()中还要调用prep_ship()
def ship_hit(sett, stats, screen, ship, sb, aliens, bullets):
stats.life -= 1
if stats.life > 0:
...
else:
....
sb.prep_ship()
- 别忘了对对相关函数的参数列表修改
最后的重构
- 将清屏和重新创建舰队的代码编写为一个函数
clear_recreate()
- 将点击PLAY按钮后的分数板和动态设置重置的函数整合为
restart()
- 将scoreboard .py中的__init__()调用的prep方法整合
最终代码:
# ship.py
import pygame
from pygame.sprite import Sprite
class Ship(Sprite):
def __init__(self, sett, screen):
self.screen = screen
self.sett = sett
self.image = pygame.image.load("ship.bmp")
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
self.center = float(self.rect.centerx)
self.right_move = False
self.left_move = False
super().__init__()
def blitme(self):
"""在指定位置绘制飞船"""
self.screen.blit(self.image, self.rect)
def update(self):
if self.right_move and self.rect.right < self.screen_rect.right:
self.center += self.sett.ship_speed
if self.left_move and self.rect.left > 0:
self.center -= self.sett.ship_speed
self.rect.centerx = self.center
# alien.py
import pygame
from pygame.sprite import Sprite
class Alien(Sprite):
"""单个外星人的类"""
def __init__(self, sett, screen):
super().__init__()
self.screen = screen
self.setting = sett
# 加载外星人图像,设置rect属性
self.image = pygame.image.load('alien.bmp')
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)
def check_edges(self):
screen_rect = self.screen.get_rect()
if self.rect.right >= screen_rect.right:
return True
elif self.rect.left <= screen_rect.left:
return True
def update(self):
self.x += self.setting.alien_speed * self.setting.fleet_direction
self.rect.x = self.x
# bullet.py
import pygame
from pygame.sprite import Sprite
class Bullet(Sprite):
"""一个对飞船的子弹管理的类"""
def __init__(self, sett, screen, ship):
super().__init__()
self.screen = screen
self.rect = pygame.Rect(0, 0, sett.bullet_width, sett.bullet_height)
self.rect.centerx = ship.rect.centerx
self.rect.top = ship.rect.top
self.y = float(self.rect.y)
self.color = sett.bullet_color
self.speed = sett.bullet_speed
def update(self):
"""向上移动子弹"""
self.y -= self.speed
self.rect.y = self.y
def draw_bullet(self):
pygame.draw.rect(self.screen, self.color, self.rect)
# setting.py
class Settings():
"""存储游戏所有设置的类"""
def __init__(self):
"""初始化游戏的设置"""
# 屏幕设置
self.screen_width = 1200
self.screen_length = 700
self.bg_color = (230, 230, 230)
# 子弹设置
self.bullet_width = 3
self.bullet_height = 15
self.bullet_color = (60, 60, 60)
self.bullet_allowed = 4
self.speed_scale = 1.1
# 速度提升率
self.score_scale = 1.5
self.award_score_scale = 1.4
# 分数提升率
self.awared_width = 300
# 奖励宽度
self.init_dynamic()
def init_dynamic(self):
self.alien_speed = 1
self.alien_drop_speed = 10
# 下降速度
self.fleet_direction = 1
# 1表示向右,-1表示向左, 可以直接作为速度的系数用于坐标运算
self.bullet_speed = 2
self.ship_speed = 1.5
self.alien_score = 50
def increase_speed(self):
self.alien_speed *= self.speed_scale
self.bullet_speed *= self.speed_scale
self.ship_speed *= self.speed_scale
self.alien_score = int(self.alien_score * self.score_scale)
# stats.py
class Gamestats():
def __init__(self, sett):
self.sett = sett
self.reset_stats()
with open("highScore.txt", 'r') as hs:
self.high_score = int(hs.read())
def reset_stats(self):
self.life = 3
self.score = 0
self.active = False
self.award_level = 1
self.bullet_award = False
self.award_b = 0
self.award_score = 1500
# button.py
import pygame.font
class Button():
def __init__(self, sett, screen, msg):
self.screen = screen
self.screen_rect = screen.get_rect()
self.width, self.height = 200, 50
self.button_color = (0, 255, 0)
self.text_color = (255, 255, 255)
self.font = pygame.font.SysFont(None, 48)
# 指定字体字号来渲染文字
self.rect = pygame.Rect(0, 0, self.width, self.height)
self.rect.center = self.screen_rect.center
# 将字符串渲染成图像
self.prep_msg(msg)
def prep_msg(self, msg):
"""将字符串渲染成图像"""
# 第二个布尔参数是反锯齿开关
self.msg_image = self.font.render(msg, True, self.text_color, self.button_color)
self.msg_image_rect = self.msg_image.get_rect()
self.msg_image_rect.center = self.rect.center
def draw_button(self):
self.screen.fill(self.button_color, self.rect)
self.screen.blit(self.msg_image, self.msg_image_rect)
# scoreboard.py
import pygame.font
from pygame.sprite import Group
from ship import Ship
class ScoreBoard():
def __init__(self, sett, screen, stats):
self.screen = screen
self.screen_rect = screen.get_rect()
self.sett = sett
self.stats = stats
self.text_color = (30, 30, 30)
self.font = pygame.font.SysFont(None, 48)
self.prep_image()
def prep_image(self):
self.prep_score()
self.prep_high()
self.prep_ship()
def prep_score(self):
rounded_score = int(round(self.stats.score, -1))
score_str = "{:,}".format(rounded_score)
self.score_image = self.font.render(score_str, True, self.text_color, self.sett.bg_color)
self.score_rect = self.score_image.get_rect()
self.score_rect.right = self.screen_rect.right - 20
self.score_rect.top = 20
def prep_high(self):
high_score_str = "{:,}". format(self.stats.high_score)
self.high_score_image = self.font.render(high_score_str, True, self.text_color, self.sett.bg_color)
self.high_score_rect = self.high_score_image.get_rect()
self.high_score_rect.centerx = self.screen_rect.centerx
self.high_score_rect.top = self.screen_rect.top
def show_score(self):
self.screen.blit(self.score_image, self.score_rect)
self.screen.blit(self.high_score_image, self.high_score_rect)
self.ships.draw(self.screen)
def prep_ship(self):
"""显示剩余飞船"""
self.ships = Group()
for ship_num in range(self.stats.life):
ship = Ship(self.sett, self.screen)
ship.rect.x = 10 + ship_num * ship.rect.width
ship.rect.y = 10
self.ships.add(ship)
# game_function.py
import sys
import pygame
from bullet import Bullet
from alien import Alien
from time import sleep
def fire_bullet(sett, screen, ship, bullets):
if len(bullets) < sett.bullet_allowed:
new_bullet = Bullet(sett, screen, ship)
bullets.add(new_bullet)
def get_number_aliens_x(sett, alien_width):
available_space_x = sett.screen_width - 2 * alien_width
number_aliens_x = int(available_space_x / (2 * alien_width))
return number_aliens_x
def get_nuber_rows(sett, alien_height, ship_height):
available_space_y = sett.screen_length - 3 * alien_height - ship_height
number_rows = int(available_space_y / (2 * alien_height))
return number_rows
def create_alien(sett, screen, aliens, alien_width, alien_number, row_number):
# 创建一个外星人并加入群组
alien = Alien(sett, screen)
alien.x = alien_width + 2 * alien_width * alien_number
alien.rect.y = alien.rect.height + 2 * alien.rect.height * row_number
alien.rect.x = alien.x
aliens.add(alien)
def create_fleet(sett, screen, ship, aliens):
"""创建外星人群"""
# 创建一个外星人,并计算一行可容纳多少外星人
alien = Alien(sett, screen)
alien_width = alien.rect.width
alien_height = alien.rect.height
number_aliens_x = get_number_aliens_x(sett, alien_width)
number_rows = get_nuber_rows(sett, alien_height, ship.rect.height)
# 创建第一行外星人
for row_number in range(number_rows):
for alien_number in range(number_aliens_x):
create_alien(sett, screen, aliens, alien_width, alien_number, row_number)
def award_check(sett, stats):
if stats.bullet_award:
if stats.award_b == 3:
stats.bullet_award = False
sett.bullet_width = 3
stats.award_b += 1
def check_keydown(event, sett, screen, stats, ship, bullets):
if event.key == pygame.K_RIGHT:
ship.right_move = True
elif event.key == pygame.K_LEFT:
ship.left_move = True
elif event.key == pygame.K_SPACE:
if stats.bullet_award:
award_check(sett, stats)
fire_bullet(sett, screen, ship, bullets)
def check_keyup(event, ship):
if event.key == pygame.K_RIGHT:
ship.right_move = False
elif event.key == pygame.K_LEFT:
ship.left_move = False
def clear_recreate(sett, screen, ship, aliens, bullets):
"""清除屏幕重新开始"""
aliens.empty()
bullets.empty()
create_fleet(sett, screen, ship, aliens)
ship.center = screen.get_rect().centerx
def restart(sett, sb):
sett.init_dynamic()
sb.prep_score()
sb.prep_ship()
def check_play(sett, screen, stats, button, ship, sb, aliens, bullets, mouseX, mouseY):
if button.rect.collidepoint(mouseX, mouseY) and not stats.active:
pygame.mouse.set_visible(False)
stats.reset_stats()
stats.active = True
clear_recreate(sett, screen, ship, aliens, bullets)
restart(sett, sb)
def check_events(sett, screen, stats, button, ship, sb, aliens, bullets):
for event in pygame.event.get():
if event.type == pygame.QUIT:
with open("highScore.txt", 'w') as hs:
hs.write(str(stats.high_score))
sys.exit()
elif event.type == pygame.KEYDOWN:
check_keydown(event, sett, screen, stats, ship, bullets)
elif event.type == pygame.KEYUP:
check_keyup(event, ship)
elif event.type == pygame.MOUSEBUTTONDOWN:
mouse_X, mouse_Y = pygame.mouse.get_pos()
check_play(sett, screen, stats, button, ship, sb, aliens, bullets, mouse_X, mouse_Y)
def check_bullet_collision(sett, screen, stats, sb, ship, bullets, aliens):
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
if len(aliens) == 0:
create_fleet(sett, screen, ship, aliens)
sett.increase_speed()
if collisions:
for aliens in collisions.values():
stats.score += sett.alien_score * len(aliens)
sb.prep_score()
award(sett, stats)
def update_bullet(sett, screen, stats, sb, ship, bullets, aliens):
bullets.update()
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
check_bullet_collision(sett, screen, stats, sb, ship, bullets, aliens)
def update_screen(sett, screen, stats, sb, ship, aliens, bullets, button):
screen.fill(sett.bg_color)
ship.blitme()
sb.show_score()
aliens.draw(screen)
for bullet in bullets:
bullet.draw_bullet()
if not stats.active:
button.draw_button()
# 让最近绘制的屏幕可见
pygame.display.flip()
def change_fleet_dir(sett, aliens):
"""将整群外星人下移"""
for alien in aliens:
alien.rect.y += sett.alien_drop_speed
sett.fleet_direction *= -1
def check_fleet_edges(sett, aliens):
"""有一个外星人到达边缘"""
for alien in aliens.sprites():
if alien.check_edges():
change_fleet_dir(sett, aliens)
break
def alien_bottom(sett, stats, screen, ship, sb, aliens, bullets):
screen_rect = screen.get_rect()
for alien in aliens:
if alien.rect.bottom >= screen_rect.bottom:
ship_hit(sett, stats, screen, ship, sb, aliens, bullets)
def ship_hit(sett, stats, screen, ship, sb, aliens, bullets):
stats.life -= 1
if stats.life > 0:
clear_restart(sett, screen, ship, aliens, bullets)
sleep(0.5)
else:
pygame.mouse.set_visible(True)
stats.active = False
if stats.score > stats.high_score:
stats.high_score = stats.score
sb.prep_high()
sb.prep_ship()
def update_aliens(sett, stats, screen, ship, sb, aliens, bullets):
check_fleet_edges(sett, aliens)
aliens.update()
if pygame.sprite.spritecollideany(ship, aliens) or alien_bottom(sett, stats, screen, ship, sb, aliens, bullets):
ship_hit(sett, stats, screen, ship, sb, aliens, bullets)
def award(sett, stats):
if stats.score >= stats.award_level * stats.award_score:
stats. bullet_award = True
sett.bullet_width = sett.awared_width
stats.award_level += 1
stats.award_b = 0
stats.award_score *= sett.award_score_scale
# invasion.py
import pygame
import game_function as gf
from pygame.sprite import Group
from settings import Settings
from ship import Ship
from stats import Gamestats
from button import Button
from scoreboard import ScoreBoard
def run_game():
# 初始化游戏并创建一个屏幕对象
pygame.init()
sett = Settings()
stats = Gamestats(sett)
screen = pygame.display.set_mode(
(sett.screen_width, sett.screen_length)
)
pygame.display.set_caption("Thunder")
play_button = Button(sett, screen, 'PLAY')
ship = Ship(sett, screen)
bullets = Group()
aliens = Group()
sb = ScoreBoard(sett, screen, stats)
gf.create_fleet(sett, screen, ship, aliens)
# 开始游戏的主循环
while True:
gf.check_events(sett, screen, stats, play_button, ship, sb, aliens, bullets)
ship.update()
gf.update_bullet(sett, screen, stats, sb, ship, bullets, aliens)
gf.update_aliens(sett, stats, screen, ship, sb, aliens, bullets)
gf.update_screen(sett, screen, stats, sb, ship, aliens, bullets, play_button)
run_game()