Python开发《外星人入侵》

跟书《Python编程:从入门到实践》学习一段时间了,书中第一个项目就是开发小游戏《外星人入侵》。下面是跟书一步步开发的代码,代码后面的注释仅仅是个人理解。

效果图:

项目文件

项目目录:alien_invasion

图片目录:alien_invasion/images

图片:alien_invasion/images/ship.bmp、alien_invasion/images/alien.bmp


alien_invasion.py

主文件alien_invasion.py创建一系列整个游戏都要用到的对象:存储在ai_settings中的设置、存储在screen中的主显示surface以及一个飞船实例。文件alien_invasion.py还包含游戏的主循环,这是一个调用check_events()ship.update()update_screen()的while循环。

要玩游戏《外星人入侵》,只需运行文件alien_invasion.py。其他文件(settings.pygame_functions.pyship.py)包含的代码被直接或间接地导入到这个文件中。

import pygame
from pygame.sprite import Group                 #导入pygame模块下sprite子模块中的Group类
from settings import Settings               #导入自定义的Settings类
from ship import Ship               #导入自定义的Ship类
import game_functions as gf                 #导入自定义的game_functions类,并起别名为gf
from game_stats import GameStats                #导入自定义的GameStats类
from button import Button               #导入自定义的Button类
from scoreboard import Scoreboard               #导入自定义的Scoreboard类


def run_game():
    # 初始化pygame、设置和屏幕对象
    pygame.init()
    ai_settings = Settings()            #实例化Settings类
    screen = pygame.display.set_mode((ai_settings.screen_width, ai_settings.screen_height))             #调用display模块的set_mode()方法设置屏幕大小
    pygame.display.set_caption("Alien Invasion")            #调用display模块的set_caption()方法设置标题

    # 创建Play按钮
    play_button = Button(ai_settings, screen, "Play")           #实例化Button类

    # 创建存储游戏统计信息的实例,并创建记分牌
    stats = GameStats(ai_settings)              #实例化GameStats类
    sb = Scoreboard(ai_settings, screen, stats)             #实例化Scoreboard类

    # 创建一艘飞船、一个子弹编组和一个外星人编组
    ship = Ship(ai_settings, screen)                #实例化Ship类
    bullets = Group()               #实例化Group类
    aliens = Group()                #实例化Group类

    # 创建外星人群
    gf.create_fleet(ai_settings, screen, ship, aliens)              #调用gf模块的create_fleet()函数

    # 开始游戏的主循环
    while True:
        # 监视键盘和鼠标事件
        gf.check_events(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets)             #调用gf模块的check_events()函数,监测键盘和鼠标的事件

        if stats.game_active:
            ship.update()               #调用Ship类的update()方法
            gf.update_bullets(ai_settings, screen, stats, sb, ship, aliens, bullets)                #调用gf模块的update_bullets函数,更新子弹的状态
            gf.update_aliens(ai_settings, screen, stats, sb, ship, aliens, bullets)             #调用gf模块的update_aliens函数,更新外星人的状态

        gf.update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button)                #调用gf模块的update_screen函数,更新屏幕的状态


run_game()                  #运行游戏

settings.py

文件settings.py包含Settings类,这个类包含方法__init__()、初始化游戏设置的方法initialize_dynamic_settings()、随游戏进程提高速度的方法increase_speed()

