Python Learning Road 13 - Scoring

This series is a compilation of notes for the introductory book "Python Programming: From Getting Started to Practice", which belongs to the primary content. The title sequence follows the title of the book.
This article is the last article of the Python game "Alien Invasion".

1 Introduction

In this article, we will end the development of the Pygame game "Alien Invasion". In this article, we will add the following:

  • Add a Play button for launching the game as needed and restarting the game after the game is over;
  • Allows the player to level up and increase the pace as they level up;
  • Add a scoring system

2. Add Play button

First in order to start the game by hitting the Play button, in GameStatsthe constructor of the class you need to self.game_activeset to False.

2.1 Button class

In order to add Playa button, we need to add a Buttonclass first. Put this class in a button.pymodule:

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 renders the string as an image to process the text, and renders the text through pygame.fontthe render()method. Its first parameter is the string to be rendered, and the second is the anti-aliasing setting (the old iron who plays the game should be very familiar with this word. Familiar with~~), the third is the font color, the fourth is the background color, if the fourth parameter is not set, the text will be rendered with a transparent background. Finally , the button draw_button()is drawn in the form through the method .Play

2.2 Modify alien_invasion.py

Instantiate a Playbutton in the main program, add its response event, and draw it.

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

Note that not only the code for instantiating the button was added, but also the update_screen()and check_events()functions were modified.

2.3 Modify game_functions.py

Modify the update_screen() function : draw the Playbutton in the form

# 增加了参数,记得修改主程序
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()

Modify the check_events() function : Playadd a response event for the button

# 增加了参数,记得修改主程序
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.MOUSEBUTTONDOWNIndicates the mouse press event; pygame.mousethe get_pos()coordinates of the mouse click are obtained by passing ; finally, the check_play_button()function is used to respond to the mouse click event. The content of the function is as follows:

Added check_play_button() function : handle mouse click events

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

Use play_button.rectthe collidepoint()method to determine whether the mouse is clicked button. If it is clicked, and the current game is in the "non-start" state, start or reset the game;

If you don't stats.game_activeconfirm it, in-game, even if Playthe button disappears, clicking the mouse where it used to be will reset the game.

In the game, in order to avoid the influence of the cursor, we hide it through pygame.mousethe method during the game; when the game is over, the cursor is redisplayed. To this end, the ship_hit() function needs to be modified :set_visible()

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

Finally, the effect of the program is as follows:
write picture description here

3. Game speed up

Every time a group of fleets is eliminated, we speed up the elements in the game, and for this, modifications settings.pyand game_functions.pymodules are required.

3.1 Modify settings.py

Add a speed-up rate parameter and add two methods:

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

We put the four parameters that need to be modified into the initialize_dynamic_settings()method, which increase_speed()is used to dynamically change the game parameters.

3.2 Modify game_functions.py

Each time a batch of alien fleets are destroyed, the game speed is accelerated, and the check_bullet_alien_collisions() function needs to be modified :

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

When restarting the game, you need to change these modified parameters back to their default values. To do this, you need to modify the check_play_button() function :

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

Below we will implement a scoring system that tracks the player's score in real time and displays the highest score, the current level and the number of ships remaining. First, we need to create a Scoreboardclass.

4.1 Add scoreboard.py

A new Scoreboardclass is added, which is used as a scoreboard in the screen. The upper part of the screen is the highest score, the right side of the screen is the current score and level, the upper left corner is the number of remaining ships, and the number of ships is represented by pictures. Therefore, we also To change the Ship class to inherit from 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 Modify settings.py

Set the alien's score, the rate at which the alien's score grows:

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 Modify game_stats.py

Set a property in GameStatsto record the highest score, and for this reason, it should be placed in the constructor, it will only grow larger, and it will not be reset until the game is re-run 0; in the reset_stats()method, Initialization scoreand leveltwo properties that Playare reset each time the button is clicked. For this attribute, add 1 for leveleach fleet destroyed .level

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 Modify the main program 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)

As can be seen from the comments above, we have generated an instance of the scoreboard score; game_functions.pyall four functions in it must be added with scoreparameters, in other words, these four functions must be modified. Let's modify these four functions one by one.

4.5 Modify game_functions.py

4.5.1 Modify parameters

There are several functions that just need to add parameters to the parameter list 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 --

Next is the function with more changes.

4.5.2 Modify the check_play_button() function

# 添加了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 --

First, parameters are added to the parameter list score, and iffour lines of code for generating scoreboards are also added to the judgment. The reason why these four lines of code are added here is actually so that when you restart the Playgame (that is, click the button a second time and later), the scoreboard will display correctly.

When running the game for the first time, the scoreboard is displayed correctly without these four lines. But starting from the second click of Play, without these four lines, although the various parameters of the game have been updated ( check_play_button()they have been updated through the various reset functions in ), these updates have not yet made the four parameters in the scoreboard. The image gets repainted, i.e. the update of the properties does not trigger scorethese four functions automatically. So the display will be incorrect, so these four lines of code must be added here.

4.5.3 Modify the update_screen() function

# 增加了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 Modify update_bullets() and update_aliens() functions

These two functions just add parameters.

# 增加了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()The internal changes are also not large, and the changes to this function are no longer listed separately in the form of code:

The function adds a scoreparameter, it internally calls the ship_hit()function, and adds scoreparameters for this call. That's all the change.

4.5.5 Modify the check_bullet_alien_collisions() function

# 增加了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)

First of all, we added a judgment statement to increase the score according to the aliens eliminated. Since it is possible that a bullet hits multiple aliens, but only one alien's score is counted, all use loops to ensure elimination. Every alien dropped is counted. collisionsis a dictionary, where bullet is the key and the alien object killed by the bullet is the value (which is a list).

We also added a function to update the highest score check_high_score(), whose code is as follows:

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

In the second if, the statement to increase the grade is added, followed by the redrawing of the grade image in the scoreboard.

4.5.6 Modify ship_hit() and check_aliens_bottom() functions

# 增加了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 Final running effect

So far all the additions are over, the following picture is the final effect of the game:
write picture description here

5. Summary

The Python game has come to an end, with a total of three articles. This article describes:

  • PlayHow to create a button for starting a new game ;
  • How to detect mouse click events;
  • How to hide the cursor when the game is active;
  • How to adjust the rhythm as the game progresses;
  • How to implement a scoring system;
  • and how to display information in text and non-text.

The last three articles will be about using Python for statistical analysis, graphing, etc.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325657818&siteId=291194637