为什么文本显示会降低我的 pygame FPS?

Why does the display of text decrease my pygame FPS?

我正在尝试编写这个简单的游戏来复制 Asteroids 的某些方面,但在 FPS 方面遇到了一些问题。我还没有完成游戏,但担心 FPS 下降,因为我有更多想要显示的文本,但由于担心游戏会遭受如此多的帧下降而无法显示。我只注意到这个帧下降,屏幕上显示文本。我的代码可以在下面找到。

import os
import pickle
import pygame
from pygame.locals import *
import random
import sys
import time

pygame.init()
pygame.display.init()

common_drops = ['Janus', 'Peace of Mind', 'Second Chance']
rare_drops = ['Invincibility', 'Shield', 'Bonus Credits', 'Weapon']
ultra_rare_drops = []

janus_count = 0
peace_of_mind_count = 0
bonus_lives_count = 0 #Second Chance
invincibility_count = 0
shield_count = 0
weapon_count = 0
credit_count = 30
high_score = 0

def save():
    loot_out = open('./nec_files/user_data/player.pickle', 'wb')
    pickle.dump(player_loot_data, loot_out)
    loot_out.close()

if os.path.isfile('./nec_files/user_data/player.pickle') == True:

    loot_in = open('./nec_files/user_data/player.pickle', 'rb')
    loot_dict = pickle.load(loot_in)

    player_loot_data = {
        'janus_count' : loot_dict['janus_count'],
        'peace_of_mind_count' : loot_dict['peace_of_mind_count'],
        'bonus_lives_count' : loot_dict['bonus_lives_count'], #Second Chance
        'invincibility_count' : loot_dict['invincibility_count'],
        'shield_count' : loot_dict['shield_count'],
        'weapon_count' : loot_dict['weapon_count'],
        'credit_count' : loot_dict['credit_count'],
        'high_score' : loot_dict['high_score']
    }
    loot_in.close()

    save()

else:

    player_loot_data = {
        'janus_count' : janus_count,
        'peace_of_mind_count' : peace_of_mind_count,
        'bonus_lives_count' : bonus_lives_count, #Second Chance
        'invincibility_count' : invincibility_count,
        'shield_count' : shield_count,
        'weapon_count' : weapon_count,
        'credit_count' : credit_count,
        'high_score' : high_score
    }

    save()

display_width = 1280
display_height = 720

black = (0,0,0)
white = (255,255,255)
blue = (0, 102, 204)
bright_blue = (102, 178, 255)
red = (204, 0, 0)
bright_red = (255, 51, 51)
yellow = (204, 204, 0)
bright_yellow = (255, 255, 102)
gray = (169, 169, 169)

game_title = 'Asteroids: Reimagined'

paused = False
alpha = True

gameDisplay = pygame.display.set_mode((display_width, display_height))
pygame.display.set_caption(game_title)
print(pygame.display.get_driver())
clock = pygame.time.Clock()

playerImg = pygame.image.load('./nec_files/graphics/player_image.png')
asteroidImg = pygame.image.load('./nec_files/graphics/asteroid_image.png')

current_drops = []

def open_common_drop():
    global current_drops

    current_drops.clear()

    if player_loot_data['credit_count'] >= 5:

        player_loot_data['credit_count'] -= 5

        for x in range(3):
            rand_num = random.randint(0, 50)
            if rand_num < 49:
                drops = random.choice(common_drops)
            elif rand_num >= 49:
                drops = random.choice(rare_drops)

            if drops == 'Janus':
                player_loot_data['janus_count'] += 1
            elif drops == 'Peace of Mind':
                player_loot_data['peace_of_mind_count'] += 1
            elif drops == 'Second Chance':
                player_loot_data['bonus_lives_count'] += 1
            elif drops == 'Bonus Credits':
                bonus_credits = random.randint(1, 50)
                player_loot_data['credit_count'] += bonus_credits
            elif drops == 'Invincibility':
                player_loot_data['invincibility_count'] += 1
            elif drops == 'Shield':
                player_loot_data['shield_count'] += 1
            elif drops == 'Weapon':
                player_loot_data['weapon_count'] += 1

            current_drops.append(drops)

        save()

