Loading... # 背景部分 --- ## 创建Pygame窗口以及响应用户输入 ```python # 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() ``` 1. 首先我们导入模块`pygame`和`sys`。sys用于退出游戏 2. 游戏以函数`run_game()`开头。 3. `pygame.init()`用于初始化游戏背景。 4. `pygame.display.set_mode()`用于创建一个名为screen的显示窗口。实参(1200,700)是一个元组,指定游戏窗口的尺寸。 5. 对象screen是一个surface。在Pygame中,surface是屏幕的一部分,用于显示游戏元素(比如外星人、飞船),游戏中每个元素都是一个surface。激活游戏的动画循环后,每经过一次循环都将重新绘制这个surface。 6. 为访问Pygame侦听到的时间,我们使用方法`pygame.event.get()`。所有的<b>键盘和鼠标事件</b>都将促使for循环运行。比如玩家点击窗口的关闭按钮时,将检测到pygame.QUIT事件,我们就调用`sys.exit()`来退出游戏。 7. `pygame.display.flip()`命令Pygame让最近绘制的屏幕可见。它在每次执行while循环时都会绘制一个空屏幕,并擦去旧屏幕。 ## 绘制背景色 ```python # 在while循环中加入如下语句: screen.fill((230,230,230)) ``` Pygame中,颜色是以RGB值表示的。 ## 创建设置类 ```python #settings.py class Settings(): """存储游戏所有设置的类""" def __init__(self): """初始化游戏的设置""" # 屏幕设置 self.screen_width = 1200 self.screen_length = 700 self.bg_color = (230, 230, 230) ``` 将所有游戏的设置存储在这个类中, 则invasion.py可修改: ```python 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() ``` <br></br> # 飞船部分 --- ## 添加飞船图像 就选用书配套的素材吧 ## 创建Ship类 ```python 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(): ```python 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() ``` 运行后结果: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190717163003208.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2lyaW1za3k=,size_16,color_FFFFFF,t_70#vwid=1502&vhei=915) # 重构:game_function模块 --- ## 函数check_events() 我们把管理事件的代码移到一个名为check_events()的函数里,以简化run_game() ```python #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中 ```python def update_screen(sett, screen, ship): screen.fill(sett.bg_color) ship.blitme() # 让最近绘制的屏幕可见 pygame.display.flip() ``` <br></br> <br></br> # 飞船移动部分 --- ## 响应按键 每当用户按键时,都在Pygame里注册一个<b>事件</b>。事件都是通过方法`pygame.event.get()`获取的,因此在函数check_events()中,我们需要制定检查哪些类型的事件。 每次按键都被注册一个`KEYDOWN`事件。检测到该事件后,我们需要检查是否按下了特定的键,执行特定的操作。比如按下右键后,要让飞船向右移动。 ```python 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事件实现持续移动。 ```python # ship.py def __init__(self, screen): ... self.right_move = False def update(self): if self.right_move: self.rect.centerx += 1 ``` 在飞船的类内初始化时多添了一个属性<b>移动标志变量</b>`right_move` 多添了一个方法`update()`,用于检查该标志变量,实现飞船属性更新:这个变量为True时,飞船就会向右移动。 而这个标志变量会<b>因KEYDOWN变为True,因KEYUP变为False</b>,以此来实现持续移动 同时,要在invasion.py的while循环里调用update()方法: ```python # invasion.py while True: gf.check_events(ship) ship.update() gf.update_screen(sett, screen, ship) ``` ## 左右移动 只需照着向右移动就能做出向左移动 ## 调整飞行速度 每次执行while循环,飞船最多移动1像素。但可以在settings模块里加入属性`ship_speed`,用于控制飞船的速度。 ```python # 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中修改: ```python 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语句判断飞船是否将飞出框外。 ```python # 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() ```python # 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) ``` <br></br> # 子弹部分 ## 添加子弹设置 在setting.py中添加新类Bullet所需的值: ```python def __init__(self): """初始化游戏的设置""" # 屏幕设置 ... # 子弹设置 self.bullet_speed = 1 self.bullet_width = 3 self.bullet_height = 15 self.bullet_color = (60, 60, 60) ``` ## 创建Bullet类 ```python # 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()`类从空白开始创建一个矩形。创建这个类的实例时,必须提供矩形<b>左上角的x坐标和y坐标</b>,还有宽度和高度。我们先在(0,0)处创建一个矩形,并在接下来放在正确的位置,这个位置取决于飞船的位置。 接下来编写`update()`和`draw_bullet`方法 ```python 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类 类似于列表,但提供了有助于游戏开发的功能。在主循环中,我们使用这个编组在屏幕上绘制子弹,更新每一个子弹的位置。 ```python 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。 <b>当你对编组调用update()时,编组将自动对每一个"精灵"调用update()</b>,即对每一个子弹。 ## 开火 因为只有在按下空格键时飞船才会开火,所以我们只需修改`check_keydown_events()`而不用修改keyup ```python # 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的子弹,并删除它们。 ```python # 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) ``` - <b>在for循环中,不应从列表或编组中删除条目,因此必须是遍历<u>编组的副本</u></b>,故需要调用方法`copy()`,返回一个编组的副本。 - 输出编组的长度,即有效子弹的数量,是为了显示子弹的数量,核实已消失的子弹确实被删除了。 子弹效果如图: ![在这里插入图片描述](https://img-blog.csdnimg.cn/2019071722263984.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2lyaW1za3k=,size_16,color_FFFFFF,t_70#vwid=1502&vhei=915) ## 限制子弹数量 多数同类型游戏里面都会有对子弹数量的限制,鼓励玩家有目标地射击。 我们在此限制子弹最大数量为4. 首先在Setting类里设置允许的最大子弹数: ```python #setting.py class Settings(): """存储游戏所有设置的类""" def __init__(self): """初始化游戏的设置""" # 屏幕设置 ... # 子弹设置 ... self.bullet_allowed = 4 ``` 在check_keydown_event()中检测到空格前,添加if语句判断子弹数量(群组长度)是否已经超过最大限制。 ```python # 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()`里: ```python # 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)) ``` 故主循环里的代码可简化: ```python # 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()`函数里: ```python # 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) ``` # 外星人部分 <br></br> ## 创建Alien类 ```python # 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实例 ```python # 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() ``` ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190718103249589.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2lyaW1za3k=,size_16,color_FFFFFF,t_70#vwid=1502&vhei=915) ## 创建一群外星人 ### 确定一行可以容纳多少外星人 我们要根据屏幕水平宽度确定一行可容纳多少外星人。我们要在屏幕两边留下边距,把它设置为外星人图像的宽度。所以放置外星人的水平空间为: > available_space_x = sett.screen_width - (2 * alien_width) <br> 外星人之间还得留下空间,设置为一个外星人的宽度。因此一行可容纳的外星人数量: > number_aliens_x = available_space_x / (2 * alien_width) ### 创建多行外星人 为创建一行外星人,首先在invasion.py中创建一个名为aliens的空编组,用于存储全部外星人,再调用game_function.py中的创建外星人群的函数: ```python # 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) ``` ```python # 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) ... # 让最近绘制的屏幕可见 ... ``` 效果如图: ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190718110516394.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2lyaW1za3k=,size_16,color_FFFFFF,t_70#vwid=1502&vhei=915) ## 重构create_fleet() 为create_fleet()新添两个函数`create_alien()`和`get_number_aliens_x()` ```python # 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) ``` ## 添加行 要创建外星人群,需要计算屏幕可容纳多少行,并对创建一行外星人的循环重复相应的次数。为计算可容纳的行数,我们<u>将屏幕高度减去第一行的外星人的上边距(外星人高度)、飞船的高度以及最初外星人高度加上外星人边距:</u> > available_space_y = sett.screen_height - 3*alien_height - ship_height 这样可以给飞船上方留出一定空白区域。 每行下方都要留出一定的空白区域,并将其设置为外星人的高度。为计算可容纳的行数,我们将可用垂直空间除以外星人高度的两倍: > number_rows = available_space_y /(2 * alien_height) ```python # 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) ``` ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190718210645558.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2lyaW1za3k=,size_16,color_FFFFFF,t_70#vwid=1502&vhei=915) # 移动外星人 ## 让外星人向右移动 为移动外星人,我们将使用alien.py中的方法update(),且对外星人群中的每个外星人都调用它。 首先添加一个外星人移动速度的设置: ```python # setting.py class Settings(): """存储游戏所有设置的类""" def __init__(self): """初始化游戏的设置""" # 屏幕设置 ... # 子弹设置 ... self.alien_speed = 1 ``` 然后在Alien类里实现update(): ```python # alien.py def update(self): self.x += self.setting.alien_speed self.rect.x = self.x ``` 接着在game_function.py里编写`update_aliens()` ```python # game_function.py def update_aliens(aliens): aliens.update() ``` aliens编组将自动对每一个外星人调用update()。 在主循环里调用update_aliens(aliens): ```python 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) ``` ## 创建表示外星人移动方向的设置 让外星人在撞到屏幕右边缘后会向下移动,再向左移动,代码如下: ```python # setting.py self.alien_speed = 1 self.alien_drop_speed = 10 # 下降速度 self.fleet_direction = 1 # 1表示向右,-1表示向左, 可以直接作为速度的系数用于坐标运算 ``` ## 检查外星人是否撞到边缘 检查外星人是否撞到边缘,为类Alien编写方法`check_edges()`: ```python # 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(): ```python def update(self): self.x += self.setting.alien_speed * self.setting.fleet_direction self.rect.x = self.x ``` ## 向下移动并改变移动方向 有<b>一个外星人</b>到达屏幕边缘时,需要将<b>整群外星人</b>下移并转向。所以我们需要对game_function.py做大修改,因为我们需要检查每一个外星人是不是已经到了边缘。为此我们编写`check_fleet_edge()`和`change_fleet_dir()` ```python # 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()的参数: ```python gf.update_aliens(sett, aliens) ``` <br></br> # 射杀外星人 ## 检测子弹与外星人的碰撞 子弹击中外星人时,我们要让外星人消失。为此我们需要在更新子弹位置后判断其是否碰撞。 我们用`sprite.groupcollide()`方法来检测两个群组的成员是否有碰撞。 它将每颗子弹的rect同每个外星人的rect进行比较,并<b>返回一个字典</b>,其中包含发生碰撞的子弹和外星人。在这个字典中,每个键都是一颗子弹,而对应的值都是被击中的外星人。(这个字典在之后计分要用到) ```python # 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。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190719102209460.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2lyaW1za3k=,size_16,color_FFFFFF,t_70#vwid=1502&vhei=915) ## 生成新的外星人群 当一个外星人群被消灭后,应该再出现另一群外星人。 我们先检查编组aliens是否为空,如果为空,就调用create_fleet()。我们将在update_bullets()中进行这个检查,<b>因为外星人都是在这里被消灭的</b> ```python # 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() ```python # 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) ``` # 结束游戏 需要添加失败事件:外星人撞到飞船,或者有外星人降到屏幕底端,飞船将会被摧毁,玩家用光生命树后游戏结束。 ## 检测飞船与外星人碰撞 ```python # 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 : ```python # 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的实例 ```python sett = Settings() stats = Gamestats(sett) ``` 接着编写飞船碰撞时的响应: ```python # 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()` ```python # 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,以便在玩家的飞船用完后结束游戏: ```python # 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 ```python # 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中的代码 ```python 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类 ```python # 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中创建 ```python ... 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,以便在游戏处于非活动状态时显示按钮 ```python def update_screen(sett, screen, stats, ship, aliens, bullets, button): ... if not stats.active: button.draw_button() # 让最近绘制的屏幕可见 pygame.display.flip() ``` <b>一定要把draw放在flip前面</b>,这样才能让绘制完所有其他元素之后再绘制按钮,然后切换到新屏幕。 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190720103745320.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2lyaW1za3k=,size_16,color_FFFFFF,t_70#vwid=1502&vhei=915) ## 开始游戏 在按下按钮时开始新游戏,需要对鼠标事件进行监视。 在game_function.py中添加如下代码: ```python 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按钮。每次单击它都应该重置整个游戏,重置统计信息,删除现有的外星人和子弹,创建新的外星人,让飞船居中。 ```python 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条件: ```python def check_play(sett, screen, stats, button, ship, aliens, bullets, mouseX, mouseY): if button.rect.collidepoint(mouseX, mouseY) and not stats.active: ... ``` ## 隐藏光标 在点击Play后的游戏活动状态,鼠标光标应该被隐藏 ```python 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 ``` 并在游戏结束后重现它: ```python 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里的设置属性分为静态和动态两部分。 ```python 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()` ```python def increase_speed(self): self.alien_speed *= self.speed_scale self.bullet_speed *= self.speed_scale self.ship_speed *= self.speed_scale ``` 并在每消灭一群外星人时调用一次这个方法: ```python 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() ``` ## 重置速度 每次开始新游戏时,速度都要重置一次 ```python 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() ``` <br></br> # 记分 在stats类里添加一个属性记录得分 ```python 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: ```python 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) ``` ## 创建记分牌实例 ```python ... 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()` ## 得分 击杀外星人后要增加分数。只需要检查子弹击中外星人时<b>返回的字典(collision)</b>即可. 我们现在setting中设置一个外星人的得分。 ```python def __init__(self): """初始化游戏的设置""" ... self.alien_score = 50 ``` 接着在`check_bullet_collision()`中检查字典。<b>(这个字典的键是一颗子弹,值是被这颗子弹击中的外星人列表)</b> ```python 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中增加一个得分提升的幅度属性 ```python self.score_scale = 1.5 ``` 在游戏难度提升时,即速度提高时,修改setting的属性alien_score (因为alien_score会变动,所以要把这个属性分类为动态,使其在动态初始化方法中被赋初值) ```python 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的整倍数。我们可以把得分圆整。 ```python # 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中增加一个属性最高分,并将其展示在屏幕顶端中央。 但是为了让数据保存,这个最高分存储在外部文件中,所以每次都需要从外部文件读入: ```python 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()) ``` 每当一场游戏结束后,都要更新最高分: ```python 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 ``` 每次关闭前都要在外部文件更新最高分: ```python 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() ... ``` 接着要在最顶端显示最高分: ```python # 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()`: ```python 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()的地方都要修改参数 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190720231853369.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2lyaW1za3k=,size_16,color_FFFFFF,t_70#vwid=1502&vhei=915) ## 奖励子弹 我们新增一个机制,当玩家分数达到一定程度后,我们将接下来的3发子弹的宽度提高100倍,提高消灭外星人的效率。 首先,现在stats.py中设置奖励标准: ```python 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`: ```python 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中编写判断函数: ```python 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。因为子弹是在按下空格后发射,所以我们可以在检测空格事件的函数中实现: ```python 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(): ```python 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) ``` ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190721111827818.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2lyaW1za3k=,size_16,color_FFFFFF,t_70#vwid=1502&vhei=915) # 左上角显示剩余生命 最后,我们来显示玩家还剩多少艘飞船,但用的是图形而不是数字。 首先,需要让Ship继承Sprite,以便创建飞船编组: ```python import pygame from pygame.sprite import Sprite class Ship(Sprite): def __init__(self, sett, screen): ... super().__init__() ``` 接着在scoreboard. py 中,创建一个可供显示的飞船编组。 ```python 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()中进行: ```python 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() ```python def ship_hit(sett, stats, screen, ship, sb, aliens, bullets): stats.life -= 1 if stats.life > 0: ... else: .... sb.prep_ship() ``` - 别忘了对对相关函数的参数列表修改 ![在这里插入图片描述](https://img-blog.csdnimg.cn/20190721115149934.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L2lyaW1za3k=,size_16,color_FFFFFF,t_70#vwid=1502&vhei=915) # 最后的重构 - 将清屏和重新创建舰队的代码编写为一个函数`clear_recreate()` - 将点击PLAY按钮后的分数板和动态设置重置的函数整合为`restart()` - 将scoreboard .py中的__init__()调用的prep方法整合 最终代码: ```python # 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 ``` ```python # 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 ``` ```python # 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) ``` ```python # 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) ``` ```python # 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 ``` ```python # 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) ``` ```python # 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) ``` ```python # 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 ``` ```python # 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() ``` 最后修改:2021 年 08 月 01 日 07 : 52 PM © 允许规范转载