class Settings():
    """存储《外星人入侵》的所有设置的类"""

    def __init__(self):
        """初始化游戏的设置"""
        # 屏幕设置
        self.screen_width = 1200                #设置屏幕宽度
        self.screen_height = 800                #设置屏幕高度
        self.bg_color = (230, 230, 230)             #设置屏幕背景色

        # 飞船的位置
        self.ship_speed_factor = 1.5                #设置飞船左右移动速度
        self.ship_limit = 3             #设置飞船个数,即飞船有几条“命”

        # 子弹设置
        self.bullet_speed_factor = 3                #设置子弹的向上移动速度
        self.bullet_width = 3               #设置子弹的宽度
        self.bullet_height = 15                 #设置子弹的高度
        self.bullet_color = 60, 60, 60              #设置子弹的眼色
        self.bullets_allowed = 5                #设置屏幕上最多同时存在的子弹数

        # 外星人设置
        self.alien_speed_factor = 1                 #设置外星人的左右移动速度
        self.fleet_drop_speed = 10              #设置外星人群的向下移动速度

        # 以什么样的速度加快游戏节奏
        self.speedup_scale = 1.1                #设置每轮游戏节奏加快速度
        # 外星人点数的提高速度
        self.score_scale = 1.5                  #设置每轮外星人分数提高倍数

        self.initialize_dynamic_settings()              #调用initialize_dynamic_settings()函数初始化游戏参数

    def initialize_dynamic_settings(self):
        """初始化随游戏进行而变化的设置"""
        self.ship_speed_factor = 1.5
        self.bullet_speed_factor = 5
        self.alien_speed_factor = 1

        # fleet_direction为1表示向右移,-1表示向左移
        self.fleet_direction = 1                设置外星人群左右移动方向

        # 记分
        self.alien_points = 50              设置外星人分数

    def increase_speed(self):
        """提高速度设置"""
        self.ship_speed_factor *= self.speedup_scale
        self.bullet_speed_factor *= self.speedup_scale
        self.alien_speed_factor *= self.speedup_scale
        self.alien_points = int(self.alien_points * self.score_scale)

ship.py

文件ship.py包含Ship类,这个类包含方法__init__()、管理飞船位置的方法update()、在屏幕上绘制飞船的方法blitme()以及让飞船在屏幕底部居中的方法center_ship()。表示飞船的图像存储在文件夹images下的文件ship.bmp中。

import pygame
from pygame.sprite import Sprite                #导入pygame模块下sprite子模块中的Sprite类


class Ship(Sprite):

    def __init__(self, ai_settings, screen):
        """初始化飞船并设置其初始位置"""
        super(Ship, self).__init__()                #兼容python2.7,Ship类继承Sprite类属性

        self.screen = screen
        self.ai_settings = ai_settings

        # 加载飞船图形并获取其外接矩形
        self.image = pygame.image.load('images/ship.bmp')               #加载图片,创建图片对象
        self.rect = self.image.get_rect()               #调用get_rect()方法,获取image的属性rect
        self.screen_rect = screen.get_rect()                #调用get_rect()方法,将表示屏幕的矩形存储在self.screen_rect中

        # 将每艘新飞船放在屏幕底部中央
        self.rect.centerx = self.screen_rect.centerx                #让飞船矩形centerx属性(中央x坐标)等于屏幕矩形centerx属性,x方向上居中
        self.rect.bottom = self.screen_rect.bottom              #让飞船矩形bottom属性等于屏幕矩形bottom属性

        # 在飞船的属性center中存储小数值
        self.center = float(self.rect.centerx)

        # 移动标志
        self.moving_right = False
        self.moving_left = False

    def update(self):
        """根据移动标志调整飞船的位置"""
        # 更新飞船的center值,而不是rect
        if self.moving_right and self.rect.right < self.screen_rect.right:              #当向右移动且飞船矩形right属性小于屏幕矩形right属性时
            self.center += self.ai_settings.ship_speed_factor
        if self.moving_left and self.rect.left > 0:             #当向左移动且飞船矩形left属性大于0时
            self.center -= self.ai_settings.ship_speed_factor

        # 根据self.center更新rect对象
        self.rect.centerx = self.center                 #如果是self.rect.centery = self.center,则飞船会上下移动,而不是左右移动

    def blitme(self):
        """在指定位置绘制飞船"""
        self.screen.blit(self.image, self.rect)             #调用blit()方法将图片对象添加到窗口对象中

    def center_ship(self):
        """让飞船在屏幕上居中"""
        self.center = self.screen_rect.centerx              #让飞船矩形center属性等于屏幕矩形centerx属性

Rect对象是用来存储矩形对象的,Rect对象有一些虚拟属性,如top、left、bottom、right。而center属性为矩形的中心点,其实就是关于x/y坐标的二元组,因此又有centerx,centery两个属性。另外Rect.x和Rect.y分别是Rect.left和Rect.top