def open_rare_drop():
    global current_drops

    current_drops.clear()

    if player_loot_data['credit_count'] >= 10:

        player_loot_data['credit_count'] -= 10

        for x in range(3):
            rand_num = random.randint(0, 50)
            if rand_num < 36:
                drops = random.choice(common_drops)
            elif rand_num >= 36:
                drops = random.choice(rare_drops)

            if drops == 'Janus':
                player_loot_data['janus_count'] += 1
            elif drops == 'Peace of Mind':
                player_loot_data['peace_of_mind_count'] += 1
            elif drops == 'Second Chance':
                player_loot_data['bonus_lives_count'] += 1
            elif drops == 'Bonus Credits':
                bonus_credits = random.randint(1, 50)
                player_loot_data['credit_count'] += bonus_credits
            elif drops == 'Invincibility':
                player_loot_data['invincibility_count'] += 1
            elif drops == 'Shield':
                player_loot_data['shield_count'] += 1
            elif drops == 'Weapon':
                player_loot_data['weapon_count'] += 1

            current_drops.append(drops)

        save()

def player(player_x, player_y):
    gameDisplay.blit(playerImg, (player_x, player_y))
def asteroid(thingx, thingy):
    gameDisplay.blit(asteroidImg, (thingx, thingy))

def game_display_text(display_msg, display_x, display_y, text_size):
    font = pygame.font.SysFont(None, text_size)
    text = font.render(str(display_msg), True, black)
    gameDisplay.blit(text, (display_x, display_y))

def title(msg):
    largeText = pygame.font.SysFont(None, 75)
    TextSurf, TextRect = text_objects(msg, largeText)
    TextRect.center = ((display_width / 2), (display_height * 0.10))
    gameDisplay.blit(TextSurf, TextRect)

def button(x, y, w, h, ic, ac, action = None):
    global paused

    mouse = pygame.mouse.get_pos()
    click = pygame.mouse.get_pressed()


    if x + w > mouse[0] > x and y + h > mouse[1] > y:
        pygame.draw.rect(gameDisplay, ac, (x, y, w, h))

        if click[0] == 1 and action == Game:
            Game()
        if click[0] == 1 and action == quitgame:
            sys.exit()
        if click[0] == 1 and action == None:
            paused = False
        if click[0] == 1 and action == StartScreen:
            save()
            StartScreen()
        if click[0] == 1 and action == LootScreen:
            LootScreen()
        if click[0] == 1 and action == open_common_drop:
            open_common_drop()
        if click[0] == 1 and action == open_rare_drop:
            open_rare_drop()

    else:
        pygame.draw.rect(gameDisplay, ic, (x, y, w, h))

def things(thingx, thingy, thingw, thingh, color):
    pygame.draw.rect(gameDisplay, color, [thingx, thingy, thingw, thingh])
def things2(thingx, thingy, thingw, thingh, color):
    pygame.draw.rect(gameDisplay, color, [thingx, thingy, thingw, thingh])
def text_box(box_x, box_y, box_w, box_h, color):
    pygame.draw.rect(gameDisplay, color, [box_x, box_y, box_w, box_h])
def text_objects(text, font):
    textSurface = font.render(text, True, black)
    return textSurface, textSurface.get_rect()
def message_display(text):
    largeText = pygame.font.Font('freesansbold.ttf', 50)
    TextSurf, TextRect = text_objects(text, largeText)
    TextRect.center = ((display_width/2),(display_height/2))
    gameDisplay.blit(TextSurf, TextRect)

    pygame.display.update()
    time.sleep(2)

def reset():
    message_display('Out of Bounds: Player Location Reset')

