Python超复古小小小小小游戏项目(终)

本文详细介绍了使用Python和Pygame库开发一款简易的太空射击游戏的过程。从安装配置到游戏功能实现,包括飞船控制、子弹射击、外星人移动及碰撞检测等核心功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

安装

我用的Python3.5。

3.4版本之后去

http://www.lfd.uci.edu/~gohlke/pythonlibs/#pygame
https://pypi.python.org/pypi/Pygame

这两个网址去下载,推荐第二个,第二个清楚。

注意对应版本,我下载的是pygame-1.9.3-cp35-cp35m-win_amd64.whl,网上好多是讲Python2.*版本的,讲的太简单了,一点都不清楚,直接略过安装,难受。

下载下来是一个.whl文件,将它放在项目的根目录下,然后在dos命令窗口中,切换到项目目录,运行下列代码:

python -m pip install --user pygame-1.9.3-cp35-cp35m-win_amd64.whl

下载了哪个版本,后面的文件名就用哪个版本,不一定和我的相同。

这里写图片描述

然后创建一个文件:

import pygame
print(pygame.ver)

输出:

1.9.3

开始

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

创建一个空的Pygame窗口。

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")

    #开始游戏的主循环
    while True:
        #监视键盘和鼠标事件
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                sys.exit()
        #让最近绘制的屏幕可见
        pygame.display.flip()

run_game()

pygame.init()

初始化背景设置,让Pygame能正确地工作。

pygame.display.set_mode()

创建一个游戏窗口的尺寸,整个游戏的所有图形元素都将在其中绘制。

实参(1200, 800)是一个元组,指定了窗口的尺寸。

对象screen是一个surface对象。

在Pygame中,surface是屏幕的一部分,用于显示游戏元素。

在这个游戏中,每个元素(如外星人或飞船)都是一个surface

pygame.display.set_mode()返回的surface表示整个游戏窗口,每一次循环都将自动绘制这个surface

pygame.display.set_caption()

设定游戏的名字,名字会在窗口左上角显示。

事件(event)

事件是用户玩游戏时执行的操作,如按键或移动鼠标。

为了让程序响应事件,编写一个事件循环,以侦听事件,并根据相应的事件执行相应的任务。

pygame.event.get()

获得Pygame检测到的事件。

pygame.QUIT

当我玩剑单击游戏的关闭按钮时,将检测到pygame.QUIT事件,根据此事件,调用sys.exit()来退出游戏。

pygame.display.flip()

命令Pygame让最近绘制的屏幕可见。

每次执行一次while循环都换绘制一个空屏幕,并擦去就屏幕,使得只有新屏幕可见。

我们在移动游戏元素时,pygame.display.flip()将会不断更新屏幕,以显示元素的新位置,并在原来位置隐藏元素,营造平滑移动的效果。

设置背景颜色

--snip--
def run_game():
    --snip--
    pygame.display.set_caption("Alien Invasion")

    # 设置背景颜色
    bg_color = (0, 0, 15)

   --snip--

        # 重绘屏幕
        screen.fill(bg_color)

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


run_game()
颜色

在Pygame中,颜色是以RGB值指定的。

创建一个颜色元组,在while循环前指定一次就好。

screen.fill(bg_color)

用背景色填充屏幕,只接受一个实参:一种颜色。

创建设置类

编写一个名为settings的类,其中包含一个,名为Settings的类,用于将设置储存在一个地方,以免在代码中到处添加设置。

折让函数调用更加简单,且在项目增大时修改游戏的外观更容易。

settings.py

class Settings():
    """储存游戏所有的设置"""

    def __init__(self):
        self.screen_width = 1200
        self.screen_height = 800
        self.bg_color = (0, 0, 15)
        self.caption = "Alien Invasion 1.0"

alien_invasion.py

--snip--
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(ai_settings.caption)

    --snip--
        # 重绘屏幕
        screen.fill(ai_settings.bg_color)
        # 让最近绘制的屏幕可见
        pygame.display.flip()


run_game()

添加飞船图像

这里写图片描述
这里写图片描述
这里写图片描述
书上的图片丑的无法接受,自己找了好久,在这个网址找到的:
6m5m.com游戏素材资源共享平台

这个图片是png格式的,背景是透明的,转换成bmp即可。

在线图片格式转换
这个网址可以保留图片的透明背景,非常好。

在线图片切割
这个也不错。

创建ship类

ship.py

import pygame

class Ship():

    def __init__(self, screen):
        """初始化飞船并设置其初始位置"""

        self.screen = screen

        # 加载飞船图像并获取其外接矩形
        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

    def blitme(self):
        """指定位置绘制飞船"""
        self.screen.blit(self.image, self.rect)

构造方法中有一个screen参数,制定了要将飞船绘制到什么地方。

pygame.image.load()

加载图像,返回一个飞船的surface对象。

rect对象

rect这个对象是用来存储成对出现的参数,比如,一个矩形框的左上角坐标、宽度和高度,RECT结构通常用于Windows编程。
———摘自百度百科

处理rect对象时,可以使用矩形四角和中心的 x y坐标。可通过设置这些纸来指定矩形的位置。

get_rect()

获取相应surface的属性rect

Pygame的效率之所以如此之高,一个原因是它能够像处理矩形(rect对象)一样处理游戏元素,即使它们的形状并非矩形。(诶哟不错哦)

rect对象属性
属性意义
centerx水平(X轴)中点坐标
centery垂直(Y轴)中点坐标
center矩形(rect对象)中点坐标
top顶部(Y轴)坐标
bottom底部(Y轴)坐标
left左边缘(X轴)坐标
right右边缘(X轴)坐标
blit()

接受一个surface对象和一个rect对象作为参数 。

在屏幕上的指定位置绘制图像。

这里写图片描述

重构:模块game_functions

函数check_events()

驾驶飞船

响应按键

每次按键都被注册为KETDOWN事件。

检测到KEYDOWN事件时,要判断按下的是否是特定的键。

例如,按下的如果是右箭头,就让飞船的centerx属性增加。

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_RIGHT:
                ship.rect.centerx += 1
            elif event.key == pygame.K_LEFT:
                ship.rect.centerx -= 1
            elif event.key == pygame.K_UP:
                ship.rect.centery -= 1
            elif event.key == pygame.K_DOWN:
                ship.rect.centery += 1

可以移动了,但是,只能按一下,动一下。

允许不断移动

可以设置一个移动标志,为布尔型,若为True则按指定方向移动,按下指定键时变为Ture放开时变为False

ship.py

--snip--
    def __init__(self, screen):
        """初始化飞船并设置其初始位置"""
        --snip--
        # 移动标志
        self.moving_right = False
        self.moving_left = False
        self.moving_up = False
        self.moving_down = False

    def update(self):
        """根据移动标志调整飞船位置"""
        if self.moving_down:
            self.rect.centery += 1
        if self.moving_up:
            self.rect.centery -= 1
        if self.moving_right:
            self.rect.centerx += 1
        if self.moving_left:
            self.rect.centerx -= 1

        --snip--

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_RIGHT:
                ship.moving_right = True
            elif event.key == pygame.K_LEFT:
                ship.moving_left = True
            elif event.key == pygame.K_UP:
                ship.moving_up = True
            elif event.key == pygame.K_DOWN:
                ship.moving_down = True

        elif event.type == pygame.KEYUP:
            if event.key == pygame.K_RIGHT:
                ship.moving_right = False
            elif event.key == pygame.K_LEFT:
                ship.moving_left = False
            elif event.key == pygame.K_UP:
                ship.moving_up = False
            elif event.key == pygame.K_DOWN:
                ship.moving_down = False

alien_invasion.py

    while True:
        # 监视键盘和鼠标事件
        gf.check_events(ship)
        ship.update()
        gf.update(ai_settings, screen, ship)

但是这里有个缺陷,如果我先按右键再按左键,它便停下不动了,作为一个游戏爱好者这不能让人接受。

这里写图片描述

所以我想稍微改动一下。
Ship()类加入self.moving_right_step属性。
ship.py

        # 移动像素
        self.moving_right_step = 1
        self.moving_left_step = 1
        self.moving_up_step = 1
        self.moving_down_step = 1

当按下右键之后,再按下左键,左键的self.moving_left变为Trueself.moving_left_step
赋值为2。相对的看起来,加12就是向左正常移动,要的是效果嘛。