game_functions.py

文件game_functions.py包含一系列函数,游戏的大部分工作都是由它们完成的。函数check_events()检测相关的事件,如按键和松开,并使用辅助函数check_keydown_events()check_keyup_events()来处理这些事件。就目前而言,这些函数管理飞船的移动。模块game_functions还包含函数update_screen(),它用于在每次执行主循环时都重绘屏幕。

import sys
from time import sleep
import pygame
from bullet import Bullet
from alien import Alien


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:               #监测按下空格键事件
        fire_bullet(ai_settings, screen, ship, bullets)                 #调用fire_bullet()函数发射子弹
    elif event.key == pygame.K_q:                   #监测按下Q键事件
        sys.exit()


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(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets):
    """响应按键和鼠标事件"""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:               #监测单击窗口关闭按钮事件
            sys.exit()
        elif event.type == pygame.KEYDOWN:              #监测键盘按下事件
            check_keydown_events(event, ai_settings, screen, ship, bullets)             #调用check_keydown_events()函数处理键盘按下事件
        elif event.type == pygame.KEYUP:                #监测键盘松开事件
            check_keyup_events(event, ship)                     #调用check_keyup_events()处理键盘松开事件
        elif event.type == pygame.MOUSEBUTTONDOWN:                  #监测鼠标按下事件
            mouse_x, mouse_y = pygame.mouse.get_pos()               #调用get_pos()方法获取鼠标光标的位置,返回元组(x, y)
            check_play_button(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets, mouse_x, mouse_y)             #调用check_play_button()函数处理按下play按钮事件


def check_play_button(ai_settings, screen, stats, sb, play_button, ship, aliens, bullets, mouse_x, mouse_y):
    """在玩家单击Play按钮时开始新游戏"""
    button_clicked = play_button.rect.collidepoint(mouse_x, mouse_y)                #调用collidepoint()方法检查鼠标单击位置是否在Play按钮的rect内
    if button_clicked and not stats.game_active:
        # 重置游戏设置
        ai_settings.initialize_dynamic_settings()               #调用Settings类的initialize_dynamic_settings()方法初始化游戏设置

        # 隐藏光标
        pygame.mouse.set_visible(False)             #调用set_visible()方法隐藏鼠标光标

        # 重置游戏统计信息
        stats.reset_stats()                 #调用GameStats类的reset_stats()方法初始化统计信息
        stats.game_active = True

        # 重置记分牌图像
        sb.prep_score()                 #调用Scoreboard类的prep_score()方法显示游戏得分
        sb.prep_high_score()                #调用Scoreboard类的prep_high_score()方法显示游戏最高得分得分
        sb.prep_level()                 #调用Scoreboard类的prep_level()方法显示游戏等级
        sb.prep_ships()                 #调用Scoreboard类的prep_ships()方法显示飞船剩余“命数”

        # 清空外星人列表和子弹列表
        aliens.empty()
        bullets.empty()

        # 创建一群新的外星人,并让飞船居中
        create_fleet(ai_settings, screen, ship, aliens)             #调用create_fleet()函数创建新的外星人群
        ship.center_ship()              #调用Ship类的center_ship()方法让飞船居中


def update_screen(ai_settings, screen, stats, sb, ship, aliens, bullets, play_button):
    """更新屏幕上的图像,并切换到新屏幕"""
    # 每次循环时都重绘屏幕
    screen.fill(ai_settings.bg_color)               #屏幕对象填充颜色
    # 在飞船和外星人后面重绘所有子弹
    for bullet in bullets.sprites():
        bullet.draw_bullet()                #绘制每颗子弹到屏幕上
    ship.blitme()               #调用Ship类的blitme()方法绘制飞船
    aliens.draw(screen)                 #aliens是编组,对编组调用draw()时,Pygame自动绘制编组的每个元素,绘制位置由元素的属性rect决定

    # 显示得分
    sb.show_score()                 #调用Scoreboard类的show_score()方法显示得分

    # 如果游戏处于非活动状态,就绘制Play按钮
    if not stats.game_active:
        play_button.draw_button()               #调用Button类的draw_button()方法绘制Play按钮

    # 让最近绘制的屏幕可见
    pygame.display.flip()


