目录

  • 安装Pygame
  • 创建Pygame窗口以及响应用户输入
  • 设置背景色
  • 创建设置类
  • 添加飞船图像
  • 创建ship类
  • 重构:模块game_functions
  • 函数check_events()
  • 函数update_screen()
  • 驾驶飞船
  • 响应按键
  • 允许不断移动
  • 左右移动
  • 调整飞船的速度
  • 限制飞船的活动范围
  • 重构check_events()
  • 射击
  • 添加子弹设置
  • 创建bullet类
  • 将子弹存储到编组中
  • 开火
  • 删除已经消失的子弹
  • 限制子弹的数量
  • 创建函数update_bullets()
  • 创建函数fire_bullet()
  • 外星人
  • 创建Alien类
  • 创建Alien实例
  • 让外星人出现在屏幕上
  • 创建一群外星人
  • 确定一行可容纳多少个外星人
  • 创建多行外星人
  • 重构create_fleet()
  • 添加行
  • 让外星人群移动
  • 向右移动的外星人
  • 创建表示外星人移动方向的设置
  • 检查外星人是否撞到屏幕边缘
  • 向下移动外星人群并改变移动方向
  • 射杀外星人
  • 检测外星人与子弹的碰撞
  • 生成新的外星人群
  • 重构update_bullets()
  • 结束游戏
  • 检测外星人和飞船碰撞
  • 响应外星人和飞船碰撞
  • 有外星人到达屏幕底部
  • 游戏结束
  • 确定应运行游戏的那些部分
  • 后记
  • 安装Pygame

    终端输入:
    pip install Pygame
    #开始游戏项目

    创建Pygame窗口以及响应用户输入

    首先:创建一个空的Pygame窗口。

    import sys
    import pygame
    
    
    def run_game():
        pygame.init()
        screen = pygame.display.set_mode((1200, 800))
        pygame.display.set_caption("Alien Invasion")
        while True:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    sys.exit()
            pygame.display.flip()
    
    
    run_game()
    

    理解:

    1. pygame.init()初始化背景设置,让Pygame能够正确地工作。
    2. Pygame.display.set_mode()来创建一个名为screen的显示窗口。每经过一次循环都将自动重绘这个surface(游戏中的元素,外星人,飞船等。)
    3. while循环包含一个事件循环以及管理屏幕更新的代码。事件是用户玩游戏时的操作,如按键或移动鼠标。为让程序响应事件,我们编写一个事件循环,用于监听事件,并根据发生的事件执行相应的任务。
    4. Pygame.QUIT,用于检测当用户单击关闭按钮时,调用sys.exit()来退出程序。
    5. pygame.display.flip(),命令pygame让最近绘制的屏幕可见。在这里,它在每次执行while循环时都绘制一个空屏幕,并擦去旧屏幕,使只有新屏幕可见。在我们移动游戏元素时,pygame.display.flip()将不断更新屏幕,以显示元素的新位置,并在原来的位置隐藏元素,从而营造平滑移动的效果。

    设置背景色

    alien_invasion.py
    
    import sys
    import pygame
    
    
    def run_game():
        pygame.init()
        screen = pygame.display.set_mode((1200, 800))
        pygame.display.set_caption("Alien Invasion")
        bg_color = (230,230,230)
        # 开始游戏主循环
        while True:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    sys.exit()
            screen.fill(bg_color)
        # 让最近绘制的屏幕可见
            pygame.display.flip()
    
    
    run_game()
    

    添加了一个screen.fill(bg_color),用于填充颜色

    创建设置类

    每次给游戏添加新功能时,通常也将引入一些新设置。下面编写一个名为settings的模块,其中包含一个名为settings的类,用于将所有设置存储在一个地方,以免在代码中到处添加设置。
    这样我们就能传递一个设置对象,而不是众多不同的设置。
    另外,这让函数调用更简单,且在项目增大时修改游戏的外观更容易:要修改游戏,只需要修改settings.py中的一些值。

    settings.py
    
    class Settings():
        def __init__(self):
            self.screen_width = 1200
            self.screen_height = 800
            self.bg_color = (230, 230, 230)
    

    修改原来的alien_invasion.py文件为:

    alien_invasion.py
    
    import sys
    import pygame
    from settings import Settings
    
    
    def run_game():
        pygame.init()
        ai_settings = Settings()
        screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height))
        pygame.display.set_caption("Alien Invasion")
        # 开始游戏主循环
        while True:
            for event in pygame.event.get():
                if event.type == pygame.QUIT:
                    sys.exit()
            screen.fill(ai_settings.bg_color)
        # 让最近绘制的屏幕可见
            pygame.display.flip()
    
    
    run_game()
    

    添加飞船图像

    在网站http://pixabay.com/中下载飞船图像,使用.bmp结尾的图像,(这样的图片不用下载图像库便可以使用)。
    大多图片为.jpg,.png,.gif格式。但可以使用Photoshop,GIMP和paint等工具将其转换为位图。
    在项目的文件夹下创建一个images的文件夹,并将图片ship.bmp放进去。

    创建ship类

    选择用于表示飞船的图像后,需要将其显示到屏幕上。我们将创建一个名为ship的模块,其中包含ship类,它负责管理飞船的大部分行为。

    ship.py
    
    import pygame
    class Ship():
        def __init__(self, screen):
            self.screen = screen
            self.image = pygame.image.load('images/ship.png')
            self.rect = self.image.get_rect()
            self.screen_rect = screen.get_rect()
            self.rect.centerx = self.screen_rect.centerx
            self.rect.bottom = self.screen_rect.bottom
    
        def blitme(self):
    
            self.screen.blit(self.image, self.rect)
    

    重构:模块game_functions

    在大型项目中,经常需要在添加新代码前重构既有代码。重构旨在简化既有代码的结构,使其更容易扩展。
    在本节中,我们将创建一个名为game_functions的新模块,它将存储大量让游戏运行的函数。通过创建模块game_functions,可避免alien_invasion.py太长,使其逻辑混乱。

    函数check_events()

    我们首先把管理事件的代码移到一个名为check_events()的函数中,以简化run_game()并隔离事件管理循环。通过隔离事件循环,可将事件管理与游戏的其他方面(如更新屏幕)分离。
    将check_events()放在一个名为game_functions的模块中。

    import sys
    import pygame
    
    
    def check_events():
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
    

    更改alien_invasions.py为:

    import sys
    import pygame
    from settings import Settings
    from ship import Ship
    import game_functions as gf
    
    
    def run_game():
        pygame.init()
        ai_settings = Settings()
        screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height))
        pygame.display.set_caption("Alien Invasion")
        pygame.display.set_caption('Alien Invasion')
        ship = Ship(screen)
        # 开始游戏主循环
        while True:
            gf.check_events()
            screen.fill(ai_settings.bg_color)
            ship.blitme()
        # 让最近绘制的屏幕可见
            pygame.display.flip()
    
    
    run_game()
    

    把循环部分更改了即可。

    函数update_screen()

    为了进一步简化run_game(),下面将更新屏幕的代码移到一个名为update_screen()的函数中,并将这个函数放在模块game_functions.py中:

    def update_screen(ai_settings, screen, ship):
        screen.fill(ai_settings.bg_color)
        ship.blitme()
        pygame.display.flip()
    

    再将alien_invasion.py中的部分替换即可:

        while True:
            gf.check_events()
            gf.update_screen(ai_settings, screen, ship)
    

    驾驶飞船

    下面让玩家可以左右移动飞船。为此,我们将编写代码,在用户按左或右箭头时做出响应。
    我们先专注于向右移动。

    响应按键

    每当用户按键时,都将在pygame中注册一个事件。事件是通过方法pygame.event.get()获取的,因此在函数check_events()中,我们需要指定要检查那些类型的事件。
    每次按键都被注册为一个KEYDOWN事件。
    检测到KEYDOWN事件时,我们需要检查按下的键是否为特定的键。例如,如果按下的时右箭头,就增大飞船的rect.centrx值。
    在game_functions.py中添加以下函数:

    def check_events(ship):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            elif event.type == pygame.KEYDOWN: # ①
                if event.key == pygame.K_PIGHT: # ②
                    ship.rect.centerx += 1
    

    解释:①处用于在pygame检测到KEYDOWN事件时作出响应。
    ②处在检测到按下为右箭头时将飞船向右移动。
    修改对应的alien_invasion.py模块:传参ship

        while True:
            gf.check_events(ship)
            gf.update_screen(ai_settings, screen, ship)
    

    允许不断移动

    玩家按住右箭头不放时,我们希望飞船不断地向右移动,直到玩家松开为止。
    思路:

    1. 检测pygame.KEYUP事件,以便玩家松开右箭头时我们能够知道这一点;然后,我们将结合使用KEYDOWN和KEYUP事件,以及一个名为moving_right的标志来实现持续移动。
    2. 飞船不动时,标志moving_right将为False。玩家按下右箭头时,我们将这个标志设为True,而玩家松开时,我们将这个标志重新设置为Falsh

    飞船的属性由ship类控制,因此我们给这个类添加一个名为moving_right的属性和一个名为update()的方法。方法update()检查标志moving_right的状态,如果这个标志为True,就调整飞船的位置。

    import pygame
    class Ship():
    	--snip--
    	def __init__(self, screen):
    		self.moving_right = False
    
        def update(self):
            if self.moving_right:
                self.rect.centerx += 1
    

    接下来更改game_functions.py中的check_events()函数,让玩家按下右箭头时将moving_right设置为True,并在玩家松开时将moving_right设置为false:

    def check_events(ship):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_RIGHT:
                    ship.moving_right = True
            elif event.type == pygame.KEYUP:
                if event.key == pygame.K_RIGHT:
                    ship.moving_right = False
    

    最后,在alien_invasion.py中的while循环,以便每次执行循环时都要调用飞船的方法update()。

        while True:
            gf.check_events(ship)
            ship.update()
            gf.update_screen(ai_settings, screen, ship)
    

    左右移动

    飞船能够不断地向右移动后,添加向左移动地逻辑很容易。我们将再次修改ship类和函数check_events().

    ship.py
    
    import pygame
    class Ship():
        def __init__(self, screen):
            --snip--
            self.moving_right = False
            self.moving_left = False
    
        def update(self):
            if self.moving_right:
                self.rect.centerx += 1
            elif self.moving_left:
                self.rect.centerx -= 1
    	--snip--
    
    game_functions.py # 中的check_events()修改。
    
    def check_events(ship):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                if event.key == pygame.K_RIGHT:
                    ship.moving_right = True
                elif event.key == pygame.K_LEFT:
                    ship.moving_left = True
            elif event.type == pygame.KEYUP:
                if event.key == pygame.K_RIGHT:
                    ship.moving_right = False
                if event.key == pygame.K_LEFT:
                    ship.moving_left = False
    

    调整飞船的速度

    接下来我们将进一步优化飞船的移动方式:调整飞船的速度;限制飞船的移动距离,以免它移到屏幕外面去。
    每次执行while循环时,飞船最多移动1像素,但我们可以在Settings类中添加属性ship_speed_factor,用于控制飞船的速度。我们将根据这个属性决定飞船在每次循环时最多移动多少距离。

    class Settings():
        def __init__(self):
            --snip--
            self.ship_speed_factor = 1.5
    

    通过将速度设置为小数值,可在后面加快游戏的节奏时更细致地控制飞船的速度。然而,rect的centerx等属性只能存储整数值,因此我们需要对ship类作修改:

    import pygame
    class Ship():
        def __init__(self, ai_settings, screen):
            --snip--
            self.center = float(self.rect.centerx)
            self.moving_right = False
            self.moving_left = False
            
    	 def update(self):
            if self.moving_right:
               	self.center += self.ai_settings.ship_speed_factor
            elif self.moving_left:
                self.center -= self.ai_settings.ship_speed_factor
         self.rect.centerx = self.center
    
    	def blitme(self):
    		--snip--
    

    最后更改alien_invasion.py

    def run_game():
    	--snip--
    	ship = Ship(ai_settings, screen)
    	--snip--
    

    限制飞船的活动范围

    当玩家按住箭头的时间足够长,飞船将移动到屏幕外面,消失得无影无踪。下面来修复这种问题。为此,我们将修改ship类的方法update():

    def run_game():
        pygame.init()
        ai_settings = Settings()
        screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height))
        pygame.display.set_caption("Alien Invasion")
        ship = Ship(ai_settings, screen)
    

    重构check_events()

    随着游戏的开发,函数check_events()将越来越长,我们将其部分代码放在两个函数中:一个处理KEYDOWN事件,一个处理KEYUP事件。

    import sys
    import pygame
    
    
    def check_keydown_events(event, ship):
        if event.key == pygame.K_RIGHT:
            ship.moving_right = True
        elif event.key == pygame.K_LEFT:
            ship.moving_left = True
    
    
    def check_keyup_events(event, ship):
        if event.key == pygame.K_RIGHT:
            ship.moving_right = False
        elif event.key == pygame.K_LEFT:
            ship.moving_left = False
    
    
    def check_events(ship):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
            elif event.type == pygame.KEYDOWN:
                check_keydown_events(event, ship)
            elif event.type == pygame.KEYUP:
                check_keyup_events(event, ship)
    
    
    def update_screen(ai_settings, screen, ship):
        screen.fill(ai_settings.bg_color)
        ship.blitme()
        pygame.display.flip()
    

    射击

    下面来添加射击设置。我们将编写玩家按空格键时发射子弹(小矩形)的代码。子弹将在屏幕中向上穿行,抵达屏幕边缘后消失。

    添加子弹设置

    class Settings():
        def __init__(self):
            --snip--
            # 子弹设置
            self.bullet_speed_factor = 1
            self.bullet_width = 3
            self.bullet_height = 15
            self.bullet_color = 60, 60, 60
    

    创建bullet类

    创建存储Bullet类的文件bullet.py。

    import pygame
    from pygame.sprite import Sprite
    
    
    class Bullet(Sprite):
        def __init__(self, ai_settings, screen, ship):
            super(Bullet, self).__init__()
            self.screen = screen
            self.rect = pygame.Rect(0, 0, ai_settings.bullet_width, ai_settings.bullet_height)
            self.rect.centerx = ship.rect.centerx
            self.rect.top = ship.rect.top
            self.y = float(self.rect.y)
            self.color = ai_settings.bullet_color
            self.speed_factor = ai_settings.bullet_speed_factor
    
        def update(self):
            self.y -= self.speed_factor
            self.rect.y = self.y
    
        def draw_bullet(self):
            pygame.draw.rect(self.screen, self.color, self.rect)
    
    

    将子弹存储到编组中

    定义Bullet类和必要的设置后,就可以编写代码了,在玩家每次按空格键时都发射出一发子弹。首先,我们将在alien_invasion.py中创建一个编组(group),用于存储所有有效的子弹,以便能够管理发射出去的子弹。
    这个编组将是pygame.sprite.Group类的一个实例;pygame.sprite.Group类,类似于列表,但提供了有助于开发游戏的额外功能。我们将在主循环中,使用这个编组在屏幕上绘制子弹,以及更新子弹的位置。

    import sys
    import pygame
    from settings import Settings
    from ship import Ship
    import game_functions as gf
    from pygame.sprite import Group
    
    
    def run_game():
        --snip--
        bullets = Group()
        # 开始游戏主循环
        while True:
            gf.check_events(ship)
            ship.update()
            bullets.update()
            gf.update_screen(ai_settings, screen, ship)
    
    
    run_game()
    

    开火

    在game_functions.py中,我们需要修改check_keydown_events(),以便在玩家按空格键时发射一颗子弹。我们无需修改check_keyup_events(),因为玩家松开空格键时什么都不会发生。我们还需要修改update_screen(),确保在调用flip()前在屏幕上重新绘制每颗子弹:

    import sys
    import pygame
    from bullet import Bullet
    
    
    def check_keydown_events(event,ai_settings , screen, ship, bullets):
        if event.key == pygame.K_RIGHT:
            ship.moving_right = True
        elif event.key == pygame.K_LEFT:
            ship.moving_left = True
        elif event.key == pygame.K_SPACE:
            new_bullet = Bullet(ai_settings, screen, ship)
            bullets.add(new_bullet)
    
    	--snip--
    
    def check_events(ai_settings, screen, ship, bullets):
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
           --snip--
            elif event.type == pygame.KEYDOWN:
                check_keydown_events(event, ai_settings, screen, ship, bullets)
    
    
    def update_screen(ai_settings, screen, ship, bullets):
        screen.fill(ai_settings.bg_color)
        for bullet in bullets.sprites():
            bullet.draw_bullet()
        ship.blitme()
        pygame.display.flip()
    

    删除已经消失的子弹

    当前,子弹抵达屏幕顶端后消失,这仅仅是因为Pygame无法在屏幕外面绘制它们。这些子弹实际上依旧存在。这是个问题,因为它们会继续消耗内存。
    为此,我们要检测这样的条件,即表示子弹的rect的bottom属性为零,它表明子弹已穿过屏幕顶端。

    # alien_invasion.py
    
     while True:
            gf.check_events(ai_settings, screen, ship, bullets)
            ship.update()
            bullets.update()
            for bullet in bullets.copy():
                if bullet.rect.bottom <= 0:
                    bullets.remove(bullet)
            print(len(bullets))
            gf.update_screen(ai_settings, screen, ship, bullets)
    

    在for循环中,不应从列表或编组中删除条目,因此必须遍历编组中的副本。我们使用了方法copy()来设置for循环,这让我们能够在循环中修改bullets。

    限制子弹的数量

    我们可以对可同时出现在屏幕上的子弹数量进行限制,以鼓励玩家有目的的射击。
    首先,在settings.py中存储允许的最大子弹数量。

    class Settings():
        def __init__(self):
            --snip--
            # 允许的子弹数量
            self.bullets_allowed = 3
    

    在创建新子弹前检查未消失得子弹数量是否小于该设置。

    # game_functions.py
    
    def check_keydown_events(event,ai_settings , screen, ship, bullets):
        if event.key == pygame.K_RIGHT:
            ship.moving_right = True
        elif event.key == pygame.K_LEFT:
            ship.moving_left = True
        elif event.key == pygame.K_SPACE:
            if len(bullets) < ai_settings.bullets_allowed: # 判断是否满足子弹数量的限制
                new_bullet = Bullet(ai_settings, screen, ship)
                bullets.add(new_bullet)
    

    创建函数update_bullets()

    编写并检查子弹管理代码后,可将其移到模块game_functions中,让主程序文件alien_invasion.py尽可能的简单。
    创建一个名为update_bullets()的函数:

    # game_functions.py 底部增加
    
    def update_bullets(bullets):
        bullets.update()
        for bullet in bullets.copy():
            if bullet.rect.bottom <= 0:
                bullets.remove(bullets)
    

    更改alien_invasion.py中的while循环:

    # game_invasion.py
    
        while True:
            gf.check_events(ai_settings, screen, ship, bullets)
            ship.update()
            bullets.update()
            gf.update_bullets(bullets)
            gf.update_screen(ai_settings, screen, ship, bullets)
    

    创建函数fire_bullet()

    将发射子弹的代码移到一个独立的函数中,这样,在check_keydown_events()中只需要使用一行代码来发射子弹,让elif代码块简单。

    # game_functions.py
    
    import sys
    import pygame
    from bullet import Bullet
    
    
    def check_keydown_events(event, ai_settings, screen, ship, bullets):
        --snip--
        elif event.key == pygame.K_SPACE:
            fire_bullet(ai_settings, screen, ship, bullets)
    
    
    def fire_bullet(ai_settings, screen, ship, bullets):
        if len(bullets) < ai_settings.bullets_allowed:
            new_bullet = Bullet(ai_settings, screen, ship)
            bullets.add(new_bullet)
    

    外星人

    接下来,我们将在游戏中添加外星人。首先,我们在屏幕边缘附近添加一个外星人,然后生成一群外星人向两边和下面移动,并删除被子弹击中的外星人。最后,我们将显示玩家拥有的飞船属性,并在玩家的飞船用完后结束游戏。
    在给项目添加新功能前,还应审核既有代码。每进一个阶段,通常项目都会更复杂,因此最好度混乱或低效的代码进行审计。
    由于每次测试时,都要点击关闭来关闭游戏,很麻烦,下面添加一个结束游戏的快捷键Q:

    # game_functions.py
    
    def check_keydown_events(event, ai_settings, screen, ship, bullets):
       --snip--
        elif event.key == pygame.K_q:
            sys.exit()
    

    创建Alien类

    # alien.py
    
    import pygame
    from pygame.sprite import Sprite
    
    
    class Alien(Sprite):
        def __init__(self, ai_settings, screen):
            super(Alien, self).__init__()
            self.screen = screen
            self.ai_settings = ai_settings
            self.image = pygame.load('images/alien.png')
            self.rect = self.image.get_rect()
            self.rect.x = self.rect.width
            self.rect.y = self.rect.height
            self.x = float(self.rect.x)
    
        def blitme(self):
            self.screen.blit(self.image, self.rect)
    

    创建Alien实例

    在alien_invasion.py中创建一个Alien实例:

    # alien_invasion.py
    
    import sys
    import pygame
    from settings import Settings
    from ship import Ship
    import game_functions as gf
    from pygame.sprite import Group
    from alien import Alien
    
    
    def run_game():
        pygame.init()
        ai_settings = Settings()
        screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height))
        pygame.display.set_caption("Alien Invasion")
        ship = Ship(ai_settings, screen)
        bullets = Group()
        alien = Alien(ai_settings, screen)
    

    让外星人出现在屏幕上

    在update_screen()中调用方法blitme()。

    # game_functions.py
    
    def update_screen(ai_settings, screen, ship, alien, bullets):
        screen.fill(ai_settings.bg_color)
        for bullet in bullets.sprites():
            bullet.draw_bullet()
        ship.blitme()
        alien.blitme() # 显示外星人
        pygame.display.flip()
    

    创建一群外星人

    首先确认一行可以容纳多少个外星人。

    确定一行可容纳多少个外星人

    为确定一行可容纳多少外星人,我们可以看看可用的水平空间有多大。并在屏幕左右各留出一个外星人的距离;最后用剩下的距离除以一个外星人的距离。
    公式:
    available_space_x=ai_settings.screen_width-(2* alien_width)
    number_aliens_x=available_space_x / (2* alien_width)

    创建多行外星人

    为创建一行外星人,首先在alien_invasion.py中创建一个名为aliens的空编组,用于存储全部外星人,再调用game_functions.py中的创建外星人群的函数。

    import sys
    import pygame
    from settings import Settings
    from ship import Ship
    import game_functions as gf
    from pygame.sprite import Group
    from alien import Alien
    
    
    def run_game():
        --snip--
        bullets = Group()
        aliens = Group()
        # alien = Alien(ai_settings, screen)
        gf.create_fleet(ai_settings, screen, aliens)
        # 开始游戏主循环
        --snip--
    

    修改update_screen():

    # game_functions.py
    
    def update_screen(ai_settings, screen, ship, aliens, bullets):
        screen.fill(ai_settings.bg_color)
        for bullet in bullets.sprites():
            bullet.draw_bullet()
        ship.blitme()
        aliens.draw(screen)    # 添加
        # alien.blitme()
        pygame.display.flip()
    

    增加新函数:create_fleet(),放在game_functions.py

    def create_fleet(ai_settings, screen, aliens):
        alien = Alien(ai_settings, screen)
        alien_width = alien.rect.width
        available_space_x = ai_settings.screen_width - 2 * alien_width
        number_aliens_x = int(available_space_x / (2 * alien_width))
        for alien_number in range(number_aliens_x):
            alien = Alien(ai_settings, screen)
            alien.x = alien_width + 2 * alien_width * alien_number
            alien.rect.x = alien.x
            aliens.add(alien)
    

    重构create_fleet()

    下面是create_fleet()和两个新函数,get_number_aliens_x()和create_alien():

    # game_function.py
    
    def get_number_aliens_x(ai_settings, alien_width):
        available_space_x = ai_settings.screen_width - 2 * alien_width
        number_aliens_x = int(available_space_x / (2 * alien_width))
        return number_aliens_x
    
    
    def create_alien(ai_settings, screen, aliens, alien_number):
        alien = Alien(ai_settings, screen)
        alien_width = alien.rect.width
        alien.x = alien_width + 2 * alien_width * alien_number
        alien.rect.x = alien.x
        aliens.add(alien)
    
    
    def create_fleet(ai_settings, screen, aliens):
        alien = Alien(ai_settings, screen)
        number_aliens_x = get_number_aliens_x(ai_settings, alien.rect.width)
        for alien_number in range(number_aliens_x):
            create_alien(ai_settings, screen, aliens, alien_number)
    

    添加行

    # game_functions.py
    
    def get_number_rows(ai_settings, ship_height, alien_height):
        available_space_y = (ai_settings.screen_height - (3 * alien_height) - ship_height)
        number_rows = int(available_space_y / (2 * alien_height))
        return number_rows
    
    
    def create_alien(ai_settings, screen, aliens, alien_number, row_number):
        --snip--
        alien.x = alien_width + 2 * alien_width * alien_number
        alien.rect.x = alien.x
        alien.rect.y = alien.rect.height + 2 * alien.rect.height * row_number
        aliens.add(alien)
    
    
    def create_fleet(ai_settings, screen, ship, aliens):
        alien = Alien(ai_settings, screen)
        number_aliens_x = get_number_aliens_x(ai_settings, alien.rect.width)
        number_rows = get_number_rows(ai_settings, ship.rect.height, alien.rect.height)
        for row_number in range(number_rows):
            for alien_number in range(number_aliens_x):
                create_alien(ai_settings, screen, aliens, alien_number, row_number)
    

    让外星人群移动

    下面来让外星人移动,在屏幕上向右移动,撞到屏幕边缘后向下移动一定的距离,再沿反方向移动。

    向右移动的外星人

    为移动外星人,我们将使用方法alien.py中的方法update(),且对外星人群中的每个外星人都调用它。
    首先在settings.py中添加控制外星人的速度

    # settings.py
    
    class Settings():
        def __init__(self):
            --snip--
            # 外星人设置
            self.alien_speed_factor = 1
    

    alien.py中添加update()函数:

    alien.py
    
        def update(self):
            self.x += self.ai_settings.alien_speed_factor
            self.rect.x = self.x
    

    更新while循环中的更新子弹和飞船的方法

    alien_invasion.py
    
        while True:
            gf.check_events(ai_settings, screen, ship, bullets)
            ship.update()
            bullets.update()
            gf.update_bullets(bullets)
            gf.update_aliens(aliens)
            gf.update_screen(ai_settings, screen, ship, aliens, bullets)
    

    最后在game.functons.py末尾添加新函数:update_aliens()

    game_functions.py
    
    def update_aliens(aliens):
        aliens.update()
    

    创建表示外星人移动方向的设置

    下面来创建让外星人撞到屏幕右边缘后向下移动,再向左移动的设置。

    settings.py
    
    class Settings():
        def __init__(self):
            --snip--
            # 外星人设置
            self.alien_speed_factor = 1
            self.fleet_drop_speed = 10
            self.fleet_direction = 1
    

    检查外星人是否撞到屏幕边缘

    现在需要编写一个方法来检查是否有外星人撞到屏幕边缘,还需要修改update(),以让每个外星人都沿正确的方法移动。

    alien.py
    
        def check_edges(self):
            screen_rect = self.screen.get_rect()
            if self.rect.right >= screen_rect.right:
                return True
            elif self.rect.left <= 0:
                return True
    
        def update(self):
            self.x += (self.ai_settings.alien_speed_factor * self.ai_settings.fleet_direction)
            self.rect.x = self.x
    

    向下移动外星人群并改变移动方向

    game_functions.py
    
    def update_aliens(ai_settings, aliens):
        check_fleet_edges(ai_settings, aliens)
        aliens.update()
    
    
    def check_fleet_edges(ai_settings, aliens):
        for alien in aliens.sprites():
            if alien.check_edges():
                change_fleet_direction(ai_settings, aliens)
                break
    
    
    def change_fleet_direction(ai_settings, aliens):
        for alien in aliens.sprites():
            alien.rect.y += ai_settings.fleet_drop_speed
        ai_settings.fleet_direction *= -1
    

    更改alien_invasion.py

    alien_invasion.py
    
        while True:
            gf.check_events(ai_settings, screen, ship, bullets)
            ship.update()
            bullets.update()
            gf.update_bullets(bullets)
            gf.update_aliens(ai_settings, aliens)
            gf.update_screen(ai_settings, screen, ship, aliens, bullets)
    

    射杀外星人

    我们创建了飞船和外星人群,但子弹射击中外星人时,将穿过外星人,因为我们还没有检查碰撞。我们将使用sprite.groupcollide()检测编组成员之间的碰撞。

    检测外星人与子弹的碰撞

    方法sprite.groupcollide()将每颗子弹的rect同每个外星人的rect进行比较,并返回一个字典,其中包含发生了碰撞的子弹和外星人。
    在函数update_bullets()中,使用下面的代码来检测碰撞:

    # game_functions.py
    
    def update_bullets(aliens,bullets):
        bullets.update()
        for bullet in bullets.copy():
            if bullet.rect.bottom <= 0:
                bullets.remove(bullets)
        print(len(bullets))
        collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
    

    update_bullets()的调用传递了实参,aliens:

    alien_functions.py
    
        while True:
            gf.check_events(ai_settings, screen, ship, bullets)
            ship.update()
            bullets.update()
            gf.update_bullets(aliens, bullets)
            gf.update_aliens(ai_settings, aliens)
            gf.update_screen(ai_settings, screen, ship, aliens, bullets)
    

    生成新的外星人群

    这个游戏的一个重要特点就是外星人无穷无尽,一个外星人被消灭后,又会出现一群新的外星人。
    首先就要检查编组aliens是否为空。如果为空,就调用create_fleet()。

    game_functions.py
    
    def update_bullets(ai_settings, screen, ship, aliens, bullets):
        --snip--
        collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
        if len(aliens) == 0:
            bullets.empty()
            create_fleet(ai_settings, screen, ship, aliens)
    

    更改alien_invasion.py

        while True:
            gf.check_events(ai_settings, screen, ship, bullets)
            ship.update()
            bullets.update()
            gf.update_bullets(ai_settings, screen, ship, aliens, bullets)
            gf.update_aliens(ai_settings, aliens)
            gf.update_screen(ai_settings, screen, ship, aliens, bullets)
    

    重构update_bullets()

    game_functions.py
    
    def check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets):
        collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
        if len(aliens) == 0:
            bullets.empty()
            create_fleet(ai_settings, screen, ship, aliens)
    
    
    def update_bullets(ai_settings, screen, ship, aliens, bullets):
        bullets.update()
        for bullet in bullets.copy():
            if bullet.rect.bottom <= 0:
                bullets.remove(bullets)
        print(len(bullets))
        check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets)
    

    结束游戏

    检测外星人和飞船碰撞

    game_functions.py
    
    def update_aliens(ai_settings, ship, aliens):
        check_fleet_edges(ai_settings, aliens)
        aliens.update()
        if pygame.sprite.spritecollideany(ship, aliens):
            print("ship hit !!!")
    

    将ship传给update_aliens():

    # alien_invasion.py
    
        while True:
            gf.check_events(ai_settings, screen, ship, bullets)
            ship.update()
            bullets.update()
            gf.update_bullets(ai_settings, screen, ship, aliens, bullets)
            gf.update_aliens(ai_settings, ship, aliens)   # 加入ship
            gf.update_screen(ai_settings, screen, ship, aliens, bullets)
    

    响应外星人和飞船碰撞

    通过跟踪游戏的统计信息来记录飞船被撞了多少次。(有助于计分)
    编写一个用于跟踪游戏统计信息的新类:GameStarts放在game_starts.py中

    class GameStats():
        def __init__(self, ai_settings):
            self.ai_settings = ai_settings
            self.reset_stats()
    
        def reset_stats(self):
            self.ships_left = self.ai_settings.ship_limit
    

    飞船数存储在settings.py中:

    class Settings():
        def __init__(self):
            --snip--
            # 飞船设置
            self.ship_speed_factor = 1.5
            self.ship_limit = 3
    

    在alien_invasion.py中创建一个GameStats实例:

    --snip--
    from alien import Alien
    from game_stats import GameStats
    
    
    def run_game():
        --snip--
        pygame.display.set_caption("Alien Invasion")
        stats = GameStats(ai_settings)
        --snip--
        while True:
            --snip--
            gf.update_bullets(ai_settings, screen, ship, aliens, bullets)
            gf.update_aliens(ai_settings, stats, screen, ship, aliens, bullets)
            --snip--
    
    run_game()
    

    最后将实现这些功能的大部分代码放到函数ship.hit()中:

    # game_functions.py
    
    def ship_hit(ai_settings, stats, screen, ship, aliens, bullets):
        stats.ships_left -= 1
        aliens.empty()
        bullets.empty()
        create_fleet(ai_settings, screen, ship, aliens)
        ship.center_ship()
        sleep(0.5)
    
    def update_aliens(ai_settings, stats, screen, ship, aliens, bullets):
        check_fleet_edges(ai_settings, aliens)
        aliens.update()
        if pygame.sprite.spritecollideany(ship, aliens):
            ship_hit(ai_settings, stats, screen, ship, aliens, bullets)
    

    将center_ship()方法,添加到ship.py末尾:

        def center_ship(self):
            self.center = self.screen_rect.centerx
    

    有外星人到达屏幕底部

    如果有外星人到达屏幕底端,我们想有外星人撞到飞船那样做出反应。
    创建一个新函数,check_aliens_bottom():

    # game_functions.py
    
    
    def update_aliens(ai_settings, stats, screen, ship, aliens, bullets):
        --snip--
        check_aliens_bottom(ai_settings, stats, screen, ship, aliens, bullets)
    
    def check_aliens_bottom(ai_settings, stats, screen, ship, aliens, bullets):
        screen_rect = screen.get_rect()
        for alien in aliens.sprites():
            if alien.rect.bottom >= screen_rect.bottom:
                ship_hit(ai_settings, stats, screen, ship, aliens, bullets)
                break
    

    游戏结束

    ships_left会不断变成更小的负数。下面在GameStats中添加一个作为标志的属性game_active,以便玩家在用完飞船后结束游戏。

    # game_stats.py
    
    class GameStats():
        def __init__(self, ai_settings):
            --snip--
            self.game_active = True
    

    在玩家所有飞船用完后,将game_active设置为False:

    # game_functions.py
    
    def ship_hit(ai_settings, stats, screen, ship, aliens, bullets):
        if stats.ships_left > 0:
            stats.ships_left -= 1
            sleep(0.5)
        else:
            stats.game_active = False
        --snip--
    

    确定应运行游戏的那些部分

    # alien_invasion.py
    
        while True:
            gf.check_events(ai_settings, screen, ship, bullets)
            if stats.game_active:
                ship.update()
                gf.update_bullets(ai_settings, screen, ship, aliens, bullets)
                gf.update_aliens(ai_settings, stats, screen, ship, aliens, bullets)
            bullets.update()
            gf.update_screen(ai_settings, screen, ship, aliens, bullets)
    

    后记

    如果有机会,下次再完善计分和开始结束让玩家可以持续玩这个游戏的功能。

    物联沃分享整理
    物联沃-IOTWORD物联网 » python项目-飞机大战

    发表回复