资讯专栏INFORMATION COLUMN

Python学习之路13-记分

tommego / 409人阅读

摘要:之所以这里要添加这四行代码,其实是为了当你重新开始也就是第二次及以后点击按钮游戏时,计分板能正确显示。当第一运行游戏时,没有这四行也能正确显示计分板。

《Python编程:从入门到实践》笔记。
本篇是Python小游戏《外星人入侵》的最后一篇。
1. 前言

本篇我们将结束Pygame小游戏《外星人入侵》的开发。在本篇中,我们将添加如下内容:

添加一个Play按钮,用于根据需要启动游戏以及在游戏结束后重启游戏;

使玩家能提高等级,并在提高等级时加快节奏;

添加一个记分系统

2. 添加Play按钮

首先为了通过点击Play按钮来开始游戏,需要在GameStats类的构造函数中将self.game_active设置为False

2.1 Button类

为了添加Play按钮,我们需要先添加一个Button类。将这个类放在button.py模块中:

import pygame

class Button:
    def __init__(self, ai_settings, screen, msg):
        """初始化按钮属性"""
        self.screen = screen
        self.screen_rect = screen.get_rect()

        # 设置按钮尺寸和其他属性
        self.width, self.height = 200, 50   # 解包,平行赋值
        self.button_color = (0, 255, 0)
        self.text_color = (255, 255, 255)
        self.font = pygame.font.SysFont(None, 48)

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

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

    def prep_msg(self, msg):
        """将msg渲染为图像,并使其在按钮上居中"""
        self.msg_image = self.font.render(msg, True, self.text_color, self.button_color)
        self.msg_image_rect = self.msg_image.get_rect()
        self.msg_image_rect.center = self.rect.center

    def draw_button(self):
        # 绘制一个用颜色填充的按钮,再绘制文本
        self.screen.fill(self.button_color, self.rect)
        self.screen.blit(self.msg_image, self.msg_image_rect)

pygame将字符串渲染为图像来处理文本,通过pygame.fontrender()方法来渲染文字,它的第一个参数是要渲染的字符串,第二个是抗锯齿设定(打游戏的老铁应该对这个词很熟悉~~),第三个是字体颜色,第四个是背景颜色,第四个参数如果不设定,将以透明背景的方式渲染文本。最后通过draw_button()方法在窗体中绘制Play按钮。

2.2 修改alien_invasion.py

在主程序中实例化一个Play按钮,并添加它的响应事件,以及将其画出。

-- snip --
from button import Button
-- snip --

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

    # 创建Play按钮
    play_button = Button(ai_settings, screen, "Play")
    
    -- snip --
    # 开始游戏的主循环
    while True:
        # 增加了参数,为按钮添加响应事件
        gf.check_events(ai_settings, screen, ship, bullets, stats, play_button, aliens)
        -- snip --
        # 增加了参数,在窗体中画出按钮
        gf.update_screen(ai_settings, screen, ship, bullets, aliens, stats, play_button)

run_game()

注意,不光新增了实例化按钮的代码,还修改了update_screen()check_events()函数。

2.3 修改game_functions.py

修改update_screen()函数:在窗体中画出Play按钮

# 增加了参数,记得修改主程序
def update_screen(ai_settings, screen, ship, bullets, aliens, stats, play_button):
    -- snip --
    # 如果游戏没启动,则显示Play按钮
    if not stats.game_active:
        play_button.draw_button()

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

修改check_events()函数:为Play按钮添加响应事件

# 增加了参数,记得修改主程序
def check_events(ai_settings, screen, ship, bullets, stats, play_button, aliens):
    for event in pygame.event.get():
        -- snip --
        elif event.type == pygame.MOUSEBUTTONDOWN:
            mouse_x, mouse_y = pygame.mouse.get_pos()
            check_play_button(stats, play_button, mouse_x, mouse_y, ai_settings,
                              screen, ship, aliens, bullets)

pygame.MOUSEBUTTONDOWN表示鼠标按下事件;通过pygame.mouseget_pos()来获得鼠标点击处的坐标;最后,通过check_play_button()函数来响应鼠标点击事件,该函数的内容如下:

新增check_play_button()函数:处理鼠标点击事件