def update_bullets(ai_settings, screen, stats, sb, ship, aliens, bullets):
    """更新子弹的位置,并删除已消失的子弹"""
    # 更新子弹的位置
    bullets.update()                #为编组bullets中的每个精灵调用bullet.update()

    # 删除已消失的子弹
    for bullet in bullets.copy():
        if bullet.rect.bottom <= 0:
            bullets.remove(bullet)

    check_bullet_alien_collisitions(ai_settings, screen, stats, sb, ship, aliens, bullets)              #调用check_bullet_alien_collisitions()函数删除碰撞的子弹和外星人


def check_bullet_alien_collisitions(ai_settings, screen, stats, sb, ship, aliens, bullets):
    """响应子弹和外星人的碰撞"""
    # 删除发生碰撞的子弹和外星人
    collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)                #检测两个精灵编组是否碰撞,后面的两个True即让发生碰撞的子弹和外星人都消失

    if collisions:
        for aliens in collisions.values():
            stats.score += ai_settings.alien_points * len(aliens)               #杀死外星人增加分数
            sb.prep_score()                 #调用Scoreboard类的prep_score()方法显示得分
        check_high_score(stats, sb)             #调用check_high_score()函数检查最高得分并显示

    if len(aliens) == 0:
        # 删除现有的子弹并新建一群外星人
        bullets.empty()
        ai_settings.increase_speed()                #调用Settings类的increase_speed()方法提高速度设置

        # 提高等级
        stats.level += 1
        sb.prep_level()                 #调用Scoreboard类的prep_level()方法显示等级

        create_fleet(ai_settings, screen, ship, aliens)                 #调用create_fleet()函数新建一个外星人群


def fire_bullet(ai_settings, screen, ship, bullets):                #检测当前子弹编组中精灵数量,小于限制则发射子弹
    """如果还没有到达限制,就发射一颗子弹"""
    # 创建新子弹并将其加入到编组bullets中
    if len(bullets) < ai_settings.bullets_allowed:
        new_bullet = Bullet(ai_settings, screen, ship)              #实例化Bullet类
        bullets.add(new_bullet)


def get_numbwe_aliens_x(ai_settings, alien_width):                  #外星人之间的留白宽度就是外星人矩形的宽度
    """计算每行可容纳多少个外星人"""
    available_space_x = ai_settings.screen_width - 2 * alien_width
    number_alien_x = int(available_space_x / (2 * alien_width))
    return number_alien_x


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):
    """创建一个外星人并将其放在当前行"""
    alien = Alien(ai_settings, screen)              #实例化Alien类
    alien_width = alien.rect.width
    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)              #实例化Alien类
    number_aliens_x = get_numbwe_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)


def check_fleet_edges(ai_settings, aliens):
    """有外星人到达边缘时采取相应的措施"""
    for alien in aliens.sprites():
        if alien.check_edges():                 #调用Alien类的check_edges()方法检测外星人是否位于屏幕边缘
            change_fleet_direction(ai_settings, aliens)                 #调用change_fleet_direction()函数下移外星人群
            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


def ship_hit(ai_settings, screen, stats, sb, ship, aliens, bullets):
    """响应被外星人撞到的飞船"""
    if stats.ships_left > 0:
        # 将ship_left减1
        stats.ships_left -= 1

        # 更新记分牌
        sb.prep_ships()

        # 清空外星人列表和子弹列表
        aliens.empty()
        bullets.empty()

        # 创建一群新的外星人,并将飞船放到屏幕底部中央
        create_fleet(ai_settings, screen, ship, aliens)             #调用create_fleet()函数创建外星人群
        ship.center_ship()              #调用Ship类的center_ship()方法让飞船在屏幕底部居中

        # 暂停
        sleep(0.5)
    else:
        stats.game_active = False                   #当飞船没有“命数”的时候,停止运行游戏
        pygame.mouse.set_visible(True)                  


