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 GameStats
the constructor of the class you need to self.game_active
set to False
.
2.1 Button class
In order to add Play
a button, we need to add a Button
class first. Put this class in a button.py
module:
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.font
the 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 Play
button 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 Play
button 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 : Play
add 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.MOUSEBUTTONDOWN
Indicates the mouse press event; pygame.mouse
the 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.rect
the 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_active
confirm it, in-game, even if Play
the 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.mouse
the 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:
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.py
and game_functions.py
modules 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 Scoreboard
class.
4.1 Add scoreboard.py
A new Scoreboard
class 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 GameStats
to 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 score
and level
two properties that Play
are reset each time the button is clicked. For this attribute, add 1 for level
each 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.py
all four functions in it must be added with score
parameters, 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 if
four 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 Play
game (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 score
these 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 score
parameter, it internally calls the ship_hit()
function, and adds score
parameters 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. collisions
is 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:
5. Summary
The Python game has come to an end, with a total of three articles. This article describes:
Play
How 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.