若接下来再松开左键,self.moving_left_step赋值为1self.moving_left变为False

但是,如果接下来我松开的是右键,那么我不仅要做上述的这些,而且,self.moving_left_step已经被赋值为2了,此时要检查self.moving_left是否为True,然后若为True,就将self.moving_left_step也赋值为1,避免让飞船出现突然加速的bug。

实现顺利。

秘技.左右横跳!!!

这里写图片描述

限制飞船活动范围

为了避免这个飞船飞走,需要限制它的行动。

也将飞船的速度作为设置之一。
settings.py

--snip--
       self.ship_speed_factor = 1

ship.py

    def update(self):
        """根据移动标志调整飞船位置"""
        if self.moving_down and self.rect.bottom < self.screen_rect.bottom:
            self.rect.centery += self.moving_down_speed
        if self.moving_up and self.rect.top > 0:
            self.rect.centery -= self.moving_up_speed
        if self.moving_right and self.rect.right < self.screen_rect.right:
            self.rect.centerx += self.moving_right_speed
        if self.moving_left and self.rect.left > 0:
            self.rect.centerx -= self.moving_left_speed

重构check_events()

game_functions.py

import sys

import pygame


def check_keydown_events(event, ship, ai_settings):
    """响应按键"""
    if event.key == pygame.K_RIGHT:
        ship.moving_right = True
        if ship.moving_left:
            ship.moving_right_speed = 2 * ai_settings.ship_speed_factor
    elif event.key == pygame.K_LEFT:
        ship.moving_left = True
        if ship.moving_right:
            ship.moving_left_speed = 2 * ai_settings.ship_speed_factor
    elif event.key == pygame.K_UP:
        ship.moving_up = True
        if ship.moving_down:
            ship.moving_up_speed = 2 * ai_settings.ship_speed_factor
    elif event.key == pygame.K_DOWN:
        ship.moving_down = True
        if ship.moving_up:
            ship.moving_down_speed = 2 * ai_settings.ship_speed_factor

def check_keyup_events(event, ship, ai_settings):
    """响应松开"""
    if event.key == pygame.K_RIGHT:
        ship.moving_right = False
        ship.moving_right_speed = ai_settings.ship_speed_factor
        if ship.moving_left:
            ship.moving_left_speed = ai_settings.ship_speed_factor
    elif event.key == pygame.K_LEFT:
        ship.moving_left = False
        ship.moving_left_speed = ai_settings.ship_speed_factor
        if ship.moving_right:
            ship.moving_right_speed = ai_settings.ship_speed_factor
    elif event.key == pygame.K_UP:
        ship.moving_up = False
        ship.moving_up_speed = ai_settings.ship_speed_factor
        if ship.moving_down:
            ship.moving_down_speed = ai_settings.ship_speed_factor
    elif event.key == pygame.K_DOWN:
        ship.moving_down = False
        ship.moving_down_speed = ai_settings.ship_speed_factor
        if ship.moving_up:
            ship.moving_up_speed = ai_settings.ship_speed_factor


def check_events(ship, ai_settings):
    """响应按键和鼠标事件"""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            check_keydown_events(event, ship, ai_settings)

        elif event.type == pygame.KEYUP:
            check_keyup_events(event, ship, ai_settings)


def update(ai_settings, screen, ship):
    """更新屏幕上的图像 , 并切换到新屏幕"""
    # 重绘屏幕
    screen.fill(ai_settings.bg_color)
    ship.blitme()
    # 让最近绘制的屏幕可见
    pygame.display.flip()

射击

好的,要开始buibuibui了。

创建Bullet类

bullet.py

import pygame
from pygame.sprite import Sprite


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


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

        # 在(0, 0)处创建一个表示子弹的矩形,再设置正确的位置
        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 = ai_settings.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)

将子弹存储到编组中

alien_invasion.py

    # 储存子弹的编组
    bullets = Group()

    # 开始游戏的主循环
    while True:
        # 监视键盘和鼠标事件
        gf.check_events(ship, ai_settings, screen, bullets)
        ship.update()
        gf.update_screen(ai_settings, screen, ship, bullets)

当对编组bullets调用update()时,将对其中的每个精灵(子弹)调用update()

开火

无需修改check_keyup_events(),空格键松开什么都不会发生。

game_functions.py

    --snip--
    elif event.key == pygame.K_SPACE:
        # 创建一颗子弹,并将其加入到编组bullets中
        new_bullet = Bullet(ai_settings, screen, ship)
        bullets.add(new_bullet)

        --snip--
        elif event.type == pygame.KEYDOWN:
            check_keydown_events(event, ship, ai_settings, screen, bullets)


def update_screen(ai_settings, screen, ship, bullets):
    """更新屏幕上的图像 , 并切换到新屏幕"""
    # 重绘屏幕
    screen.fill(ai_settings.bg_color)
    # 绘制飞船
    ship.blitme()
    # 绘制子弹
    for bullet in bullets.sprites():
        bullet.draw_bullet()
    # 让最近绘制的屏幕可见
    pygame.display.flip()

alien_invasion.py

    while True:
        # 监视键盘和鼠标事件
        gf.check_events(ship, ai_settings, screen, bullets)
        ship.update()
        bullets.update()
        gf.update_screen(ai_settings, screen, ship, bullets)

删除已消失的子弹

子弹到达屏幕外部,但并没有消失,还占用着内存和处理器,所以,让它消失。

    while True:
        # 监视键盘和鼠标事件
        gf.check_events(ship, ai_settings, screen, bullets)
        ship.update()
        bullets.update()

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

限制子弹数量

不存在的,我不,这是我的游戏。

这里写图片描述

创建函数update_bullets()

为了让主程序尽量简单,将子弹管理部分作为一个函数。
game_functions.py

def update_bullets(bullets):
    # 更新子弹位置
    bullets.update()
    # 删除已消失的子弹
    for bullet in bullets.copy():
        if bullet.rect.bottom <= 0:
            bullets.remove(bullet)

alien_invasion.py

    while True:
        # 监视键盘和鼠标事件
        gf.check_events(ship, ai_settings, screen, bullets)
        ship.update()
        gf.update_bullets(bullets)
        gf.update_screen(ai_settings, screen, ship, bullets)

创建函数fire_bullet()

开火也作为一个函数。

game_functions.py

    elif event.key == pygame.K_SPACE:
        fire_bullets(bullets, ai_settings, screen, ship)


--snip--
def fire_bullets(bullets, ai_settings, screen, ship):
    # 创建一颗子弹,并将其加入到编组bullets中
    new_bullet = Bullet(ai_settings, screen, ship)
    bullets.add(new_bullet)

射击个人优化

这个游戏只能按一下,发一颗子弹,作为一名游戏爱好者,这让我不能接受。

这里写图片描述

按住空格,什么时候不松开,它就得一个劲给我buibuibui。

所以,设置一个射击开关shooting,为布尔型,这个开关作为Ship()类的属性,而不是Bullet()类的。

当其为True ,即按下空格时,不断射击。

并且修改上面刚创建的那个函数,怎么叫个开火函数?应该是装弹函数啊,给飞船装弹,bullets.update()才是发射。

ship.py

import pygame

from bullet import Bullet


class Ship():
    def __init__(self, screen, ai_settings):

    --snip--
        # 射击标志
        self.shooting = False

    def update(self, bullets, ai_settings, screen, ship):

        # 射击
        if self.shooting:
            self.add_bullets(bullets, ai_settings, screen, ship)

    def add_bullets(self, bullets, ai_settings, screen, ship):
        # 创建一颗子弹,并将其加入到编组bullets中
        new_bullet = Bullet(ai_settings, screen, ship)
        bullets.add(new_bullet)

将装弹,添加到Ship()类中。
alien_invasion.py

    while True:
        --snip--
        ship.update(bullets, ai_settings, screen, ship)
        --snip--

结果,不尽人意,甚至非常恶心的一大条:

这里写图片描述

我不服,再想。

可以这样,当最后一个发射的子弹的底部的坐标距离飞船的顶部坐标为3像素的时候,才添加,而且这个应该可以读取到最后一个。

查阅官方文档,发现:
这里写图片描述
之前用的这个函数返回的是一个列表,那就好办了啊。

        # 射击
        if self.shooting and (len(bullets.sprites()) == 0
                              or ship.rect.top - bullets.sprites()[-1].rect.bottom >= 20):
            self.add_bullets(bullets, ai_settings, screen, ship)