def check_aliens_bottom(ai_settings, screen, stats, sb, ship, aliens, bullets):
    """检查是否有外星人到达了屏幕底部"""
    screen_rect = screen.get_rect()
    for alien in aliens.sprites():
        if alien.rect.bottom >= screen_rect.bottom:
            # 像飞船被撞到一样继续处理
            ship_hit(ai_settings, screen, stats, sb, ship, aliens, bullets)
            break


def update_aliens(ai_settings, screen, stats, sb, ship, aliens, bullets):
    """检查是否有外星人位于屏幕边缘,并更新外星人群中所有外星人的位置"""
    check_fleet_edges(ai_settings, aliens)                  #调用check_fleet_edges()函数检查外星人是否位于屏幕边缘并向下移动
    aliens.update()             ##为编组aliens中的每个精灵调用alien.update(),做左右移动

    # 检测外星人和飞船直接的碰撞
    if pygame.sprite.spritecollideany(ship, aliens):
        ship_hit(ai_settings, screen, stats, sb, ship, aliens, bullets)

    # 检查是否有外星人到达屏幕底部
    check_aliens_bottom(ai_settings, screen, stats, sb, ship, aliens, bullets)


def check_high_score(stats, sb):
    """检查是否诞生了新的最高得分"""
    if stats.score > stats.high_score:
        stats.high_score = stats.score
        sb.prep_high_score()                #调用Scoreboard类的prep_high_score()方法显示最高得分

pygame.sprite.collide_rect():检测两个精灵是否碰撞,两个精灵视为矩形。
pygame.sprite.collide_circle():检测两个精灵是否碰撞,两个精灵视为圆形。
pygame.sprite.groupcollide():检测两个精灵组是否碰撞,依次判断精灵组A中的每一个精灵是否与精灵组B中的每一个精灵发生碰撞。
pygame.sprite.spritecollideany():检测单个精灵和精灵组是否碰撞,返回一个bool值,碰撞则为True。


bullet.py

文件bullet.py包含Bullet类,这个类包含方法__init__()、管理子弹位置的方法update()以及在屏幕上绘制子弹的方法draw_bullet()

import pygame
from pygame.sprite import Sprite


class Bullet(Sprite):
    """一个对飞船发射的子弹进行管理的类"""

    def __init__(self, ai_settings, screen, ship):
        """在飞船所处的位置创建一个子弹对象"""
        super(Bullet, self).__init__()
        self.screen = screen

        # 在 (0,0) 处创建一个子弹的矩形,再设置正确的位置
        self.rect = pygame.Rect(0, 0, ai_settings.bullet_width, ai_settings.bullet_height)              #调用Rect类创建子弹矩形
        self.rect.centerx = ship.rect.centerx               #让子弹矩形的centerx属性等于飞船矩形的centerx属性
        self.rect.top = ship.rect.top               #让子弹矩形的top属性等于飞船矩形的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
        # 更新表示子弹的rect的位置
        self.rect.y = self.y

    def draw_bullet(self):
        """在屏幕上绘制子弹"""
        pygame.draw.rect(self.screen, self.color, self.rect)                #调用pygame.draw.rect()绘制子弹矩形到屏幕

alien.py