def quitgame():
    pygame.quit()
    sys.exit()

def StartScreen():

    intro = True

    settings_x = 1230
    settings_y = 670

    while intro:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                save()

                pygame.quit()
                sys.exit()

        gameDisplay.fill(gray)
        title(game_title)

        button(420, 250, 125, 50, blue, bright_blue, Game)
        button(720, 250, 125, 50, red, bright_red, quitgame)
        button(570, 250, 125, 50, yellow, bright_yellow, LootScreen)

        game_display_text('Start', 450, 260, 40)
        game_display_text('Quit', 750, 260, 40)
        game_display_text('Loot', 600, 260, 40)

        game_display_text('Licensed by: @1024MBStudio', 925, 690, 35)

        pygame.display.update()
        clock.tick(30)

def LootScreen():
    global current_drops

    loot = True

    while loot:
        for event in pygame.event.get():

            if event.type == pygame.QUIT:
                save()

                pygame.quit()
                sys.exit()

            if event.type == pygame.KEYDOWN:
                if event.key == pygame.K_t:
                    open_common_drop()
                elif event.key == pygame.K_y:
                    open_rare_drop()

                if event.key == pygame.K_ESCAPE:
                    StartScreen()

        gameDisplay.fill(gray)
        title('Loot Chests!')

        button(400, 150, 260, 50, blue, bright_blue, None)
        button(695, 150, 260, 50, red, bright_red, None)
        button(display_width * 0.42, display_height / 1.15, 255, 50, red, bright_red, StartScreen)

        game_display_text('Open Common Chest (T)', 407, 165, 30)
        game_display_text('Open Rare Chest (Y)', 725, 165, 30)
        game_display_text('You Got: %s' % current_drops, 50, display_height / 2, 35)
        game_display_text('Credits: %.2f' % player_loot_data['credit_count'], 15, 15, 35)
        game_display_text('Main Menu', display_width * 0.47, display_height / 1.13, 35)

        game_display_text('Janus\': %.2f' % player_loot_data['janus_count'] , 1025, 500, 35)
        game_display_text('Peace of Minds: %.2f' % player_loot_data['peace_of_mind_count'], 1025, 535, 35)

        pygame.display.update()
        clock.tick(30)

def PauseScreen():

    global paused

    paused = True

    pausebox_x = 0
    pausebox_y = 625
    pausebox_width = display_width
    pausebox_height = display_height - 625

    while paused:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                save()

                pygame.quit()
                sys.exit()

            if event.type == pygame.KEYDOWN:

                if event.key == pygame.K_ESCAPE:
                    paused = False

        gameDisplay.fill(gray)
        title('Paused')

        button(560, 130, 173, 50, blue, bright_blue, None)
        button(560, 205, 173, 50, red, bright_red, StartScreen)

        game_display_text('Resume', 590, 140, 40)
        game_display_text('Quit', 615, 218, 40)

        text_box(pausebox_x, pausebox_y, pausebox_width, pausebox_height, blue)

        game_display_text('Janus\': %s' % player_loot_data['janus_count'] , 5, 630, 35)
        game_display_text('Peace of Minds: %s' % player_loot_data['peace_of_mind_count'], 5, 665, 35)
        game_display_text('Bonus Lives: %s' % player_loot_data['bonus_lives_count'], 250, 630, 35)

        pygame.display.update()
        clock.tick(30)

def DeadScreen():

    current_score = 0

    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                save()

                pygame.quit()
                sys.exit()

        gameDisplay.fill(gray)
        title('You Died')

        game_display_text('You earned %s' % credit_gain + ' credits that game!', display_width * 0.33, display_height * 0.40, 40)

        button(520, 120, 250, 55, blue, bright_blue, Game)
        button(520, 190, 250, 55, red, bright_red, StartScreen)

        game_display_text('Play Again?', 560, 132, 40)
        game_display_text('Main Menu', 569, 205, 40)

        pygame.display.update()
        clock.tick(30)