结果依然不尽人意,更恶心了。

可能计算机有延迟,子弹太多太快了,处理不过来,所以。

第二个解决方案,让计算机延迟执行子弹添加函数。

导入模块time
ship.py

        # 射击
        if self.shooting:
            self.add_bullets(bullets, ai_settings, screen, ship)
            time.sleep(0.01)

结果依然非常恶心:

这里写图片描述

那个sleep()把整个程序都变慢了,单纯的变成了慢动作而已。

这里写图片描述

再想办法。

既然每次循环是“1帧”,那何不设置一个记录帧数的变量,这个变量应该在主程序while循环之前就有,命名为ticks
alien_invasion.py

    # 计数器
    ticks = 0

    # 开始游戏的主循环
    while True:
        # 控制游戏帧率
        ticks += 1

        if ticks >= ai_settings.animate_cycle:
            ticks = 0
        # 监视键盘和鼠标事件
        gf.check_events(ship, ai_settings, screen, bullets)
        ship.update(bullets, ai_settings, screen, ship, ticks)
        gf.update_bullets(bullets)
        gf.update_screen(ai_settings, screen, ship, bullets)

shipupdate()函数添加一个新参数ticks
ship.py

        # 射击
        if self.shooting:
            if ticks % 10 == 0:
                self.add_bullets(bullets, ai_settings, screen, ship)

见证奇迹的时刻到了:
这里写图片描述

爽。

外星人

创建第一个外星人

创建Alien类

自己找的外星人的图片:

这里写图片描述

是不是有点熟悉,没错,如果你是《赛尔号》的骨灰老玩家,那你当年就应该体会过被它支配的恐怖,大BOSS————尤纳斯!!!!

alien.py

import pygame
from pygame.sprite import Sprite

class Alien(Sprite):
    """外星人 类"""

    def __init__(self, ai_settings, screen):
        """初始化外星人并设置其起始位置"""
        # 类似Ship类的设置
        super().__init__()

        self.screen = screen
        self.ai_settings = ai_settings
        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)

game_functions.py

def update_screen(ai_settings, screen, ship, bullets, alien):
    """更新屏幕上的图像 , 并切换到新屏幕"""
    # 重绘屏幕
    screen.fill(ai_settings.bg_color)
    # 绘制飞船
    ship.blitme()
    # 绘制外星人
    alien.blitme()
    # 绘制子弹
    for bullet in bullets.sprites():
        bullet.draw_bullet()
    # 让最近绘制的屏幕可见
    pygame.display.flip()

alien_invasion.py

    # 创建外星人实例
    alien = Alien(ai_settings, screen)

这里写图片描述

创建一群外星人

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

需要在屏幕两端留下边距,边距长与外星人宽度相等。

available_space_x = ai_settings.screen_width - (2 * alien_width)

每个外星人占据的空间应该是一个外星人的宽度的两倍,一个宽度为外星人的,一个为外星人右边的空白区域:

number_aliens_x = available_space_x / (2 * alien_width)

创建多行外星人

alien_invasion.py

    # 创建外星人群
    gf.create_fleet(ai_settings, screen, aliens)
    # 开始游戏的主循环
    while True:
        # 控制游戏帧率
        ticks += 1
        aliens.draw(screen)
        if ticks >= ai_settings.animate_cycle:
            ticks = 0
        # 监视键盘和鼠标事件
        gf.check_events(ship, ai_settings, screen, bullets)
        ship.update(bullets, ai_settings, screen, ship, ticks)
        gf.update_bullets(bullets)
        gf.update_screen(ai_settings, screen, ship, bullets, aliens)

game_functions.py

def update_screen(ai_settings, screen, ship, bullets, aliens):
    """更新屏幕上的图像 , 并切换到新屏幕"""
    # 重绘屏幕
    screen.fill(ai_settings.bg_color)
    # 绘制飞船
    ship.blitme()
    # # 绘制外星人
    aliens.draw(screen)

创建外星人群

game_functions.py

def create_fleet(ai_settings, screen, aliens):
    """创建外星人群"""
    # 创建一个外星人,并计算一行可以容纳多少外星人
    # 外星人间距为外星人宽度
    new_alien = Alien(ai_settings, screen)
    available_space_x = ai_settings.screen_width - (2 * new_alien.rect.width)
    number_aliens_x = int(available_space_x / (2 * new_alien.rect.width))
    for alien_number in range(0, number_aliens_x):
        # 创建一个外星人并加入当前行
        alien = Alien(ai_settings, screen)
        print(type(alien.x))
        alien.x = alien.rect.width * (2 * alien_number + 1)
        alien.rect.x = alien.x
        aliens.add(alien)

效果:

这里写图片描述

重构create_fleet()

game_functions.py

def create_alien(ai_settings, screen, aliens, alien_number, number_row):
    # 创建一个外星人并加入当前行
    alien = Alien(ai_settings, screen)
    alien.x = alien.rect.width * (2 * alien_number + 1)
    alien.y = alien.rect.height * 2 * number_row
    alien.rect.x = alien.x
    alien.rect.y = alien.y
    aliens.add(alien)


def get_number_rows(ai_settings, screen, new_alien, ship):
    available_space_y = ai_settings.screen_height - ship.rect.width - new_alien.rect.height
    return int(available_space_y / (2 * new_alien.rect.height))


def create_fleet(ai_settings, screen, aliens, ship):
    """创建外星人群"""
    new_alien = Alien(ai_settings, screen)
    number_aliens_x = get_number_aliens(ai_settings, screen, new_alien)
    number_rows = get_number_rows(ai_settings, screen, new_alien, ship)
    for number_row in range(number_rows):
        for alien_number in range(number_aliens_x):
            create_alien(ai_settings, screen, aliens, alien_number, number_row)

因为外星人有点稍微的大所以结果有点不尽人意:

这里写图片描述

排列得太整齐了,作为一个游戏爱好者,这不能让人接受。

随机出现,也可说成,随机“不”出现,这个就好办多了:
game_functions.py

import sys
from random import randint

--snip--

def create_fleet(ai_settings, screen, aliens, ship):
    """创建外星人群"""
    new_alien = Alien(ai_settings, screen)
    number_aliens_x = get_number_aliens(ai_settings, screen, new_alien)
    number_rows = get_number_rows(ai_settings, screen, new_alien, ship)
    for number_row in range(number_rows):
        for alien_number in range(number_aliens_x):
            create_alien(ai_settings, screen, aliens, alien_number, number_row)
    # 随机删除
    for alien in aliens.copy():
        if len(aliens.sprites()) > 6 and randint(0,1):
            aliens.remove(alien)

顺便设置下限,不能让他把所有的都给我删了,不然打谁?

效果很棒:

这里写图片描述

randint()

使用前导入random模块。

randint(a, b)返回ab之间的随机数,包括ab

源码如下:

def randint(self, a, b):
        """Return random integer in range [a, b], including both end points.
        """

        return self.randrange(a, b+1)

including both end pointsrandrange(a, b+1)已经很清楚了,不再赘述。

让外星人移动

alien.py

    def check_edge(self):
        """如果触碰边缘返回True"""
        if self.rect.right == self.screen.get_rect().right or \
                        self.rect.left == self.screen.get_rect().left:
            return True

game_functions.py

def check_fleet_edges(ai_settings, aliens):
    """有外星人到达边缘的措施"""
    for alien in aliens.sprites():
        if alien.check_edge():
            change_fleet_direction(ai_settings, aliens)
            break


def change_fleet_direction(ai_settings, aliens):
    """改变外星人群的运动方向"""
    for alien in aliens.sprites():
        alien.y += ai_settings.fleet_drop_speed
        alien.rect.y = alien.y
    ai_settings.fleet_direction *= -1


def update_aliens(ai_settings, aliens):
    """更新外星人群的位置"""
    check_fleet_edges(ai_settings, aliens)
    aliens.update(ai_settings)

alien_invasion.py

        # 更新外星人状态
        gf.update_aliens(ai_settings, aliens)

下降速度为1,太小了,但若是设置的大了些,就会很突兀,蹦的一下下来一大截,作为一个游戏爱好者,这不能让人接受。