文件alien.py包含Alien类,这个类包含方法__init__()、管理外星人位置的方法update()、检测外星人是否位于屏幕边缘的方法check_edges()以及在屏幕上绘制外星人的方法blitme()。表示外星人的图像存储在文件夹images下的文件alien.bmp中。

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

        # 加载外星人图像,并设置其rect属性
        self.image = pygame.image.load('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)

    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

    def update(self):
        """向左或向右移动外星人"""
        self.x += (self.ai_settings.alien_speed_factor * self.ai_settings.fleet_direction)
        self.rect.x = self.x

game_stats.py

文件game_stats.py包含GameStats类,这个类包含方法__init__()、重置统计信息的方法reset_stats()

class GameStats():
    """跟踪游戏的统计信息"""

    def __init__(self, ai_settings):
        """初始化统计信息"""
        self.ai_settings = ai_settings
        self.reset_stats()

        # 游戏刚启动时处于活动状态
        self.game_active = True

        # 在任何情况下都不应重置最高得分
        self.high_score = 0

    def reset_stats(self):
        """初始化在游戏运行期间可能变化的统计信息"""
        self.ships_left = self.ai_settings.ship_limit
        self.score = 0
        self.level = 1

button.py

文件button.py包含Button类,这个类包含__init__()、将文字渲染为图形的方法prep_msg()以及在屏幕上绘制按钮文字的方法draw_button()

import pygame.font              #导入pygame.font模块,它让pygame能够将文本渲染到屏幕上


class Button():

    def __init__(self, ai_settings, screen, msg):
        """初始化按钮的属性"""
        self.screen = screen
        self.screen_rect = screen.get_rect()                #调用get_rect()方法,将表示屏幕的矩形存储在self.screen_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)               #调用font模块的SysFont类创建文字对象

        # 创建按钮的rect对象,并使其居中
        self.rect = pygame.Rect(0, 0, self.width, self.height)              #调用Rect类创建rect对象
        self.rect.center = self.screen_rect.center              #让按钮位于屏幕中央

        # 按钮的标签只需创建一次
        self.prep_msg(msg)              #调用prep_msg()方法创建按钮标签

    def prep_msg(self, msg):
        """将msg渲染为图形,并使其在按钮上居中"""
        self.msg_image = self.font.render(msg, True, self.text_color, self.button_color)                #调用font.render()方法将存储在msg中的文本转换为图像
        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)              #调用fill()方法填充按钮
        self.screen.blit(self.msg_image, self.msg_image_rect)               #调用blit()方法将文字对象添加到窗口上

scoreboard.py

文件scoreboard.py包含Scoreboard类,这个类包含方法__init__()、渲染得分的方法prep_score()、渲染最高得分的方法prep_high_score()、渲染等级的方法prep_level()、渲染剩余飞船的方法prep_ships()及在屏幕上显示得分、最高得分、等级、飞船的方法show_score()

import pygame.font
from pygame.sprite import Group

from ship import Ship


