源代码:
AlienInvasion/game at main · CrashBugger/AlienInvasion (github.com)
本文来自作者对《python编程-从入门到实践》的学习记录,刚刚入门python,小白一个,若有错误,欢迎大佬指出。
图片资源:
just nobibi,show me the code!话不多说,开始项目。
1.创建pygame窗口响应用户输出导包
import pygame
import sys
import settings
接下来
开始进行初步准备sssssssss
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 == pygame.QUIT:
sys.exit()
# 让最近的绘制的屏幕可见
pygame.display.flip()
- 其中pygame.init()方法初始化背景设置
- pygame.display.set_mode()创建一个名为screen的窗口对象,以后我们将在这里面绘制图形元素,需要传入一个元组,表示窗口的长高。screen是一个surface对象
- while循环控制游戏进行,for循环侦听事件,pygame.event.get()方法拿到事件列表,当检测到用户离开时,系统退出
-
display.flip()方法让最近绘制的屏幕可见
先上代码
def run_game():
# 初始化游戏并创建一个屏幕对象
pygame.init()
screen = pygame.display.set_mode((1200, 800))
pygame.display.set_caption("Alien Invasion")
# shezhibeijingse
bg_color = (230, 230, 230)
# 游戏主循环
while True:
# 监视键盘和鼠标时间
for event in pygame.event.get():
if event == pygame.QUIT:
sys.exit()
# 每次循环重新绘制屏幕
screen.fill(bg_color)
# 让最近的绘制的屏幕可见
pygame.display.flip()
- 创建一个元组bg_color存储RGB颜色,接下来在主循环中在screen中填充颜色,
这一步对前一步进行优化,创建一个settings类,用来保存我们的设置
class Settings():
def __init__(self):
self.screen_width = 1200
self.screen_height = 800
self.bg_color = (230, 230, 230)
接下来回到主方法创建这个类的实例进行设置
def run_game():
# 初始化游戏,并创建一个屏幕对象
pygame.init()
ai_settings = settings.Settings()
screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height))
pygame.display.set_caption("Ailien Invasion")
# bg_color = (230, 230, 230)
while True:
# 监听事件
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
# 每次循环重置屏幕
screen.fill(ai_settings.bg_color)
# 最近绘制的屏幕可见
pygame.display.flip()
4.创建ship类并绘制飞船
创建一个新的文件夹,命名为images,将ship.bmp放进去,接下来创建ship类
import pygame
class Ship():
def __init__(self, screen):
self.screen = screen
# 加载飞船图像并获取外接矩形
self.image = pygame.image.load("D:\PyCharm\Code\project\game\images\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)
- pygame可以像处理矩形一样处理游戏元素,处理rect对象,可以使用矩形中心xy坐标和矩形四角
- 要将飞船显示在中央,需要设置rect对象的center,centerx或centery;如果要将飞船与边缘对其,可以设置其top,bottom,left,right属性;如果要调整坐标,需要设置其x,y属性,注意原点在左上角。
- 在主方法中加入飞船
def run_game():
# 初始化游戏,并创建一个屏幕对象
pygame.init()
ai_settings = settings.Settings()
screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height))
pygame.display.set_caption("Ailien Invasion")
# 创建一艘飞船
ship = Ship(screen)
while True:
# 监听事件
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
# 每次循环重置屏幕
screen.fill(ai_settings.bg_color)
ship.blitme()
# 最近绘制的屏幕可见
pygame.display.flip()
5.重构模块geme_functions
- 我们将游戏中的各个控制方法抽离到一个专门的模块
- game_functions.py
import sys
import pygame
import ship
from game import settings
def check_events():
"""响应按键和鼠标事件"""
for event in pygame.event.get():
if event.type == pygame.QUIT:
sys.exit()
def update_screen(ai_settings: settings.Settings, screen, ship: ship.Ship):
"""更新屏幕上的图像,并切换到新屏幕"""
# 每次循环chonghuipingmu
screen.fill(ai_settings.bg_color)
ship.blitme()
# 最近绘制的屏幕可见
pygame.display.filp()
这些都是原本主循环中的方法,现在整合到一个模块
主方法在修改一下
先把刚创建的模块导包
import game_functions as gf
def run_game():
# 初始化游戏,并创建一个屏幕对象
pygame.init()
ai_settings = settings.Settings()
screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height))
pygame.display.set_caption("Ailien Invasion")
# 创建一艘飞船
ship = Ship(screen)
while True:
# 监听事件
gf.check_events()
# 每次循环重置屏幕
gf.update_screen(ai_settings, screen, ship)
6.驾驶飞船
先在ship类中新加一个属性moving_right=False,当检测到移动时,置位true,并新定义update方法,更新飞船位置
- Ship类
def __init__(self, screen):
self.screen = screen
# 加载飞船图像并获取外接矩形
self.image = pygame.image.load("D:\PyCharm\Code\project\game\images\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.moving_right = False
def blitme(self):
"""在指定位置绘制飞船"""
self.screen.blit(self.image, self.rect)
def update(self):
if self.moving_right:
self.rect.centerx += 1
我们在game_functions.py的check_events方法中加入检测用户键盘的代码
- game_functions.py
def check_events(ship: ship.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
当用按下键盘时,我们在event检测其type属性,若为keydown,再进一步判断是否为向右键,并将ship类中的moving——righ属性置为true,当玩家抬起右键时,检测到keyup,将其置为false,
并在主方法中不断调用ship类的update方法
- 主方法
def run_game():
# 初始化游戏,并创建一个屏幕对象
pygame.init()
ai_settings = settings.Settings()
screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height))
pygame.display.set_caption("Ailien Invasion")
# 创建一艘飞船
ship = Ship(screen)
while True:
# 监听事件
gf.check_events(ship)
# 更新
ship.update()
# 每次循环重置屏幕
gf.update_screen(ai_settings, screen, ship)
7.调整飞船速度并限制飞船移动边界
- 在settings类中新加属性self.ship_speed_factor用于控制飞船速度
- 接下来在ship的方法init方法传入settings对象,并在update方法中用他的属性修改坐标,因为rect只存储整数部分的值,所以为了精细控制速度,我们用float方法返回rect.centerx保存到center属性,并在update方法对center修改,最后再用center覆盖centerx。
- 判断边界只需要在update方法中判断当前rect的left属性和right属性是否超出边界。
Ship类
class Ship():
def __init__(self, screen, ai_settings: Settings):
self.screen = screen
# 加载飞船图像并获取外接矩形
self.image = pygame.image.load("D:\PyCharm\Code\project\game\images\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.moving_right = False
self.moving_left = False
# 飞船设置
self.ai_settings = ai_settings
# 在飞船的center属性中存储小数值
self.center = float(self.rect.centerx)
def blitme(self):
"""在指定位置绘制飞船"""
self.screen.blit(self.image, self.rect)
def update(self):
# 加上边界判断
if self.moving_right and self.rect.right < self.screen_rect.right:
self.center += self.ai_settings.ship_speed_factor
if self.moving_left and self.rect.left > 0:
self.center -= self.ai_settings.ship_speed_factor
# 覆盖
self.rect.centerx = self.center
8.重构check_functions
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: ship.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)
- 提取出两个方法,如图
bullet.py
import pygame
from pygame.sprite import Sprite
from game.settings import Settings
from game.ship import Ship
class Bullet(Sprite):
def __init__(self, ai_settings: Settings, screen, ship: Ship):
"""在飞船所在位置创建子d"""
super().__init__()
self.screen = screen
# 在(0.0)处创建一个子d矩形
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):
"""向上移动子d"""
# 更新y坐标
self.y -= self.speed_factor
# 覆盖
self.rect.y = self.y
def draw_bullet(self):
"""在屏幕上绘制子d"""
pygame.draw.rect(self.screen, self.color, self.rect)
- 我们继承sprite类,可以将子d编组,同时 *** 作编组中的元素。并传入settings对象ship对象和screen对象
- 用rect存储子d矩形,并设置其centerx和top属性,让它与飞船相同
- 跟上一个相同的原因,我们用float()精细调整子dy坐标
我们在主方法导包并创建Group()实例,并将其传入while循环中的三个方法
from pygame.sprite import Group
# 初始化游戏,并创建一个屏幕对象
pygame.init()
ai_settings = settings.Settings()
screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height))
pygame.display.set_caption("Ailien Invasion")
# 创建一艘飞船
ship = Ship(screen, ai_settings)
# 创建用于存储子d的编组
bullets = Group()
while True:
# 监听事件
gf.check_events(ship, bullets)
# 更新
ship.update()
# 更新子d位置
bullets.update()
# 每次循环重置屏幕
gf.update_screen(ai_settings, screen, ship, bullets)
- 编组的作用就是存储子d,并且当调用update()方法时,自动对里面的每个子d调用update方法,bullets.update()将被每颗子d调用
- 这里期望玩家按空格键可以完成开火
- 我们在functions对check_keydown方法加入对空格的判断若判断到,则在编组中新加入子d;并在update_screen方法中重绘子d,注意重绘子d应在screen.flip()方法调用之前
- 注意修改方法的参数
class Bullet(Sprite):
def __init__(self, ai_settings: Settings, screen, ship: Ship):
"""在飞船所在位置创建子d"""
super().__init__()
self.screen = screen
# 在(0.0)处创建一个子d矩形
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):
"""向上移动子d"""
# 更新y坐标
self.y -= self.speed_factor
# 覆盖
self.rect.y = self.y
def draw_bullet(self):
"""在屏幕上绘制子d"""
pygame.draw.rect(self.screen, self.color, self.rect)
- 成功开火后,有个问题,那些飞出屏幕外的子d,仍然留在内存中,这样越积越多,会让我们的游戏慢的龟爬一样,所以在主方法while循环中添加一个删除子d的方法,判断子d的bottom属性是否小于等于0,成立则从编组中删除掉
while True:
# 监听事件
gf.check_events(ai_settings, screen, ship, bullets)
# 更新
ship.update()
# 更新子d位置
bullets.update()
# 删除已经消失的子d
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
# 每次循环重置屏幕
gf.update_screen(ai_settings, screen, ship, bullets)
- 下来我们在对代码进行优化,创建update_bullets函数和fire_bullets来将原本开火和更新的代码抽离
- games_functions.py
-
def update_bullets(bullets: Bullet): """更新子d未位置""" # 更新位置 bullets.update() # 删除已经消失的子d for bullet in bullets.copy(): if bullet.rect.bottom <= 0: bullets.remove(bullet) def fire_bullets(ai_settings: Settings, screen: surface, ship: ship.Ship, bullets: Bullet): # 若子弹数量小于3,创建一颗新子弹, if len(bullets) < ai_settings.bullets_allowed: new_bullet = Bullet(ai_settings, screen, ship) bullets.add(new_bullet)
修改其中的keydown函数
-
11.创建外星人def check_keydown_events(event, ai_settings: Settings, screen, ship, bullets: Bullet): 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_bullets(ai_settings, screen, ship, bullets)
-
我们首先改变一下游戏退出的方式,按下Q键即可退出,在game_functions.py中的key_down方法加上下面一条
-
elif event.key == pygame.K_q: sys.exit(1)
创建外星人类
import pygame
from pygame import surface
from pygame.sprite import Sprite
from game.settings import Settings
class Alien(Sprite):
"""表示单个外星人的类"""
def __init__(self, ai_settings: Settings, screen: surface):
# 初始化外星人并设置其起始位置
super(Alien, self).__init__()
self.screen = screen
self.ai_settings = ai_settings
# 加载外星人图像,设置其rect属性
self.image = pygame.image.load(r"D:\PyCharm\Code\project\game\images\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)
- 讲解同ship类一样,不在赘述
- 接下来我们要将外星人在图像上画出来,需要在主方法中创建一个实例
-
# 创建外星人实例 alien = Alien(ai_settings, screen)
并在game_functions中修改update_screen方法
-
game_functions.py
def update_screen(ai_settings: Settings, screen, ship: ship.Ship, alien: Alien, bullets: Bullet): """更新屏幕上的图像,并切换到新屏幕""" # 每次循环重绘屏幕 screen.fill(ai_settings.bg_color) ship.blitme() # 绘制外星人 alien.blitme() # 重绘子d for bullet in bullets.sprites(): bullet.draw_bullet() # 最近绘制的屏幕可见 pygame.display.flip()
- 调用个blitme方法即可,注意还需要在flip方法调用之前
- 尝试成功,接下来我们要创建一行外星人
- 在game_functions.py中加入如下方法
- 在主方法中循环之前加入
-
# 创建外星人群 aliens = Group() gf.create_fleet(ai_settings, screen, aliens)
aliens是一个空的编组,前面介绍过,编组可以对其中的每个元素调用方法,并将它传入create_fleet()方法,这个方法在game_functions.py中定义
-
game_functions.py
def create_fleet(ai_settings: Settings, screen, aliens: Group):
"""创建外星人群"""
# 创建一个外星人,并计算一行可以容纳多少个外星人
alien: 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):
newalien = Alien(ai_settings, screen)
newalien.x = alien_width + 2 * alien_width * alien_number
newalien.rect.x = newalien.x
aliens.add(newalien)
- 首先计算一行可以容纳多少个外星人,间距我们设置为外星人的宽度,接下来在编组中加入新建的外星人
def create_fleet(ai_settings: Settings, screen, aliens: Group):
"""创建外星人群"""
alien = Alien(ai_settings, screen)
number_aliens_x = get_number_aliens(ai_settings, alien.rect.width, )
for alien_number in range(number_aliens_x):
create_alien(ai_settings, screen, aliens, alien_number)
def get_number_aliens(ai_settings: Settings, alien_width: int) -> int:
"""获取一行容纳的外星人数量"""
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: Group, 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)
将其中的几个功能提取出来
13.添加多行外星人game_functions.py
def get_number_rows(ai_settings: Settings, ship_height, alien_height) -> int:
"""计算能容纳多少行"""
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_fleet(ai_settings: Settings, screen, ship: ship.Ship, aliens: Group):
"""创建外星人群"""
alien = Alien(ai_settings, screen)
number_aliens_x = get_number_aliens(ai_settings, alien.rect.width)
number_rows = get_number_rows(ai_settings, ship.rect.height, alien.rect.height)
for alien_number in range(number_aliens_x):
for alien_row in range(number_rows):
create_alien(ai_settings, screen, aliens, alien_number, alien_row)
def create_alien(ai_settings, screen, aliens: Group, alien_number, row_number):
"""创建一个外星人并放在当前行"""
alien = Alien(ai_settings, screen)
alien_width = alien.rect.width
# 当前x
alien.x = alien_width + 2 * alien_width * alien_number
# 当前y
alien.rect.y = alien.rect.height + 2 * alien.rect.height * row_number
alien.rect.x = alien.x
aliens.add(alien)
- 修改create_fleet方法和create_alien方法
- create_alien中加入一个参数row_number,表示当前应该添加到第几行,并据此修改y坐标
- create_fleet方法中for循环新加一层层数循环
首先在settings.py中添加设置参数
- settings.py
# 外星人设置
self.alien_speed_factor = 0.5#外星人水平移动速度
self.fleet_drop_speed = 10#外星人竖直移动速度
self.fleet_direction = 1 #移动方向: 1为向右,-1向左
- Alien.py中加入下述代码
def update(self):
"""向左向右移动外星人"""
self.x += self.ai_settings.alien_speed_factor * self.ai_settings.fleet_direction
self.rect.x = self.x
def check_edges(self):
"""如果外星人到达边缘,返回True"""
screen_rect = self.screen.get_rect()
if self.rect.right >= screen_rect.right:
return True
elif self.rect.left <= 0:
return True
- game_functions.py中加入
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 check_fleet_edges(ai_settings: Settings, aliens):
"""有外星人到达边缘时采取相应的措施"""
for alien in aliens.sprites():
if alien.check_edges():
change_fleet_direction(ai_settings, aliens)
break
def update_aliens(ai_settings, aliens):
"""检查是否有外星人位于屏幕边缘,并更新调整外星人位置"""
check_fleet_edges(ai_settings, aliens)
aliens.update()
- update_aliens为我们对主方法提供的接口,上面两个为辅助函数
- 主方法while中调用update_aliens方法
# 更新aliens
gf.update_aliens(ai_settings, aliens)
15.射杀外星人并生成新的外星人群
我们需要调用pygame.sprite.groupcollied方法,这个方法检测两个编组中的元素是否重合,重合的话进行处理,并返回一个字典,每个键值对为子d-外星人。其中第一个True表示重合的话删除第一个编组中的元素,第二个true表示删除第二个编组中的元素。我们在更新子d位置时调用这个方法,并增加主方法中的参数调用
- 击杀外星人后,我们需要判断是否需要生成新的外星人,若编组为空,重新调用生成外星人的方法
- game_functions.py
def update_bullets(ai_settings, screen, ship, aliens: Alien, bullets: Sprite):
"""更新子d未位置"""
# 更新位置
bullets.update()
# 删除已经消失的子d
for bullet in bullets.copy():
if bullet.rect.bottom <= 0:
bullets.remove(bullet)
check_bullet_alien_collision(ai_settings, screen, ship, aliens, bullets)
def check_bullet_alien_collision(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)
16.Boom!!!飞船与外星人相撞
如果飞船与外星人相撞或者外星人到底部,我们让游戏重置,重新生成外星人,并将飞船居中
ship.py
def center_ship(self):
"""让飞船居中"""
self.center = self.screen_rect.centerx
- 我们还需要将飞船的命数减一,为此我们新建一个GameStatus类,用于记录游戏状态信息
- 在Settings.py中新加属性
-
self.ship_limit = 3
- GameStatus.py
class GameStatus(): """跟踪游戏的统计信息""" def __init__(self, ai_settings): """初始化统计的信息""" self.ai_settings = ai_settings self.reset_status() def reset_status(self): """初始化在游戏运行期间可能变化的信息""" self.ships_left = self.ai_settings.ship_limit
game_functions.py
def update_aliens(ai_settings, stats, screen, ship, bullets, aliens):
"""检查是否有外星人位于屏幕边缘,并更新调整外星人位置"""
check_fleet_edges(ai_settings, aliens)
aliens.update()
# 检测外星人和飞船是否相撞
if pygame.sprite.spritecollideany(ship, aliens):
ship_hit(ai_settings, stats, screen, ship, aliens, bullets)
# 检查外星人是否撞到底部
check_aliens_bottom(aliens, ai_settings, stats, screen, ship, bullets)
def ship_hit(ai_settings: Settings, status: GameStatus, screen, ship: ship.Ship, aliens, bullets):
"""响应外星人撞到飞船"""
# 将飞船数量ship_left减一
status.ships_left -= 1
# 清空外星人和子d
aliens.empty()
bullets.empty()
# 创建一群新的外星人,重置飞船
create_fleet(ai_settings, screen, ship, aliens)
ship.center_ship()
# 暂停
sleep(0.5)
def check_aliens_bottom(aliens: Alien, ai_settings: Settings, stats: GameStatus,
screen: Surface, ship: ship.Ship, bullets):
"""检查是否有外星人到达底部"""
screen_rect = screen.get_rect()
for alien in aliens:
if alien.rect.bottom >= screen_rect.bottom:
# 像飞船撞到飞船一样处理
ship_hit(ai_settings, stats, screen, ship, aliens, bullets)
break
我们调用update_aliens方法时,顺便检查一下飞船与外星人是否相撞,通过
pygame.sprite.spritecollideany(ship, aliens)
方法,第一个参数为一个飞船对象,第二个参数为一个编组,
17.游戏结束game_functions.py的__ini__t中加入
# 游戏刚启动处于活动状态
self.game_active = True
- 这个属性让我们可以再玩家命数耗尽后置为False退出游戏。
- 改动game_functions.py方法,加入判断
def ship_hit(ai_settings: Settings, status: GameStatus, screen, ship: ship.Ship, aliens, bullets):
"""响应外星人撞到飞船"""
# 将飞船数量ship_left减一
if status.ships_left > 0:
status.ships_left -= 1
# 清空外星人和子d
aliens.empty()
bullets.empty()
# 创建一群新的外星人,重置飞船
create_fleet(ai_settings, screen, ship, aliens)
ship.center_ship()
# 暂停
sleep(0.5)
else:
status.game_active = False
- 主方法的while循环改为
-
18.添加开始按钮while True: # 监听事件 gf.check_events(ai_settings, screen, ship, bullets) if stats.game_active: # 更新 ship.update() # 更新子d gf.update_bullets(ai_settings, screen, ship, aliens, bullets) # 更新aliens gf.update_aliens(ai_settings, stats, screen, ship, bullets, aliens) # 每次循环重置屏幕 gf.update_screen(ai_settings, screen, ship, aliens, bullets)
- button.py
import pygame.font
class Button():
def __init__(self, ai_settings: Settings, screen: Surface, 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)
# 创建按钮的rect对象,并使其居中
self.rect = pygame.Rect(0, 0, self.width, self.height)
self.rect.center = self.screen_rect.center
# 按钮标签只需创建一次
self.prep_msg(msg)
- font属性中我们调用pygame.font()方法,None表示用默认字体,48指的是字体字号。为了让按钮在屏幕居中,我们创建一个rect对象,并让他居中
- 接下来在新建一个方法prep_msg方法来处理按钮的渲染和绘制按钮的方法
def prep_msg(self, msg):
"""将msg渲染为图像,并将其在按钮上居中"""
self.msg_image = self.font.render(self, 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)
- 其中的font.render方法将存储在msg中的文本转换为图像,并将其存储在msg_image中,其中的True参数表示开启抗锯齿功能,可以让文字边缘更光滑。接下来最后调通过rect调整按钮位置。
- 接下来draw_button来绘制按钮。
- game_functions.py的update_screen中加入判断游戏活动状态的if语句,若不在活动状态,绘制一个按钮
-
def update_screen(ai_settings: Settings, screen, stats: GameStatus, ship: ship.Ship, aliens: Group, bullets: Bullet, play_button: Button): """更新屏幕上的图像,并切换到新屏幕""" # 每次循环重绘屏幕 screen.fill(ai_settings.bg_color) ship.blitme() # 绘制外星人 aliens.draw(screen) # 重绘子d for bullet in bullets.sprites(): bullet.draw_bullet() if not stats.game_active: play_button.draw_button() # 最近绘制的屏幕可见 pygame.display.flip()
主方法中创建按钮实例,并将其传入update_screen方法中
-
19.开始游戏或重置游戏# 创建play按钮 play_button = Button(ai_settings, screen, "Play")
- 在game_functions.py中加入监视与按钮相关的鼠标事件
-
def check_events(ai_settings, screen, ship, bullets, stats: GameStatus, play_button: Button): """响应按键和鼠标事件""" for event in pygame.event.get(): # 用户按下键盘 if 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.K_SPACE: fire_bullets(ai_settings, ) elif event.type == pygame.MOUSEBUTTONDOWN: # 若按下按钮 mouse_x, mouse_y = pygame.mouse.get_pos() check_play_button(stats, play_button, mouse_x, mouse_y) def check_play_button(stats: GameStatus, play_button: Button, mouse_x, mouse_y): """在玩家单击play时开始游戏""" if play_button.rect.collidepoint(mouse_x, mouse_y): stats.game_active = True
其中mouse.get_pos返回一个元组,表示鼠标点击的坐标,我们用play_button.rect.collidepoint方法判断是否与按钮坐标重合
-
接下来重置游戏,因为上面仅对第一次点击有用
-
再次修改check_lpay_button方法
-
def check_play_button(ai_settings, screen, stats: GameStatus, play_button: Button, mouse_x, mouse_y, aliens, ship, bullets): """在玩家单击play时开始游戏""" if play_button.rect.collidepoint(mouse_x, mouse_y): # 重置游戏并统计信息 stats.reset_status() stats.game_active = True # 清空外星人和子d的列表 bullets.empty() aliens.empty() # 创建外星人,并让飞船居中 create_fleet(ai_settings, screen, ship, aliens) ship.center_ship()
我们新加了这个方法的参数,不要忘了在调用这个方法的位置加上(pycharm中ctrl+alt+h可以查看调用次方法的方法)
-
现在还是有个问题,如果玩家在点击按钮后再次点击,会出现再次重置的情况,所以我们再加入对stats.game_active的判断,同时再让点击后光标不可见
-
def check_play_button(ai_settings, screen, stats: GameStatus, play_button: Button, mouse_x, mouse_y, aliens, ship, bullets): """在玩家单击play时开始游戏""" button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y) if button_clicked and not stats.game_active: #让光标不可见 pygame.mouse.set_visible(False) # 重置游戏并统计信息 stats.reset_status() stats.game_active = True # 清空外星人和子d的列表 bullets.empty() aliens.empty() # 创建外星人,并让飞船居中 create_fleet(ai_settings, screen, ship, aliens) ship.center_ship()
修改ship_hit方法,让游戏重置时,鼠标可见
-
20.加快游戏节奏def ship_hit(ai_settings: Settings, status: GameStatus, screen, ship: ship.Ship, aliens, bullets): """响应外星人撞到飞船""" # 将飞船数量ship_left减一 if status.ships_left > 0: status.ships_left -= 1 # 清空外星人和子d aliens.empty() bullets.empty() # 创建一群新的外星人,重置飞船 create_fleet(ai_settings, screen, ship, aliens) ship.center_ship() # 暂停 sleep(0.5) else: status.game_active = False pygame.mouse.set_visible(True)
- 我们修改Settings.py中的属性来加快游戏
-
class Settings(): def __init__(self): """ 初始化游戏时的静态设置""" self.screen_width = 1200 self.screen_height = 800 self.bg_color = (230, 230, 230) # 飞船设置 self.ship_speed_factor = 1.0 self.ship_limit = 3 # 子d设置 self.bullet_speed_factor = 0.7 self.bullet_width = 3 self.bullet_height = 15 self.bullet_color = 60, 60, 60 # 限制子d数量 self.bullets_allowed = 3 # 外星人设置 self.alien_speed_factor = 0.5 # 外星人水平移动速度 self.fleet_drop_speed = 10 # 外星人竖直移动速度 self.fleet_direction = 1 # 移动方向: 1为向右,-1向左 # 以什么样的速度加快游戏节奏 self.speedup_scale = 1.1 self.initialize_dynamic_settings() def initialize_dynamic_settings(self): """初始化游戏进行而变化的设置""" self.ship_speed_factor = 1.5 self.bullet_speed_factor = 3 self.alien_speed_factor = 1 self.fleet_direction = 1 def increase_speed(self): """提高速度设置""" self.ship_speed_factor *= self.speedup_scale self.alien_speed_factor *= self.speedup_scale self.alien_speed_factor *= self.speedup_scale
增加了一个倍率属性和两个方法
def check_play_button(ai_settings: Settings, screen, stats: GameStatus, play_button: Button, mouse_x, mouse_y, aliens, ship, bullets): """在玩家单击play时开始游戏""" button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y) if button_clicked and not stats.game_active: #重置游戏节奏 ai_settings.initialize_dynamic_settings() 。。。。。。。。。。。。。。。。。。。。。。。
-
接下来修改
game_functions.py中的check_bullet_alien_collision
def check_bullet_alien_collision(ai_settings: Settings, screen, ship, aliens, bullets):
# 检查是否有子d击中外星人
collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
# 更新外星人
if len(aliens) == 0:
bullets.empty()
#增加游戏节奏
ai_settings.increase_speed()
create_fleet(ai_settings, screen, ship, aliens)
- 但是现在还有个问题,我们重置游戏时游戏节奏没有重置
-
21.计分def check_play_button(ai_settings: Settings, screen, stats: GameStatus, play_button: Button, mouse_x, mouse_y, aliens, ship, bullets): """在玩家单击play时开始游戏""" button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y) if button_clicked and not stats.game_active: #重置游戏节奏 ai_settings.initialize_dynamic_settings()
- 新增game_status.py中GameStatus类中的属性
-
def reset_status(self): """初始化在游戏运行期间可能变化的信息""" self.ships_left = self.ai_settings.ship_limit self.score = 0
新建scoreboard.py模块,并创建Scoreboard类:
-
22.创建记分牌class Scoreboard(): """显示得分信息的类""" def __init__(self, ai_settings: Settings, screen: Surface, status: GameStatus): """初始化得分涉及的属性""" self.screen = screen self.screen_rect = screen.get_rect() self.ai_settings = ai_settings self.status = status # 显示得分时的字体设置 self.text_color = (30, 30, 30) self.font = pygame.font.SysFont(None, 48) # 准备初始得分图像 self.prep_score() def prep_score(self): """将得分转换为渲染图像""" score_str = str(self.status.score) self.score_image = self.font.render(score_str, True, self.text_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)
- 在主方法中循环前创建得分牌实例
-
# 创建一个记分牌 sb = Scoreboard(ai_settings, screen, stats)
并将其传入循环中的update_screen方法中
-
game_functions.py
-
def update_screen(ai_settings: Settings, screen, stats: GameStatus, ship: ship.Ship, aliens: Group, bullets: Bullet, play_button: Button, sb: Scoreboard): """更新屏幕上的图像,并切换到新屏幕""" 。。。。。。。。。。。。。。 # 显示得分 sb.show_score() # 最近绘制的屏幕可见 pygame.display.flip()
接下来我们开始增加分数
-
settings.py中加入分数设置
-
def initialize_dynamic_settings(self): """初始化游戏进行而变化的设置""" self.ship_speed_factor = 1.5 self.bullet_speed_factor = 0.7 self.alien_speed_factor = 0.7 self.fleet_direction = 1 # 计分 self.alien_points = 50
- game_functions.py中修改方法
def check_bullet_alien_collision(ai_settings: Settings, screen, ship, aliens, bullets, stats: GameStatus, sb: Scoreboard): # 检查是否有子d击中外星人 collisions = pygame.sprite.groupcollide(bullets, aliens, True, True) #增加分数 if collisions: for aliens in collisions.values(): stats.score += ai_settings.alien_points * len(aliens) sb.prep_score() # 更新外星人 if len(aliens) == 0: bullets.empty() # 增加游戏节奏 ai_settings.increase_speed() create_fleet(ai_settings, screen, ship, aliens)
其中collisions是一个字典,我们拿到他的值,这个值是一个列表,其中包括我们一颗子d打掉的全部飞船(不排除一箭双雕),所以求出列表长度来算分
-
不要忘了在形参中加入新的值并修改调用此方法传入的参数
-
接下来我们设置游戏难度提高后的点数
-
settings.py
-
class Settings(): def __init__(self): 。。。。。。。。。。。。。。 # 点数提高速度 self.score_scale = 1.5 def increase_speed(self): 。。。。。。。。。。。。。。 """提高点数""" self.alien_points = int(self.alien_points * self.score_scale)
为了让记分牌跟以前那种街机风格的记分牌一样,每三位用“,”隔开,我们修改prep_score方法
-
scoreboard.py
-
def prep_score(self): """将得分转换为渲染图像""" # 将得分圆整 rounded_score = int(round(self.status.score, -1)) score_str = "{:,}".format(rounded_score) # score_str = str(self.status.score)删去 self.score_image = self.font.render(score_str, True, self.text_color) # 将得分显示在屏幕右上角 self.score_rect = self.score_image.get_rect() self.score_rect.right = self.screen_rect.right - 20 self.score_rect.top = 20
其中round()中的-1表示我们向10取整
- 23.显示最高得分
- scoreboard.py
-
def __init__(self, ai_settings: Settings, screen: Surface, status: GameStatus): """初始化得分涉及的属性""" 。。。。。。。。。。。。 self.prep_high_score() def show_score(self): """在屏幕上显示得分""" 。。。。。。。。。。。。。。。。。 self.screen.blit(self.high_score_image, self.high_score_rect) def prep_high_score(self): """将最高得分转换为渲染的图像""" high_score = int(round(self.status.high_score, -1)) high_score_str = "{:,}".format(high_score) self.high_score_image = self.font.render(high_score_str, True, self.text_color, self.ai_settings.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.score_rect.top
接下来我们判断是否诞生了最高分
-
game_functions.py加入
-
def check_high_score(stats: GameStatus, sb: Scoreboard): """检查是否诞生了最高分""" if stats.high_score < stats.score: stats.high_score = stats.score sb.prep_high_score()
我们需要在check_bullet_collision中调用分数判断
24、显示等级def check_bullet_alien_collision(ai_settings: Settings, screen, ship, aliens, bullets, stats: GameStatus, sb: Scoreboard): # 检查是否有子d击中外星人 collisions = pygame.sprite.groupcollide(bullets, aliens, True, True) # 增加分数 if collisions: for aliens in collisions.values(): stats.score += ai_settings.alien_points * len(aliens) sb.prep_score() check_high_score(stats, sb) 。。。。。。。。。。。。。。。
- 增加属性:
- game_status.py
def reset_status(self): """初始化在游戏运行期间可能变化的信息""" 。。。。。。。。。。。。。 self.level = 1
scoreboard.py
-
def __init__(self, ai_settings: Settings, screen: Surface, status: GameStatus): """初始化得分涉及的属性""" 。。。。。。。。。。。。。。。。 self.prep_level() def show_score(self): """在屏幕上显示得分""" 。。。。。。。。。。。。。。。。。。 self.screen.blit(self.level_image, self.level_rect) def prep_level(self): """将等级渲染为图像""" self.level_image self.level_image = self.font.render(str(self.status.level), True, self.text_color, self.ai_settings.bg_color) """将等级放在得分下方""" self.level_rect = self.level_image.get_rect() self.level_rect.right = self.score_rect.right self.level_rect.top = self.score_rect.bottom + 10
game_functions.py中修改
-
def check_bullet_alien_collision(ai_settings: Settings, screen, ship, aliens, bullets, stats: GameStatus,sb: Scoreboard): 。。。。。。。。。。。。。。。。。。。。。。。。。 # 更新外星人 if len(aliens) == 0: bullets.empty() # 增加游戏节奏 ai_settings.increase_speed() # 如果外星人都被消灭,就提高一个等级 stats.level += 1 sb.prep_level() create_fleet(ai_settings, screen, ship, aliens)
为了每次点击play按钮时重置记分牌,我们修改
-
def check_play_button(ai_settings: Settings, screen, stats: GameStatus, play_button: Button, mouse_x, mouse_y, aliens, ship, bullets, sb: Scoreboard, ): """在玩家单击play时开始游戏""" ........................................... # 重置得分牌图像 sb.prep_score() sb.prep_high_score() sb.prep_level() # 清空外星人和子d的列表 bullets.empty() aliens.empty() # 创建外星人,并让飞船居中 create_fleet(ai_settings, screen, ship, aliens) ship.center_ship()
我们新传进来了个参数sb(ScoreBoard类)
- 25.显示剩余飞船
- ship.py
-
class Ship(Sprite): def __init__(self, screen, ai_settings: Settings): """初始化飞船,并设置其起始位置""" super(Ship, self).__init__() 。。。。。。。。。。。。。。。。。。。。。。。。。。。。
让飞船继承sprite类,这样我们可以加入编组中管理
-
scoreboard.py类修改
-
class Scoreboard(): """显示得分信息的类""" def __init__(self, ai_settings: Settings, screen: Surface, status: GameStatus): """初始化得分涉及的属性""" 。。。。。。。。。。。。。。 self.prep_ships() def show_score(self): """在屏幕上显示得分""" 。。。。。。。。。。。。。。。。。。 self.ships.draw(self.screen) def prep_ships(self): """显示还剩下多少飞船""" self.ships = Group() for ship_number in range(self.status.ships_left): ship = Ship(self.screen, self.ai_settings) ship.rect.x = 10 + ship_number * ship.rect.width ship.rect.y = 10 self.ships.add(ship)
game_functions.py修改,调用prep_ships方法
-
def check_play_button(ai_settings: Settings, screen, stats: GameStatus, play_button: Button, mouse_x, mouse_y, aliens, ship, bullets, sb: Scoreboard, ): """在玩家单击play时开始游戏""" 。。。。。。。。。。。。。。。。。。 # 重置得分牌图像 sb.prep_score() sb.prep_high_score() sb.prep_level() sb.prep_ships() # 清空外星人和子d的列表 bullets.empty() aliens.empty() # 创建外星人,并让飞船居中 create_fleet(ai_settings, screen, ship, aliens) ship.center_ship()
接下来我们在飞船撞到外星人时更新ships编组
-
def ship_hit(ai_settings: Settings, status: GameStatus, screen, ship: ship.Ship, aliens, bullets, sb: Scoreboard): """响应外星人撞到飞船""" # 将飞船数量ship_left减一 if status.ships_left > 0: status.ships_left -= 1 # 更新记分牌 sb.prep_ships() 。。。。。。。。。。。。。。。。。。
我们又加了一个sb参数,按ctrl+alt+h查看调用此方法的函数,依次修改函数形参
-
最终结果
完结撒花,本来没想到需要这么多字,打的我手疼(哭)。。。。没有辛劳也有苦劳,祝点赞的各位看官bug少少,头发多多~~~~
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)