实现思路:
在设置里加一个下落的标志self.drop = False,若为真就“只”下落,不左右移动。
settings.py

# 下落标志
        self.drop = False
        # 下落开始时间(帧数)点
        self.drop_ticks = 0
        # 下落时间(帧数)
        self.drop_cycle = 60

alien.py

    def update(self, ai_settings):
        if ai_settings.drop:
            self.y += self.ai_settings.fleet_drop_speed
            self.rect.y = self.y
        else:
            self.x += self.ai_settings.alien_speed_factor * ai_settings.fleet_direction
            self.rect.x = self.x

在外星人到达边缘时,判断外星人是否正在下降,若正在下降并且到达下降时间,就停止下降,并改变左右移动方向,开始左右移动。

若是刚碰到边缘,就将下降标志self.drop = True,记好时间点ticks,作为函数change_fleet_direction()实参,参数ticks默认为0,是可选参数,避免了多写一个函数的麻烦。

若正在下降,且还没下降完全,就不做改变,接着下降。

game_functions.py

def check_fleet_edges(ai_settings, aliens, ticks):
    """有外星人到达边缘的措施"""
    for alien in aliens.sprites():
        if alien.check_edge():
            if ai_settings.drop and \
                    (ticks - ai_settings.drop_ticks) % ai_settings.drop_cycle == 0:
                change_fleet_direction(ai_settings, aliens)
                break
            elif ai_settings.drop is False:
                change_fleet_direction(ai_settings, aliens, ticks)
                break


def change_fleet_direction(ai_settings, aliens, ticks=0):
    """改变外星人群的运动方向"""
    if ticks:
        ai_settings.drop = True
        ai_settings.drop_ticks = ticks
    else:
        ai_settings.drop = False
        ai_settings.fleet_direction *= -1


def update_aliens(ai_settings, aliens, ticks):
    """更新外星人群的位置"""
    check_fleet_edges(ai_settings, aliens, ticks)
    aliens.update(ai_settings)

效果很满意:
这里写图片描述

射杀外星人

终于到了该buibuibui时候了。

检测子弹与外星人的碰撞

game_functions.py

def update_bullets(bullets, aliens):
    # 更新子弹位置
    bullets.update()
    # 删除已消失的子弹
    for bullet in bullets.copy():
        if bullet.rect.bottom <= 0:
            bullets.remove(bullet)
    # 检查是否有子弹击中了外星人
    # 若击中了,删除相应的子弹和外星人
    collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)

alien_invasion.py

    while True:
        # 计数
        ticks += 1
        aliens.draw(screen)

        # 监视键盘和鼠标事件
        gf.check_events(ship, ai_settings)
        # 更新飞船状态
        ship.update(bullets, ai_settings, screen, ship, ticks)
        # 更新子弹状态
        gf.update_bullets(bullets, aliens)

太无敌了,一会儿就死完了。

pygame.sprite.groupcollide()

将每颗子弹的rect与每个外星人的rect进行比较,并返回一个字典。

在这个字典中,每个键都是第一个参数组 中的精灵(子弹),而相应的值都是第二个参数组中的精灵(外星人)。

第三第四个参数为布尔型,第三个对应第一个参数组,第四个对应第二个参数组。

若位真True,则碰撞后对应参数组的精灵消失,反之同理。

生成新的外星人

外星人那么可怜,那就多造一些吧。

game_functions.py

def check_bullet_alien_collision(ai_settings, screen, ship, aliens, bullets):
    """检查是否有子弹击中了外星人
        若击中了,删除相应的子弹和外星人
    """
    collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)

    if len(aliens) == 0:
        bullets.empty()
        create_fleet(ai_settings, screen, aliens, ship)


def update_bullets(ai_settings, screen, ship, bullets, aliens):
    # 更新子弹位置
    bullets.update()
    # 删除已消失的子弹
    for bullet in bullets.copy():
        if bullet.rect.bottom <= 0:
            bullets.remove(bullet)

    check_bullet_alien_collision(ai_settings, screen, ship, aliens, bullets)

不好意思,我依旧无敌,此游戏毫无平衡性可言。

这里写图片描述

结束游戏

好歹让我输一次啊。

检测外星人和飞船碰撞

game_functions.py

def update_aliens(ai_settings, aliens, ticks, ship):
    """更新外星人群的位置"""
    check_fleet_edges(ai_settings, aliens, ticks)
    aliens.update(ai_settings)

    # 检测外星人与飞船的碰撞
    if pygame.sprite.spritecollideany(ship, aliens):
        print("!!!!!")
pygame.sprite.spritecollideany()

这个函数看名字就是sprite-collide-any

接受两个实参:一个精灵和一个编组。

当然,虽然ship没有继承Sprite类,但是,只要有其中的rect属性就行。

它检查编组是否有成员与精灵发生了碰撞,找到第一个它找到的第一个与飞船发生了碰撞的成员后停止遍历数组。

返回第一个与精灵(飞船)碰撞的编组中的成员(外星人)。

响应外星人和飞船碰撞

编写GameStatus类

game_stats.py

class GameStats():
    """统计游戏各类信息"""
    def __init__(self, ai_settings):
        """初始化统计信息"""
        self.ai_settings = ai_settings
        self.reset_stats()

    def reset_stats(self):
        """初始化运行期间可能变化的统计信息"""
        self.ship_left = self.ai_settings.ship_limit

game_functions.py

def ship_hit(ai_settings, screen, ship, aliens, bullets, stats):
    """响应飞船遭到碰撞后的事件"""
    # 将ship_left 减一
    stats.ship_left -= 1

    # 清空所有子弹和外星人
    aliens.empty()
    bullets.empty()

    # 创建新的外星人群,并将飞船至于中央
    create_fleet(ai_settings, screen, aliens, ship)
    ship.center_ship()

    # 暂停
    sleep(0.5)

ship.py

    def center_ship(self):
        self.rect.centerx = self.screen_rect.centerx
        self.rect.bottom = self.screen_rect.bottom

有外星人到达底部

类比之前检查外星人到达边缘。

game_functions.py

def check_aliens_bottom(ai_settings, screen, aliens, bullets, stats, ship):
    """有外星人到达底部的措施"""
    for alien in aliens.sprites():
        if alien.rect.bottom >= screen.get_rect().bottom:
            ship_hit(ai_settings, screen, ship, aliens, bullets, stats)
            break

与撞到飞船时的效果相同。

小bug修复

之前做的外星人平滑下降的功能,有个bug,确切的说,是两个。

第一个:

若在外星人下降时将触碰边缘的外星人消灭,外星人会一直下降,就算将其全部消灭也无济于事。

这里写图片描述

bug重现还是也是一件技术活啊,试了好几次,手残。

看代码:

game_functions.py

def check_fleet_edges(ai_settings, aliens, ticks):
    """有外星人到达边缘的措施"""
    for alien in aliens.sprites():
        if alien.check_edge():
            if ai_settings.drop and \
                    (ticks - ai_settings.drop_ticks) % ai_settings.drop_cycle == 0:
                change_fleet_direction(ai_settings)
                break
            elif ai_settings.drop is False:
                change_fleet_direction(ai_settings, ticks)
                break

所有的方向改变的行为都是基于一个前提————触碰边缘,而控制下降的是ai_settings.drop,我把边缘的外星人消灭了。

alien.check_edge()就不会返回True,第4行以下的代码一个也运行不了,根本改变不了ai_settings.drop(下降标志),其与外星人一直下降。

就算到达底部,重新刷新一群外星人,由于下降标志不变,而外星人与屏幕左右有空隙,依然下降,噩梦循环。

所以,错了,就得改:

def check_fleet_edges(ai_settings, aliens, ticks):
    """有外星人到达边缘的措施"""
    for alien in aliens.sprites():
        if alien.check_edge():
            if ai_settings.drop is False:
                change_fleet_direction(ai_settings, ticks)
                break
        if ai_settings.drop and \
                            (ticks - ai_settings.drop_ticks) % ai_settings.drop_cycle == 0:
            change_fleet_direction(ai_settings)
            break

将下落时的情况单独列出来,与“有外星人触碰边缘”事件并列,相互为独立事件,所以不用elif而用if

第二个:

当在下降途中外星人触碰到飞机或是屏幕底部,刷新后也会一直下降。

看代码:

game_functions.py