def check_play_button(stats, play_button, mouse_x, mouse_y, ai_settings, screen,
                      ship, aliens, bullets):
    """在玩家单机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

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

        # 创建一群新的外星人,并让飞船居中
        create_fleet(ai_settings, screen, ship, aliens)
        ship.center_ship()

通过play_button.rectcollidepoint()方法来确定鼠标是否点击到了button,如果点击到了,并且当前游戏是“非启动”状态,则启动或者重置游戏;

如果不对stats.game_active进行确认,则在游戏中,即使Play按钮消失了,鼠标点击它原来所在的地方,也会重置游戏。

在游戏中,为了避免光标的影响,游戏时我们通过pygame.mouseset_visible()方法将其隐藏;游戏结束时,重新显示光标,为此,需要修改ship_hit()函数

def ship_hit(ai_settings, stats, screen, ship, aliens, bullets):
    -- snip --
    else:
        -- snip --
        pygame.mouse.set_visible(True)

最后,程序的效果如下:

3. 游戏提速

每当消灭一批舰队后,我们就为游戏里的元素提个速,为此,需要修改settings.pygame_functions.py模块。

3.1 修改settings.py

添加一个提速倍率参数,并增加两个方法:

class Settings:
    def __init__(self):
        -- snip --
        # 以什么样的速度提节奏
        self.speedup_scale = 1.1
        # 前面有四个属性放到了该方法中
        self.initialize_dynamic_settings()

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

        # 外星舰队方向标志:1向右,-1向左
        self.fleet_direction = 1

    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

我们将需要修改的四个参数放到了initialize_dynamic_settings()方法中,increase_speed()方法用于动态改变游戏参数。

3.2 修改game_functions.py

每消灭一批外星舰队,就对游戏提速,需要修改check_bullet_alien_collisions()函数

def check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets):
    -- snip --
    if len(aliens) == 0:
        -- snip --
        ai_settings.increase_speed()
        -- snip --

当重新开始游戏时,需要将这些被修改了的参数改回默认值,为此,需要修改check_play_button()函数

def check_play_button(stats, play_button, mouse_x, mouse_y, ai_settings, screen,
                      ship, aliens, bullets):
    if play_button.rect.collidepoint(mouse_x,
                                     mouse_y) and not stats.game_active:
        # 重置游戏设置
        ai_settings.initialize_dynamic_settings()
        -- snip --
4. 记分板

下面我们将实现一个记分系统,实时跟踪玩家的得分,并显示最高得分,当前等级和余下的飞船数。首先,我们需要创建一个Scoreboard类。

4.1 新增scoreboard.py

新增一个Scoreboard类,用作屏幕中的记分板,它的屏幕正中央上方部分是最高分数,屏幕右边是当前分数和等级,左上角是剩余的飞船数量,飞船数量用图片表示,因此,我们还要将Ship类更改为从Sprite继承

import pygame
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 = 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 = 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)
4.2 修改settings.py

设置外星人的分数,外星人分数增长的速度:

class Settings:
    def __init__(self):
        -- snip --
        # 外星人点数的提高速度
        self.score_scale = 1.5

        self.initialize_dynamic_settings()

    def initialize_dynamic_settings(self):
        -- snip --
        # 记分, 每一个外星人的分数
        self.alien_points = 50

    def increase_speed(self):
        -- snip --
        # 动态增加每个外星人的分数
        self.alien_points = int(self.alien_points * self.score_scale)
4.3 修改game_stats.py

GameStats中设置一个用于记录最高分的属性,也正因此,应该将它放在构造函数中,它只会变大,在没有重新运行游戏前,它不会被重置为0;在reset_stats()方法中,初始化scorelevel两个属性,这两个属性每点一次Play按钮都会被重置。对于level这个属性,每消灭一批舰队,level就加1.

class GameStats:
    def __init__(self, ai_settings):
        -- snip --
        # 在任何情况下都不应重置最高得分
        self.high_score = 0

    def reset_stats(self):
        -- snip --
        self.score = 0
        self.level = 1
4.4 修改主程序alien_invasion.py
-- snip --
from scoreboard import Scoreboard

def run_game():
    -- snip --
    # 创建计分板
    score = Scoreboard(ai_settings, screen, stats)

    # 开始游戏的主循环
    while True:
        # 添加score参数
        gf.check_events(ai_settings, screen, ship, bullets, stats, play_button,
                        aliens, score)

        if stats.game_active:
            ship.update()
            # 添加score参数
            gf.update_bullets(bullets, aliens, ship, screen, ai_settings, stats, score)
            # 添加score参数
            gf.update_aliens(ai_settings, aliens, ship, screen, bullets, stats, score)
        # 添加score参数
        gf.update_screen(ai_settings, screen, ship, bullets, aliens, stats,
                         play_button, score)

从上面的注释可以看出,我们生成了一个计分板的实例scoregame_functions.py中的四个函数都要添加score参数,换句话说,这四个函数都要修改,下面我们逐一修改这四个函数。

4.5 修改game_functions.py 4.5.1 修改参数

有几个函数只需要在参数列表中增加score参数:

# 增加score参数
def check_events(ai_settings, screen, ship, bullets, stats, play_button, aliens, score):
    for event in pygame.event.get():
        -- snip --
        elif event.type == pygame.MOUSEBUTTONDOWN:
            mouse_x, mouse_y = pygame.mouse.get_pos()
            # 增加score参数, 该函数有所改动
            check_play_button(stats, play_button, mouse_x, mouse_y, ai_settings,
                              screen, ship, aliens, bullets, score)

# 增加score参数
def update_bullets(bullets, aliens, ship, screen, ai_settings, stats, score):
    -- snip --
    # 增加score参数,该函数有所改动
    check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets, stats, score)

# 增加score参数
def update_aliens(ai_settings, aliens, ship, screen, bullets, stats, score):
    -- snip --
    if pygame.sprite.spritecollideany(ship, aliens):
        # 增加score参数,该函数有所改动
        ship_hit(ai_settings, stats, screen, ship, aliens, bullets, score)
    # 增加score参数,该函数有所改动
    check_aliens_bottom(ai_settings, stats, screen, ship, aliens, bullets, score)

# 增加score参数,该函数有所改动
def update_screen(ai_settings, screen, ship, bullets, aliens, stats, play_button, score):
    -- snip --
    aliens.draw(screen)
    # 在if语句前面添加绘制计分板的代码
    # 显示得分
    score.show_score()

    if not stats.game_active:
        play_button.draw_button()
    -- snip --

接下来是改动较多的函数。

4.5.2 修改check_play_button()函数
# 添加了score参数
def check_play_button(stats, play_button, mouse_x, mouse_y, ai_settings, screen,
                      ship, aliens, bullets, score):
    """在玩家单机Play按钮时开始新游戏"""
    if play_button.rect.collidepoint(mouse_x,
                                     mouse_y) and not stats.game_active:
        -- snip --
        stats.game_active = True  # 这一句不是新增的
        
        # 以下四行是新增的
        score.prep_score()
        score.prep_high_score()
        score.prep_level()
        score.prep_ships()

        # 清空外星人列表和子弹列表
        -- snip --

首先参数列表添加了score参数,if判断中还添加了四行生成计分板的代码。之所以这里要添加这四行代码,其实是为了当你重新开始(也就是第二次及以后点击Play按钮)游戏时,计分板能正确显示。

当第一运行游戏时,没有这四行也能正确显示计分板。但是从第二次点击Play开始,如果没有这四行,游戏的各个参数虽然更新了(通过check_play_button()中的各种重置函数得到了更新),可这些更新还没有让记分板中这四个参数的图像得到重新绘制,即属性的更新没有自动触发score的这四个函数。所以显示会不正确,因此必须在这里添加这四行代码。

4.5.3 修改update_screen()函数
# 增加了score参数
def update_screen(ai_settings, screen, ship, bullets, aliens, stats, play_button, score):
    -- snip --
    # 增加显示得分的代码
    score.show_score()

    if not stats.game_active:
        -- snip --
4.5.4 修改update_bullets()和update_aliens()函数

这俩函数只是增加参数而已。

# 增加了score参数
def update_bullets(bullets, aliens, ship, screen, ai_settings, stats, score):
    -- snip --
    # 增加了score参数, 函数有改动
    check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets, stats, score)

# 增加了score参数
def update_aliens(ai_settings, aliens, ship, screen, bullets, stats, score):
    -- snip --
    # 检测外星人和飞船之间的碰撞
    if pygame.sprite.spritecollideany(ship, aliens):
        # 增加了score参数, 函数有改动
        ship_hit(ai_settings, stats, screen, ship, aliens, bullets, score)
    # 增加了score参数
    check_aliens_bottom(ai_settings, stats, screen, ship, aliens, bullets, score)

check_aliens_bottom()内也变化也不大,该函数的变化不再以代码的形式多带带列出:

该函数增加了一个score参数,它内部调用了ship_hit()函数,为这个调用也增加score参数。这就是全部变化。

4.5.5 修改check_bullet_alien_collisions()函数
# 增加了score参数
def check_bullet_alien_collisions(ai_settings, screen, ship, aliens, bullets, stats, score):
    collisions = pygame.sprite.groupcollide(bullets, aliens, True, True)

    if collisions:
        for aliens in collisions.values():
            stats.score += ai_settings.alien_points * len(aliens)
            # 其实这里可以将其放到for循环之外,应为并不能立刻就呈现分数变化
            # 要等到主程序中的update_screen()中才能呈现
            score.prep_score()
        # 该函数是新增的
        check_high_score(stats, score)

    if len(aliens) == 0:
        # 删除现有的子弹并创建新的舰队
        bullets.empty()
        ai_settings.increase_speed()

        # 提高等级
        stats.level += 1
        score.prep_level()

        create_fleet(ai_settings, screen, ship, aliens)

首先我们增加了一个判断语句,用于根据消灭的外星人来增加分数,由于有可能一颗子弹打到多个外星人但只算了一个外星人的分数,所有用循环来确保消灭掉的每一个外星人都得到了统计。collisions是一个字典,这里子弹是键,该子弹消灭的外星人对象为值(是个列表)。

我们还新增了一个更新最高积分的函数check_high_score(),它的代码如下:

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

第二个if中,添加了增加等级的语句,紧跟着的是重新在计分板中绘制等级图像。

4.5.6 修改ship_hit()和check_aliens_bottom()函数
# 增加了score参数
def ship_hit(ai_settings, stats, screen, ship, aliens, bullets, score):
    if stats.ships_left > 0:
        stats.ships_left -= 1

        # 更新记分牌
        score.prep_ships()
        # 清空外星人列表和子弹列表
        -- snip --
4.6 最后运行效果

至此所有的添加都已经结束,下图是游戏的最终效果:

5. 小结

Python小游戏告一段落,一共三篇文章。本文中讲述了:

如何创建用于开始新游戏的Play按钮;

如何检测鼠标点击事件;

如何在游戏处于活动状态时隐藏光标;

如何随游戏的进行调整节奏;

如何实现记分系统;

以及如何以文本和非文本方式显示信息。

后三篇文章将是使用Python来进行数据统计分析、绘图等内容。


迎大家关注我的微信公众号"代码港" & 个人网站 www.vpointer.net ~

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/41788.html

相关文章

  • 入行数据科学一定要有研究生学历吗?

    摘要:如果你的目标是成为数据科学家或机器学习工程师研究员,那么有博士学位会给你加分不少。当然,有些人更喜欢学术研究,而不是在行业中运用数据科学或机器学习。二硕士学位入行数据科学需要硕士学位吗视情况而定。 showImg(https://segmentfault.com/img/bVbm5Mw?w=850&h=566);作者 | Jeremie Harris翻译 | MikaCDA 数据分析师...

    DrizzleX 评论0 收藏0
  • 云计算正在成为全球性市场吗?未必!

    摘要:商业软件联盟周三称,一些欧洲国家和快速增长的发展中国家如巴西和中国担心美国将轻易获取新兴的云计算市场。因此,这些国家建立了一些屏障阻止数据跨越其国境,从而阻碍了全球云计算市场的发展。 商业软件联盟周三称,一些欧洲国家和快速增长的发展中国家(如巴西和中国)担心美国将轻易获取新兴的云计算市场。因此,这些国家建立了一些屏障阻止数据跨越其国境,从而阻碍了全球云计算市场的发展。   商业软件联盟CE...

    jindong 评论0 收藏0
  • Python 进阶之路 (六) 九浅一深 lambda,陈独秀你给我坐下!

    摘要:好啦,首先让我们先搞明白基础定义,到底是什么表达了中用于创建匿名函数的特殊语法。其实总结起来,可以理解为一个小的匿名函数,函数可以使用任意数量的参数,但只能有一个表达式。 lambda是什么 大家好,今天给大家带来的是有关于Python里面的lambda表达式详细解析。lambda在Python里面的用处很广,但说实话,我个人认为有关于lambda的讨论不是如何使用的问题,而是该不该用...

    WelliJhon 评论0 收藏0
  • Python全栈之路系列之递归

    摘要:所谓递归其实就是函数本身调用函数,直到满足指定条件之后一层层退出函数,例如从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢故事是什么呢从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢故事是什么呢从前有座山,山里有座庙 所谓递归其实就是函数本身调用函数,直到满足指定条件之后一层层退出函数, 例如 从前有座山,山里有座庙,庙里有个老和尚,正在给小和尚讲故事呢!故事是...

    kviccn 评论0 收藏0

发表评论

0条评论

tommego

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<