class Scoreboard():
    """显示得分信息的类"""

    def __init__(self, ai_settings, screen, stats):
        """初始化显示得分涉及的属性"""
        self.screen = screen
        self.screen_rect = screen.get_rect()
        self.ai_settings = ai_settings
        self.stats = stats

        # 显示得分信息时使用的字体设置
        self.text_color = (30, 30, 30)
        self.font = pygame.font.SysFont(None, 48)

        # 准备初始得分图像
        self.prep_score()
        self.prep_high_score()
        self.prep_level()
        self.prep_ships()

    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.ai_settings.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_score(self):
        """将最高得分转换为渲染的图像"""
        high_score = int(round(self.stats.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

    def prep_level(self):
        """将等级转换为渲染的图像"""
        self.level_image = self.font.render(str(self.stats.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

    def prep_ships(self):
        """显示还余下多少艘飞船"""
        self.ships = Group()
        for ship_number in range(self.stats.ships_left):
            ship = Ship(self.ai_settings, self.screen)
            ship.rect.x = 10 + ship_number * ship.rect.width
            ship.rect.y = 10
            self.ships.add(ship)

    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.screen.blit(self.level_image, self.level_rect)

        # 绘制飞船
        self.ships.draw(self.screen)

进一步改动

在之前跟书开放《外星人入侵》小游戏之后,我们可以对其增加一些更新的功能,让这个小游戏更具可玩性。

绘制背景

如果是导入背景图片,我们可以把窗口的宽度和高度设置为背景图片的像素(如800x600),这样窗口显示背景图片就是完整显示。不过有一点不好的地方,就是在处理飞船和外星人的时候是以矩形处理的,这样在背景图片颜色比较暗的情况下我们看到的飞船和外星人就是一个个矩形在移动,这样比较突兀。

下面导入背景图片,修改game_functions模块的update_screen函数:

    #screen.fill(ai_settings.bg_color)
    bg_image = pygame.image.load('images/background.png')
    screen.blit(bg_image, (0, 0))

但可以看到,导入之后飞船、外星人、子弹的移动速度都会变慢,可能跟图片的渲染有一定关系。


飞船上下移动

上面导入图片之后,导致飞船、外星人、子弹的移动速度都会变慢,因此还是选择不导入背景图片。飞船之前只能左右移动,而不能上下移动,改进一下让它上下左右都可以移动,并让W/S/A/D键也可用于飞船的上下左右控制。

  • 修改Ship类的两个方法:
    def __init__(self, ai_settings, screen):
        """初始化飞船并设置其初始位置"""
        super(Ship, self).__init__()

        self.screen = screen
        self.ai_settings = ai_settings

        # 加载飞船图形并获取其外接矩形
        self.image = pygame.image.load('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

        # 在飞船的属性center中存储小数值
        self.centerx = float(self.rect.centerx)
        self.centery = float(self.rect.centery)

        # 移动标志
        self.moving_right = False
        self.moving_left = False
        self.moving_up = False
        self.moving_down = False

    def update(self):
        """根据移动标志调整飞船的位置"""
        # 更新飞船的center值,而不是rect
        if self.moving_right and self.rect.right < self.screen_rect.right:
            self.centerx += self.ai_settings.ship_speed_factor
        if self.moving_left and self.rect.left > 0:
            self.centerx -= self.ai_settings.ship_speed_factor
        if self.moving_up and self.rect.top > self.screen_rect.top:
            self.centery -= self.ai_settings.ship_speed_factor
        if self.moving_down and self.rect.bottom < self.screen_rect.bottom:
            self.centery += self.ai_settings.ship_speed_factor

        # 根据self.center更新rect对象
        self.rect.centerx = self.centerx
        self.rect.centery = self.centery

这里需要注意的是,pygame窗口矩形的坐标(0,0)是左上角,x轴方向向右,y轴方向向下。

  • 修改game_functions模块的两个函数:
def check_keydown_events(event, ai_settings, screen, ship, bullets):
    """响应按键"""
    if event.key == pygame.K_RIGHT or event.key == pygame.K_d:
        ship.moving_right = True
    elif event.key == pygame.K_LEFT or event.key == pygame.K_a:
        ship.moving_left = True
    elif event.key == pygame.K_UP or event.key == pygame.K_w:
        ship.moving_up = True
    elif event.key == pygame.K_DOWN or event.key == pygame.K_s:
        ship.moving_down = True
    elif event.key == pygame.K_SPACE:
        fire_bullet(ai_settings, screen, ship, bullets)
    elif event.key == pygame.K_q:
        sys.exit()


def check_keyup_events(event, ship):
    """响应松开"""
    if event.key == pygame.K_RIGHT or event.key == pygame.K_d:
        ship.moving_right = False
    elif event.key == pygame.K_LEFT or event.key == pygame.K_a:
        ship.moving_left = False
    elif event.key == pygame.K_UP or event.key == pygame.K_w:
        ship.moving_up = False
    elif event.key == pygame.K_DOWN or event.key == pygame.K_s:
        ship.moving_down = False

如果你够仔细就可以发现,上面上下左右移动没有什么问题,但是当飞船向上移动撞上外星人时,飞船无法复位——回到底部居中,然后一直卡在原位置重复死亡。反复测试,改动或删除Ship类的center_ship()方法后再次运行游戏,都和不改动和不删除的效果一样,该方法并没有起到重置飞船位置的作用。

center_ship()方法代码如下:

    def center_ship(self):
        """让飞船在屏幕上居中"""
        self.rect.centerx = self.screen_rect.centerx
        self.rect.bottom = self.screen_rect.bottom

希望对此有深入理解的朋友可以帮我解答这个疑惑。总而言之,《外星人入侵》虽然是个小游戏,但对理解python类、对象及实例有很大帮助。


源代码及素材分享,提取码:h0pb

你可能感兴趣的:(#,Python小知识)