def create_fleet(ai_settings, screen, aliens, ship):
    """创建外星人群"""
    new_alien = Alien(ai_settings, screen)
    number_aliens_x = get_number_aliens(ai_settings, new_alien)
    number_rows = get_number_rows(ai_settings, new_alien, ship)
    for number_row in range(number_rows):
        for alien_number in range(number_aliens_x):
            create_alien(ai_settings, screen, aliens, alien_number, number_row)
    # 随机删除
    for alien in aliens.copy():
        if len(aliens.sprites()) > 6 and randint(0, 1):
            aliens.remove(alien)

刷新外星人群后,同样,一直下降,也是由于无法改变ai_settings.drop下降标志。

当然,加上了上例的代码,有所改善,因为保证了每次下降的距离都会有限制。

但碰撞后还会下降一小段距离,接着上次没下降完的那一部分。

这次让下降的距离长些,好观察:

这里写图片描述

知错能改,善莫大焉:

def create_fleet(ai_settings, screen, aliens, ship):
    """创建外星人群"""
    ai_settings.drop = False
    ai_settings.fleet_direction = 1

每次创建外星人群,都初始化其运动状态。

两个bug完美解决

为自己鼓掌,piapiapiapiapiapia。

这里写图片描述

游戏结束

输了就输了,大不了再来一次。

当飞机用完之后游戏结束。

GameStats类中添加属性game_active

game_stats.py

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

game_dunctions.py

def ship_hit(ai_settings, screen, ship, aliens, bullets, stats):
    """响应飞船遭到碰撞后的事件"""
    if stats.ship_left > 0:
        # 将ship_left 减一
        stats.ship_left -= 1
        --snip--

    else:
        stats.game_active = False

如果玩家用完了所有飞船,将game_active置为False,游戏结束。

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

alien_invasion.py

    while True:
        # 计数
        ticks += 1
        aliens.draw(screen)
        if stats.game_active:
            # 监视键盘和鼠标事件
            gf.check_events(ship, ai_settings)

        # 更新飞船状态
        ship.update(bullets, ai_settings, screen, ship, ticks)
            --snip--     

注意最后一条ship.update(bullets, ai_settings, screen, ship, ticks)是和if语句并列的。

飞船用完后,将会停止游戏。

记分

添加Play按钮

先让游戏一开始处于非活动状态。

game_stats.py

    def __init__(self, ai_settings):
        """初始化统计信息"""
        self.ai_settings = ai_settings
        self.reset_stats()
        # 游戏刚启动时处于非活动状态
        self.game_active = False

创建Button类

button.py

import pygame
from pygame.sprite import Sprite
import pygame.font


class Button():

    def __init__(self, ai_settings, screen, msg):
        """初始化按钮信息"""
        self.ai_settings = ai_settings
        self.screen = screen
        self.screen_rect = screen.get_rect()

        # 设置按钮尺寸以及其他信息
        self.width, self.height = 200, 50
        self.color = (0, 255, 0)
        self.text_color = (255, 255, 255)
        self.font = pygame.font.SysFont(None, 50)

        # 创建按钮的rect对象将其居中
        self.rect = pygame.Rect(0, 0, self.width, self.height)
        self.rect.center = self.screen_rect.center

        # 将msg渲染为图像,使其在按钮上居中
        self.mas_image = pygame.font.render(msg, True, self.text_color, self.color)
        self.msg_image_rect = self.mas_image.get_rect()
        self.msg_image_rect.center = self.rect.center

    def draw_button(self):
        """先绘出按钮,再绘制文本"""
        self.screen.fill(self.color, self.rect)
        self.screen.blit(self.mas_image, self.msg_image_rect)
pygame.font.SysFont()

官方文档:

create a Font object from the system fonts

SysFont(name, size, bold=False, italic=False) -> Font

Return a new Font object that is loaded from the system fonts. The font will match the requested bold and italic flags. If a suitable system font is not found this will fall back on loading the default pygame font. The font name can be a comma separated list of font names to look for.

就是返回一个字体对象,详细的就不翻译了,高中水平就能看得懂。

comma : 逗号

pygame.font.Font.render

draw text on a new Surface

render(text, antialias, color, background=None) -> Surface

This creates a new Surface with the specified text rendered on it. pygame provides no way to directly draw text on an existing Surface: instead you must use Font.render() to create an image (Surface) of the text, then blit this image onto another Surface.

返回了一个Surface对象。

antialias[‘æntɪ’eɪlɪəs]:抗锯齿
render:在这里翻译成“渲染”,不是传递,给予,表达 之类的……

若要问抗锯齿是什么?

这里写图片描述

放心我没那么狠,看这里看这里。

在屏幕上绘制按钮

game_functions.py

def update_screen(ai_settings, screen, ship, bullets, aliens, play_button, stats):
    """更新屏幕上的图像 , 并切换到新屏幕"""
    # 重绘屏幕
    screen.fill(ai_settings.bg_color)
    # 若游戏处于非活动状态,绘制按钮
    if not stats.game_active:
        play_button.draw_button()
    play_button.draw_button()
    # 绘制飞船
    ship.blitme()
    # 绘制外星人
    aliens.draw(screen)
    # 绘制子弹
    for bullet in bullets.sprites():
        bullet.draw_bullet()
    # 让最近绘制的屏幕可见
    pygame.display.flip()

开始游戏

添加鼠标事件:

game_functions.py

def check_events(ship, ai_settings, stats, play_button):
    """响应按键和鼠标事件"""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            check_keydown_events(event, ship, ai_settings)
        elif event.type == pygame.KEYUP:
            check_keyup_events(event, ship, ai_settings)
        elif event.type == pygame.MOUSEBUTTONDOWN:
            mouse_x, mouse_y = pygame.mouse.get_pos()
            check_play_button(stats, play_button, mouse_x, mouse_y)

def check_play_button(stats, play_button, mouse_x, mouse_y):
    """在玩家单击Play按钮时开始游戏"""
    if play_button.rect.collidepoint(mouse_x, mouse_y):
        stats.game_active = True

alien_invasion.py

        # 监视键盘和鼠标事件
        gf.check_events(ship, ai_settings, stats, play_button)

重置游戏

每次单击Play按钮,都要重置游戏,重置统计信息、删除现有的外星人和子弹、创建一群新的外星人,并让飞船居中。

game_functions.py

def check_play_button(stats, play_button, mouse_x, mouse_y, ship, ai_settings,
                      screen, aliens, bullets):
    """在玩家单击Play按钮时开始游戏"""
    if play_button.rect.collidepoint(mouse_x, mouse_y):
        # 重置游戏统计信息
        stats.reset_stats()
        stats.game_active = True

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

        # 创建新的外星人群
        create_fleet(ai_settings, screen, aliens, ship)
        ship.center_ship()

将Play切换到非活动状态

存在一个问题,即:即使Play按钮不可见,玩家单击其原来所在的区域时,游戏自然会作出响应。游戏开始后,如果玩家不小心单击了Play按钮原来所处的区域,游戏奖重新开始。