def Game():
    global death_counter, attempt_counter, credit_gain

    player_x = (display_width * 0.5)
    player_y = (display_height * 0.5)
    player_speed = 5.5
    playerHeight = 50
    x_change = 0
    y_change = 0

    enemyWidth = 165
    thing_startx = 1500
    thing2_startx = 1500
    thing_speed = -6
    thing2_speed = -5.5
    thing_starty = random.randrange(75, display_height - enemyWidth)
    thing2_starty = random.randrange(75, display_height - enemyWidth)

    dead = False
    janus = False
    peace_of_mind = False
    invincibility = False
    full_screen = False

    earnable_credits = 0.125 
    current_score = 0
    credit_gain = 0
    current_lives = 0

    RESETEVENT = pygame.USEREVENT + 1
    DISABLEJANUS = pygame.USEREVENT + 5

    textbox_x = 0
    textbox_y = 0
    textbox_width = 1280
    textbox_height = 60

    while not dead:

        if pygame.display.get_active() == True:
            for event in pygame.event.get():
                pygame.time.set_timer(RESETEVENT, 275)

                if peace_of_mind == True:
                    thing_original_speed = thing_speed
                    thing2_original_speed = thing2_speed

                if event.type == RESETEVENT:
                    current_score += 1
                    pygame.time.set_timer(RESETEVENT, 275)
                if event.type == DISABLEJANUS:
                    janus = False
                if event.type == pygame.QUIT:
                    save()

                    pygame.quit()
                    sys.exit()

                if event.type == pygame.KEYDOWN:
                    if janus == True:
                        if event.key == pygame.K_LEFT or event.key == pygame.K_a:
                            x_change = -player_speed
                        elif event.key == pygame.K_RIGHT or event.key == pygame.K_d:
                            x_change = player_speed
                    if event.key == pygame.K_UP or event.key == pygame.K_w:
                        y_change = -player_speed
                    elif event.key == pygame.K_DOWN or event.key == pygame.K_s:
                        y_change = player_speed

                    if event.key == pygame.K_u and player_loot_data['janus_count'] > 0 and janus == False:
                        pygame.time.set_timer(DISABLEJANUS, 15000)
                        player_loot_data['janus_count'] -= 1
                        janus = True
                    elif event.key == pygame.K_i and player_loot_data['peace_of_mind_count'] > 0 and peace_of_mind == False:
                        player_loot_data['peace_of_mind_count'] -= 1
                        peace_of_mind = True
                    elif event.key == pygame.K_o and player_loot_data['bonus_lives_count'] > 0:
                        player_loot_data['bonus_lives_count'] -= 1
                        current_lives += 1

                    if event.key == pygame.K_ESCAPE:
                        PauseScreen()
                    elif event.key == pygame.K_F4:
                        sys.exit()
                    elif event.key == pygame.K_F11:
                        if full_screen == False:
                            pygame.display.set_mode((display_width, display_height), pygame.FULLSCREEN)
                            full_screen = True
                            PauseScreen()
                        elif full_screen == True:
                            pygame.display.set_mode((display_width, display_height))
                            PauseScreen()

                elif event.type == pygame.KEYUP:
                    if event.key == pygame.K_LEFT or event.key == pygame.K_RIGHT or event.key == pygame.K_a or event.key == pygame.K_d:
                        x_change = 0
                    elif event.key == pygame.K_UP or event.key == pygame.K_DOWN or event.key == pygame.K_w or event.key == pygame.K_s:
                        y_change = 0
                    elif event.key == pygame.K_SPACE:
                        y_change = 0

            if thing_startx < 0 - enemyWidth:
                thing_startx = 1500
                thing_starty = random.randrange(75, display_height - enemyWidth)
                thing_speed += -0.05
                player_loot_data['credit_count'] += earnable_credits
                credit_gain += earnable_credits

            if thing2_startx < 0 - enemyWidth:
                thing2_startx = 1500
                thing2_starty = random.randrange(75, display_height - enemyWidth)
                thing2_speed += -0.1
                player_loot_data['credit_count'] += earnable_credits
                credit_gain += earnable_credits

            player_x += x_change
            player_y += y_change
            thing_startx += thing_speed
            thing2_startx += thing2_speed

            if player_loot_data['high_score'] < current_score:
                player_loot_data['high_score'] = current_score

            if player_y > display_height:
                player_y = textbox_height
            if player_y < 10:
                player_y = display_height - playerHeight
            if player_x < 0 - playerHeight:
                player_x = (display_width * 0.5)
            if player_x > display_width:
                player_x = (display_width * 0.5)


            if player_y < thing_starty + enemyWidth and player_y + playerHeight > thing_starty:

                if player_x > thing_startx and player_x < thing_startx + enemyWidth or player_x + playerHeight > thing_startx and player_x + playerHeight < thing_startx + enemyWidth:

                    if current_lives > 0:
                        current_lives -= 1

                        player_x = (display_width * 0.5)
                        player_y = (display_height * 0.5)

                        thing_startx = 1500
                        thing2_startx = 1500                    

                    else:
                        dead = True

            if player_y < thing2_starty + enemyWidth and player_y + playerHeight > thing2_starty:

                if player_x > thing2_startx and player_x < thing2_startx + enemyWidth or player_x + playerHeight > thing2_startx and player_x + playerHeight < thing2_startx + enemyWidth:

                    if current_lives > 0:
                        current_lives -= 1

                        player_x = (display_width * 0.5)
                        player_y = (display_height * 0.5)

                        thing_startx = 1500
                        thing2_startx = 1500

                    else:
                        dead = True

            else:
                crossover = 'null'

            gameDisplay.fill(gray)

            player(player_x, player_y)
            asteroid(thing_startx, thing_starty)
            asteroid(thing2_startx, thing2_starty)

            text_box(textbox_x, textbox_y, textbox_width, textbox_height, blue)

            game_display_text('High Score: %s' % player_loot_data['high_score'], 5, 5, 30)
            game_display_text('Current Score: %s' % current_score, 5, 35, 30)
            game_display_text('Current Chances: %s' % current_lives, 200, 5, 30)

            if janus == True:
                game_display_text('Janus Enabled', 850, 5, 30)
            if peace_of_mind == True:
                game_display_text('Peace of Mind Enabled', 850, 35, 30)
            if invincibility == True:
                game_display_text('Invincibility Enabled', 950, 5, 30)

            if alpha == True:
                game_display_text('FPS: %s' % clock.get_fps(), 5, 635, 30)

            pygame.display.update()
            clock.tick()
        else:
            PauseScreen()

    DeadScreen()

if __name__ == '__main__':
    StartScreen()
    sys.exit()

您的文本呈现有两处错误。

第一个(可能也是主要的)一个是每次要显示一些文本时都要一次又一次地加载字体:

def game_display_text(display_msg, display_x, display_y, text_size):
    font = pygame.font.SysFont(None, text_size)
    ...

您应该创建一次字体对象,这样您就不会每次都从磁盘加载它。

第二个问题是 将文本渲染 Surface 是一个相当昂贵的操作:

def game_display_text(display_msg, display_x, display_y, text_size):
    ...
    text = font.render(str(display_msg), True, black)
    ...

更好的方法是缓存已经创建的表面,并重新使用它们。

一个非常简单的缓存可能如下所示:

text_font = pygame.font.SysFont("whatever", 14)
cache={}
def get_msg(msg):
    if not msg in cache:
      cache[msg] = text_font.render(msg, 1 , text_color)
    return cache[msg]

然后您将使用 get_msg 方法来创建您的文本表面。或者使用类似的东西lru_cache decorator.