可设置为,只有游戏在`game_activeFalse时,单击Play才有效。

game_functions.py

def check_play_button(stats, play_button, mouse_x, mouse_y, ship, ai_settings,
                      screen, aliens, bullets):
    """在玩家单击Play按钮时开始游戏"""
    if play_button.rect.collidepoint(mouse_x, mouse_y) and not stats.game_active:
        # 重置游戏统计信息
        stats.reset_stats()
        stats.game_active = True
        --snip--

隐藏光标

开始游戏后让光标隐藏,结束后再出现。

def ship_hit(ai_settings, screen, ship, aliens, bullets, stats):
    """响应飞船遭到碰撞后的事件"""
    if stats.ship_left > 0:
        --snip--
    else:
        stats.game_active = False
        # 显示光标
        pygame.mouse.set_visible(True)


def check_play_button(stats, play_button, mouse_x, mouse_y, ship, ai_settings,
                      screen, aliens, bullets):
    """在玩家单击Play按钮时开始游戏"""
    if play_button.rect.collidepoint(mouse_x, mouse_y) and not stats.game_active:
        # 隐藏光标
        pygame.mouse.set_visible(False)
        --snip--

提高等级

就这么玩下去没有什么难度,每消灭一群,都会提高外星人的难度。

修改飞船速度

settings.py

class Settings():
    """储存游戏所有的设置"""

    --snip--
        # 加快游戏节奏的节奏
        self.speedup_scale = 1.1
        self.initialiize_dynamic_settings()

    def initialiize_dynamic_settings(self):
        # 飞船速度
        self.ship_speed_factor = 1
        # 外星人左右移动速度设置
        self.alien_speed_factor = 0.5
        # 下落速度
        self.fleet_drop_speed = 0.5
        # 下落时间(帧数)
        self.drop_cycle = 60

        # 动画周期
        self.animate_cycle = 60

    def increase_speed(self):
        """提高速度设置"""
        self.ship_speed_factor *= self.speedup_scale
        # 外星人左右移动速度设置
        self.alien_speed_factor *= self.speedup_scale
        # 下落速度
        self.fleet_drop_speed *= self.speedup_scale
        # 下落时间(帧数)
        self.drop_cycle *= self.speedup_scale

        # 动画周期
        self.animate_cycle *= self.speedup_scale

game_functions.py

def check_bullet_alien_collision(ai_settings, screen, ship, aliens, bullets):
    """检查是否有子弹击中了外星人
        若击中了,删除相应的子弹和外星人
    """
    collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)

    if len(aliens) == 0:
        bullets.empty()
        ai_settings.increase_speed()
        create_fleet(ai_settings, screen, aliens, ship)

重置速度

game_functions.py

def ship_hit(ai_settings, screen, ship, aliens, bullets, stats):
    """响应飞船遭到碰撞后的事件"""
    if stats.ship_left > 0:
        --snip--
    else:
        stats.game_active = False
        ai_settings.initialiize_dynamic_settings()
        # 显示光标
        pygame.mouse.set_visible(True)

记分

GameStats中添加score属性:
game_stats.py

    def __init__(self, ai_settings):
        """初始化统计信息"""
        self.ai_settings = ai_settings
        self.reset_stats()
        # 游戏刚启动时处于非活动状态
        self.game_active = False

        # 得分
        self.score = 0

显示得分

首先创建一个新类ScoreBoard,类似Button

score_board.py

import pygame.font


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 = (50, 50, 50)
        self.font = pygame.font.SysFont(None, 50, True)

        # 准备初始得分图像

    def prep_score(self):
        score_str = str(self.ai_settings.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.score_rect.right - 20
        self.score_rect.top = 20

    def show_score(self):
        """在屏幕上显示得分"""
        self.screen.blit(self.score_image, self.score_rect)

创建记分牌

game_functions.py

def update_screen(ai_settings, screen, ship, bullets, aliens, play_button, stats, sb):
    """更新屏幕上的图像 , 并切换到新屏幕"""
    # 重绘屏幕
    screen.fill(ai_settings.bg_color)
    # 显示分数
    sb.show_score()
    # 若游戏处于非活动状态,绘制按钮
    if not stats.game_active:
        play_button.draw_button()

    # 绘制飞船
    ship.blitme()
    --snip--

alien_invasion.py

    # 创建记分牌
    sb = ScoreBoard(ai_settings, screen, stats)
    --snip--
        # 更新屏幕
        gf.update_screen(ai_settings, screen, ship, bullets, aliens,
                         play_button, stats, sb)

在外星人被消灭时更新得分

settings.py

    def __init__(self):
        --snip--
        # 外星人点数
        self.alien_points = 50
    def increase_speed(self):
        --snip--
        # 外星人点数
        self.alien_points = int(self.speedup_scale * self.alien_points)

game_functions.py

def check_bullet_alien_collision(ai_settings, screen, ship, aliens, bullets, stats, sb):
    """检查是否有子弹击中了外星人
        若击中了,删除相应的子弹和外星人
    """
    collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
    if collisions:
        for aliens in collisions.values():
            stats.score += ai_settings.alien_points * len(aliens)
            sb.prep_score()

    --snip--

最高得分

score_board.py

    def show_score(self):
        """在屏幕上显示得分"""
        self.screen.blit(self.score_image, self.score_rect)
        self.screen.blit(self.high_score_image, self.high_score_rect)

    def prep_high_score(self):
        high_score_str = str(self.stats.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 = 60

game_functions.py



def check_high_score(stats, sb):
    """检查是否出现了新的最高分"""
    if stats.score > stats.high_score:
        stats.high_score = stats.score
        sb.prep_high_score()

将最高分存入文件

game_functions.py

def check_high_score(stats, sb):
    """检查是否出现了新的最高分"""
    if stats.score > stats.high_score:
        stats.high_score = stats.score
        sb.prep_high_score()
        save_high_score(stats)

def save_high_score(stats):
    """将最高分保存入文件"""
    with open("high_score.json", 'w') as file:
        json.dump(stats.high_score, file)

game_stats.py

    --snip--
        # 得分
        self.score = 0
        self.high_score = 0
        self.get_high_score()

    def get_high_score(self):
        try:
            with open("high_score.json") as file:
                self.high_score = json.load(file)
        except FileNotFoundError:
            with open("high_score.json", 'w') as file:
                json.dump(self.high_score, file)

这样最高分就算重新开始游戏也不会从零开始了。

显示等级

game_stats.py

        # 难度等级
        self.level = 1

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

score_board.py

    def show_score(self):
        """在屏幕上显示得分"""
        self.screen.blit(self.score_image, self.score_rect)
        self.screen.blit(self.high_score_image, self.high_score_rect)
        self.screen.blit(self.level_image, self.level_rect)

    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.top = self.score_rect.bottom + 10
        self.level_rect.left = self.score_rect.left

game_functions.py

def check_bullet_alien_collision(ai_settings, screen, ship, aliens, bullets, stats, sb):
    """检查是否有子弹击中了外星人
        若击中了,删除相应的子弹和外星人
    """
    --snip--
        # 提高等级
        stats.level += 1
        sb.prep_level()
        --snip--

显示余下的飞船

ship_small.py

import pygame
from pygame.sprite import Sprite


class ShipSmall(Sprite):
    """小飞船"""
    def __init__(self, screen):
        super().__init__()
        self.screen = screen
        self.image = pygame.image.load('images/ship_small.bmp')
        self.rect = self.image.get_rect()
        self.rect.top = 10

因为之前的飞船的图片太大,又做了个小的,重写了一个类。

类似于子弹一样,有多少个话多少个,写一个编组,把小飞船放进去。

score_board.py

   def show_score(self):
        """在屏幕上显示得分"""
        self.screen.blit(self.score_image, self.score_rect)
        self.screen.blit(self.high_score_image, self.high_score_rect)
        self.screen.blit(self.level_image, self.level_rect)
        self.ships_small.draw(self.screen)

    def prep_ships(self):
        """显示还剩下多少飞船"""
        self.ships_small = Group()
        for ship_number in range(self.stats.ship_left):
            ship_small = ShipSmall(self.ai_settings)
            ship_small.rect.left = 10 + ship_number * ship_small.rect.width
            self.ships_small.add(ship_small)

其余的就和之前相似,在game_functions.pyalien_invasion.py中添加函数,以及参数即可,毫无难度可言。

完整代码展示

alien_invasion.py

import pygame
from pygame.sprite import Group

from settings import Settings
from ship import Ship
from game_stats import GameStats
from  button import Button
from score_board import ScoreBoard
import game_functions as gf


def run_game():
    # 初始化游戏,并创建一个屏幕对象
    pygame.init()
    # 创建设置实例
    ai_settings = Settings()
    # 创建储存游戏信息实例
    stats = GameStats(ai_settings)
    # 创建游戏屏幕
    screen = pygame.display.set_mode(
        (ai_settings.screen_width, ai_settings.screen_height))
    # 设定游戏名
    pygame.display.set_caption(ai_settings.caption)
    # 创建Play按钮
    play_button = Button(ai_settings, screen, "Play")
    # 创建记分牌
    sb = ScoreBoard(ai_settings, screen, stats)
    # 创建飞船实例
    ship = Ship(screen, ai_settings)
    # 储存子弹的编组
    bullets = Group()
    # 储存外星人的编组
    aliens = Group()
    # 计数器
    ticks = 0
    # 创建外星人群
    gf.create_fleet(ai_settings, screen, aliens, ship)
    # 开始游戏的主循环
    while True:
        # 计数
        ticks += 1
        aliens.draw(screen)
        if ticks >= ai_settings.animate_cycle:
                        ticks = 0
        # 监视键盘和鼠标事件
        gf.check_events(ship, ai_settings, stats, play_button, screen, aliens, bullets, sb )
        if stats.game_active:
            # 更新飞船状态
            ship.update(bullets, ai_settings, screen, ship, ticks)
            # 更新子弹状态
            gf.update_bullets(ai_settings, screen, ship, bullets, aliens, stats, sb)
            # 更新外星人状态
            gf.update_aliens(ai_settings, screen, aliens, bullets, stats, ticks, ship, sb)
        # 更新屏幕
        gf.update_screen(ai_settings, screen, ship, bullets, aliens,
                         play_button, stats, sb)


run_game()

game_functions.py

import sys
from random import randint
from time import sleep
import json

import pygame
from alien import Alien


def check_keydown_events(event, ship, ai_settings):
    """响应按键"""
    if event.key == pygame.K_RIGHT:
        ship.moving_right = True
        if ship.moving_left:
            ship.moving_right_speed = 2 * ai_settings.ship_speed_factor
    elif event.key == pygame.K_LEFT:
        ship.moving_left = True
        if ship.moving_right:
            ship.moving_left_speed = 2 * ai_settings.ship_speed_factor
    elif event.key == pygame.K_UP:
        ship.moving_up = True
        if ship.moving_down:
            ship.moving_up_speed = 2 * ai_settings.ship_speed_factor
    elif event.key == pygame.K_DOWN:
        ship.moving_down = True
        if ship.moving_up:
            ship.moving_down_speed = 2 * ai_settings.ship_speed_factor
    elif event.key == pygame.K_SPACE:
        ship.shooting = True
    elif event.key == pygame.K_q:
        sys.exit()


def check_keyup_events(event, ship, ai_settings):
    """响应松开"""
    if event.key == pygame.K_RIGHT:
        ship.moving_right = False
        ship.moving_right_speed = ai_settings.ship_speed_factor
        if ship.moving_left:
            ship.moving_left_speed = ai_settings.ship_speed_factor
    elif event.key == pygame.K_LEFT:
        ship.moving_left = False
        ship.moving_left_speed = ai_settings.ship_speed_factor
        if ship.moving_right:
            ship.moving_right_speed = ai_settings.ship_speed_factor
    elif event.key == pygame.K_UP:
        ship.moving_up = False
        ship.moving_up_speed = ai_settings.ship_speed_factor
        if ship.moving_down:
            ship.moving_down_speed = ai_settings.ship_speed_factor
    elif event.key == pygame.K_DOWN:
        ship.moving_down = False
        ship.moving_down_speed = ai_settings.ship_speed_factor
        if ship.moving_up:
            ship.moving_up_speed = ai_settings.ship_speed_factor
    elif event.key == pygame.K_SPACE:
        ship.shooting = False


def check_events(ship, ai_settings, stats, play_button, screen, aliens, bullets, sb):
    """响应按键和鼠标事件"""
    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            sys.exit()
        elif event.type == pygame.KEYDOWN:
            check_keydown_events(event, ship, ai_settings)
        elif event.type == pygame.KEYUP:
            check_keyup_events(event, ship, ai_settings)
        elif event.type == pygame.MOUSEBUTTONDOWN:
            mouse_x, mouse_y = pygame.mouse.get_pos()
            check_play_button(stats, play_button, mouse_x, mouse_y, ship, ai_settings,
                              screen, aliens, bullets, sb)


def update_screen(ai_settings, screen, ship, bullets, aliens, play_button, stats, sb):
    """更新屏幕上的图像 , 并切换到新屏幕"""
    # 重绘屏幕
    screen.fill(ai_settings.bg_color)
    # 显示分数
    sb.show_score()


    # 绘制飞船
    ship.blitme()
    # 绘制外星人
    aliens.draw(screen)
    # 绘制子弹
    for bullet in bullets.sprites():
        bullet.draw_bullet()
    # 若游戏处于非活动状态,绘制按钮
    if not stats.game_active:
        play_button.draw_button()
    # 让最近绘制的屏幕可见
    pygame.display.flip()


def check_bullet_alien_collision(ai_settings, screen, ship, aliens, bullets, stats, sb):
    """检查是否有子弹击中了外星人
        若击中了,删除相应的子弹和外星人
    """
    collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)
    if collisions:
        for aliens in collisions.values():
            stats.score += ai_settings.alien_points * len(aliens)
            sb.prep_score()
        check_high_score(stats, sb)

    if len(aliens) == 0:
        bullets.empty()
        ai_settings.increase_speed()
        # 提高等级
        stats.level += 1
        sb.prep_level()
        create_fleet(ai_settings, screen, aliens, ship)


def update_bullets(ai_settings, screen, ship, bullets, aliens, stats, sb):
    # 更新子弹位置
    bullets.update()
    # 删除已消失的子弹
    for bullet in bullets.copy():
        if bullet.rect.bottom <= 0:
            bullets.remove(bullet)

    check_bullet_alien_collision(ai_settings, screen, ship, aliens, bullets, stats, sb)


def get_number_aliens(ai_settings, new_alien):
    # 创建一个外星人,并计算一行可以容纳多少外星人
    # 外星人间距为外星人宽度
    available_space_x = ai_settings.screen_width - (2 * new_alien.rect.width)
    return int(available_space_x / (2 * new_alien.rect.width))


def create_alien(ai_settings, screen, aliens, alien_number, number_row):
    # 创建一个外星人并加入当前行
    alien = Alien(ai_settings, screen)
    alien.x = alien.rect.width * (2 * alien_number + 1)
    alien.y = alien.rect.height * (2 * number_row + 1)
    alien.rect.x = alien.x
    alien.rect.y = alien.y
    aliens.add(alien)


def get_number_rows(ai_settings, new_alien, ship):
    available_space_y = ai_settings.screen_height - ship.rect.width - new_alien.rect.height
    return int(available_space_y / (2 * new_alien.rect.height))


def create_fleet(ai_settings, screen, aliens, ship):
    """创建外星人群"""
    ai_settings.drop = False
    ai_settings.fleet_direction = 1
    new_alien = Alien(ai_settings, screen)
    number_aliens_x = get_number_aliens(ai_settings, new_alien)
    number_rows = get_number_rows(ai_settings, new_alien, ship)
    for number_row in range(number_rows):
        for alien_number in range(number_aliens_x):
            create_alien(ai_settings, screen, aliens, alien_number, number_row)
    # 随机删除
    for alien in aliens.copy():
        if len(aliens.sprites()) > 6 and randint(0, 1):
            aliens.remove(alien)


def check_fleet_edges(ai_settings, aliens, ticks):
    """有外星人到达边缘的措施"""
    for alien in aliens.sprites():
        if alien.check_edge():

            if ai_settings.drop is False:
                change_fleet_direction(ai_settings, ticks)
                break
        if ai_settings.drop and \
                                (ticks - ai_settings.drop_ticks) % ai_settings.drop_cycle == 0:
            change_fleet_direction(ai_settings)
            break


def check_aliens_bottom(ai_settings, screen, aliens, bullets, stats, ship, sb):
    """有外星人到达底部的措施"""
    for alien in aliens.sprites():
        if alien.rect.bottom >= screen.get_rect().bottom:
            ship_hit(ai_settings, screen, ship, aliens, bullets, stats, sb)
            break


def change_fleet_direction(ai_settings, ticks=0):
    """改变外星人群的运动方向"""
    if ticks:
        ai_settings.drop = True
        ai_settings.drop_ticks = ticks
    else:
        ai_settings.drop = False
        ai_settings.fleet_direction *= -1


def update_aliens(ai_settings, screen, aliens, bullets, stats, ticks, ship, sb):
    """更新外星人群的位置"""
    check_fleet_edges(ai_settings, aliens, ticks)
    # 检查是否有外星人到达底端
    check_aliens_bottom(ai_settings, screen, aliens, bullets, stats, ship, sb)
    aliens.update(ai_settings)

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


def ship_hit(ai_settings, screen, ship, aliens, bullets, stats, sb):
    """响应飞船遭到碰撞后的事件"""
    if stats.ship_left > 0:
        # 将ship_left 减一
        stats.ship_left -= 1

        # 清空所有子弹和外星人
        aliens.empty()
        bullets.empty()

        # 创建新的外星人群,并将飞船至于中央
        create_fleet(ai_settings, screen, aliens, ship)
        ship.center_ship()

        # 更新记分牌
        sb.prep_ships()

        # 暂停
        sleep(0.5)
    else:
        stats.game_active = False
        ai_settings.initialiize_dynamic_settings()
        # 显示光标
        pygame.mouse.set_visible(True)


def check_play_button(stats, play_button, mouse_x, mouse_y, ship, ai_settings,
                      screen, aliens, bullets, sb):
    """在玩家单击Play按钮时开始游戏"""
    if play_button.rect.collidepoint(mouse_x, mouse_y) and not stats.game_active:
        # 隐藏光标
        pygame.mouse.set_visible(False)
        # 重置游戏统计信息
        stats.reset_stats()
        stats.game_active = True
        # 重置分数
        sb.prep_score()
        # 重置等级
        sb.prep_level()
        # 重置飞船数
        sb.prep_ships()
        # 清空外星人列表和子弹列表
        aliens.empty()
        bullets.empty()

        # 创建新的外星人群
        create_fleet(ai_settings, screen, aliens, ship)
        ship.center_ship()


def check_high_score(stats, sb):
    """检查是否出现了新的最高分"""
    if stats.score > stats.high_score:
        stats.high_score = stats.score
        sb.prep_high_score()
        save_high_score(stats)


def save_high_score(stats):
    """将最高分保存入文件"""
    with open("high_score.json", 'w') as file:
        json.dump(stats.high_score, file)

ship.py

import pygame

from bullet import Bullet


class Ship():

    def __init__(self, screen, ai_settings):
        """初始化飞船并设置其初始位置"""
        super().__init__()
        self.screen = screen

        # 加载飞船图像并获取其外接矩形
        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

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

        # 移动像素
        self.moving_right_speed = ai_settings.ship_speed_factor
        self.moving_left_speed = ai_settings.ship_speed_factor
        self.moving_up_speed = ai_settings.ship_speed_factor
        self.moving_down_speed = ai_settings.ship_speed_factor

        # 射击标志
        self.shooting = False

    def update(self, bullets, ai_settings, screen, ship, ticks):
        """根据移动标志调整飞船状态"""
        # 移动
        if self.moving_down and self.rect.bottom < self.screen_rect.bottom:
            self.rect.centery += self.moving_down_speed
        if self.moving_up and self.rect.top > 0:
            self.rect.centery -= self.moving_up_speed
        if self.moving_right and self.rect.right < self.screen_rect.right:
            self.rect.centerx += self.moving_right_speed
        if self.moving_left and self.rect.left > 0:
            self.rect.centerx -= self.moving_left_speed
        # 射击
        if self.shooting and ticks % 15 == 0:
            self.add_bullets(bullets, ai_settings, screen, ship)

    def blitme(self):
        """指定位置绘制飞船"""
        self.screen.blit(self.image, self.rect)

    def add_bullets(self, bullets, ai_settings, screen, ship):
        # 创建一颗子弹,并将其加入到编组bullets中
        new_bullet = Bullet(ai_settings, screen, ship)
        bullets.add(new_bullet)

    def center_ship(self):
        self.rect.centerx = self.screen_rect.centerx
        self.rect.bottom = self.screen_rect.bottom

ship_small.py

import pygame
from pygame.sprite import Sprite


class ShipSmall(Sprite):
    """小飞船"""
    def __init__(self, screen):
        super().__init__()
        self.screen = screen
        self.image = pygame.image.load('images/ship_small.bmp')
        self.rect = self.image.get_rect()
        self.rect.top = 10

settings.py

class Settings():
    """储存游戏所有的设置"""

    def __init__(self):
        self.screen_width = 1500
        self.screen_height = 900
        self.bg_color = (0, 0, 15)
        self.caption = "Alien Invasion 1.0"
        # 飞船速度
        self.ship_speed_factor = 1
        # 飞船数量
        self.ship_limit = 3

        # 子弹设置
        self.bullet_speed = 3
        self.bullet_width = 3
        self.bullet_height = 15
        self.bullet_color = (255, 0, 0)

        # 外星人左右移动速度设置
        self.alien_speed_factor = 0.5
        # 下落速度
        self.fleet_drop_speed = 0.5
        # 左右移动方向
        self.fleet_direction = 1
        # 下落标志
        self.drop = False
        # 下落开始时间(帧数)点
        self.drop_ticks = 0
        # 下落时间(帧数)
        self.drop_cycle = 60
        # 外星人点数
        self.alien_points = 50

        # 动画周期
        self.animate_cycle = 60

        # 加快游戏节奏的节奏
        self.speedup_scale = 1.1
        self.initialiize_dynamic_settings()

    def initialiize_dynamic_settings(self):
        # 飞船速度
        self.ship_speed_factor = 1
        # 外星人左右移动速度设置
        self.alien_speed_factor = 0.5
        # 下落速度
        self.fleet_drop_speed = 0.5
        # 下落时间(帧数)
        self.drop_cycle = 60
        # 外星人点数
        self.alien_points = 50
        # 动画周期
        self.animate_cycle = 60

    def increase_speed(self):
        """提高速度设置"""
        self.ship_speed_factor *= self.speedup_scale
        # 外星人左右移动速度设置
        self.alien_speed_factor *= self.speedup_scale
        # 下落速度
        self.fleet_drop_speed *= self.speedup_scale
        # 下落时间(帧数)
        self.drop_cycle *= self.speedup_scale
        # 动画周期
        self.animate_cycle *= self.speedup_scale
        # 外星人点数
        self.alien_points = int(self.speedup_scale * self.alien_points)

alien.py

import pygame
from pygame.sprite import Sprite


class Alien(Sprite):
    """外星人 类"""

    def __init__(self, ai_settings, screen):
        """初始化外星人并设置其起始位置"""
        # 类似Ship类的设置
        super().__init__()

        self.screen = screen
        self.ai_settings = ai_settings
        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)
        self.y = float(self.rect.y)

    def update(self, ai_settings):
        if ai_settings.drop:
            self.y += self.ai_settings.fleet_drop_speed
            self.rect.y = self.y
        else:
            self.x += self.ai_settings.alien_speed_factor * ai_settings.fleet_direction
            self.rect.x = self.x

    def check_edge(self):
        """如果触碰边缘返回True"""
        if self.rect.right >= self.screen.get_rect().right or \
                        self.rect.left <= self.screen.get_rect().left:
            return True

game_stats.py

import json


class GameStats():
    """统计游戏各类信息"""
    def __init__(self, ai_settings):
        """初始化统计信息"""
        self.ai_settings = ai_settings
        self.reset_stats()
        # 游戏刚启动时处于非活动状态
        self.game_active = False
        # 飞船数量
        self.ship_left = self.ai_settings.ship_limit
        # 得分
        self.score = 0
        # 最高分
        self.high_score = 0
        self.get_high_score()
        # 难度等级
        self.level = 1

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

    def get_high_score(self):
        """读取最高分"""
        try:
            with open("high_score.json") as file:
                self.high_score = json.load(file)
        except FileNotFoundError:
            # 若第一次玩游戏,则创建文件,将 0 写入文件
            with open("high_score.json", 'w') as file:
                json.dump(self.high_score, file)

bullet.py

import pygame
from pygame.sprite import Sprite


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

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

        # 在(0, 0)处创建一个表示子弹的矩形,再设置正确的位置
        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 = ai_settings.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)

score_board.py

import pygame.font
from pygame.sprite import Group

from ship_small import ShipSmall


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 = (255, 255, 255)
        self.font = pygame.font.SysFont(None, 50, italic=True)

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

    def prep_score(self):
        score_str = str(self.stats.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 = 10
        self.prep_ships()

    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_small.draw(self.screen)

    def prep_high_score(self):
        high_score_str = str(self.stats.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 = 10

    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.top = self.score_rect.bottom + 10
        self.level_rect.left = self.score_rect.left

    def prep_ships(self):
        """显示还剩下多少飞船"""
        self.ships_small = Group()
        for ship_number in range(self.stats.ship_left):
            ship_small = ShipSmall(self.ai_settings)
            ship_small.rect.left = 10 + ship_number * ship_small.rect.width
            self.ships_small.add(ship_small)

但学头陀法

前心何以忘

要开心

要平安

2017 1 11 20:39:10

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值