Python pygame 如何设置FPS

Python pygame how to set the FPS

我正在制作 python 游戏,但我不知道应该将 FPS 设置为多少。我的游戏卡住了,不流畅。我怎么知道 FPS 应该是多少?

这是我的代码:

http://bin.shortbin.eu:8080/x87bSUKUiN

运行使用您的代码后,我还注意到帧速率下降会影响游戏的流畅性。

这里有两个不同的问题:

1. FPS 下降

FPS 下降的原因可能是您无法控制的,例如垃圾收集器的工作。 即使您无法控制此类问题,您通常也可以提高游戏的性能。查看以下游戏分析器 运行 的屏幕截图:

可以看到大部分时间花在了blit上。然而,很大一部分时间也花在了 get_y_list 上。 get_y_list 方法还使用大型列表,这会为垃圾收集器稍后收集产生大量垃圾,从而进一步影响性能。

据我了解,get_y_list 方法是您用于碰撞检测的一种非常粗略的方法的一部分,该方法基本上需要二次方时间。您的算法似乎将每个对象划分为大量的二维单元格,然后测试每对单元格之间的碰撞。相反,您可以只测试框与框的交集。如果您希望对象具有复杂的碰撞形状,您可以使用其他算法,互联网上有很多这样的算法。例如:https://developer.mozilla.org/en-US/docs/Games/Techniques/2D_collision_detection

使用其他算法进行碰撞检测将大大提高您的性能。

2。 FPS 下降时游戏变得不流畅。

您的对象的 xy 位置正在更新,例如:player.x -= player.vx。物理上正确的方法是:player.x -= player.vx * DELTA_T。其中 DELTA_T 是自上一帧以来经过的时间。如果您不使用自上一帧以来经过的时间,则角色的运动速度将取决于 FPS。这正是我们所看到的。

我从哪里得到 DELTA_T 你可能会问。调用 tick 时直接执行:

def main():
    global DELTA_T

    finish = False
    background_x = 0
    background_y = 0
    background = pygame.image.load(BACKGROUND_IAMGE)

    while not finish:
        DELTA_T = clock.tick(REFRESH_RATE)

我尝试用 DELTA_T 加乘法,但游戏变得不稳定,因为您假设物体在每一帧中精确地前进 vx 'cells' 以检测碰撞和地板穿透。请参阅下面的尝试:

import pygame

pygame.init()
WINDOW_WIDTH = 700
WINDOW_HEIGHT = 500
SIZE = (WINDOW_WIDTH, WINDOW_HEIGHT)
SCREEN = pygame.display.set_mode(SIZE)
pygame.display.set_caption("Python Game")
WHITE = (255, 255, 255)
RED = (255, 0, 0)
GREEN = (0, 255, 0)
BLUE = (0, 0, 255)
PINK = (255, 0, 255)
BACKGROUND_IAMGE = 'back.png'
clock = pygame.time.Clock()
REFRESH_RATE = 30
FRAMES_DELAY = 3

PLAYER_HEIGHT = 72
PLAYER_WIDTH = 40

MUSHROOM_HEIGHT = 31
MUSHROOM_WIDTH = 40

BLOCK_HEIGHT = 30
BLOCK_WIDTH = 30

FLOOR_Y = 435

DELTA_T = 0

class Player:
    def __init__(self, x, y):
        self.__images = [pygame.image.load('mario1.png'), pygame.image.load('mario2.png'),
                         pygame.image.load('mario3.png'), pygame.image.load('mario4.png'),
                         pygame.image.load('mario5.png'), pygame.image.load('mario6.png')]
        self.__current_image = self.__images[0]
        self.x = x
        self.y = y
        self.vx = 5/33.
        self.vy = 10/33.
        self.__is_mid_air = False
        self.__direction = "right"
        self.__is_jumping = False
        self.__MAX_JUMP = 20
        self.__is_walking = False
        self.__counter = 0
        self.__jump_counter = 0

    def reset_jump_counter(self):
        self.__jump_counter = 0

    def get_bottom_y(self):
        return int(self.y + PLAYER_HEIGHT)

    def set_walk(self, bol):
        self.__is_walking = bol

    def set_is_mid_air(self, bol):
        self.__is_mid_air = bol

    def is_mid_air(self):
        return self.__is_mid_air

    def get_image(self):
        if self.__is_mid_air and self.__direction == "right":
            self.__current_image = self.__images[4]
            return self.__current_image

        if self.__is_mid_air and self.__direction == "left":
            self.__current_image = self.__images[5]
            return self.__current_image

        self.__counter += 1
        if self.__counter > FRAMES_DELAY:
            self.__counter = 0

        if self.__counter == FRAMES_DELAY and self.__direction == "right":
            if self.__is_walking and self.__current_image == self.__images[0]:
                self.__current_image = self.__images[1]
            else:
                self.__current_image = self.__images[0]

        if self.__counter == FRAMES_DELAY and self.__direction == "left":
            if self.__is_walking and self.__current_image == self.__images[2]:
                self.__current_image = self.__images[3]
            else:
                self.__current_image = self.__images[2]

        return self.__current_image

    def stand_still(self):
        if self.__direction == "right":
            self.__current_image = self.__images[0]
        if self.__direction == "left":
            self.__current_image = self.__images[2]

    def set_jump(self, bol):
        self.__is_jumping = bol
        if not bol:
            self.reset_jump_counter()

    def check_jump(self):
        if self.__jump_counter != self.__MAX_JUMP and self.__is_jumping:
            self.y -= self.vy * DELTA_T
            self.__jump_counter += 1
        if self.__jump_counter >= self.__MAX_JUMP:
            self.__is_jumping = False
            self.__jump_counter = 0

    def is_jumping(self):
        return self.__is_jumping

    def fall(self):
        if not self.__is_jumping:
            self.y += self.vy * DELTA_T

    def get_direction(self):
        return self.__direction

    def turn_around(self):
        if self.__direction == "right":
            self.__direction = "left"
        elif self.__direction == "left":
            self.__direction = "right"

    def get_x_list(self):
        result = []
        for i in range(PLAYER_WIDTH + 1):
            result.append(self.x + i)
        return result

    def get_y_list(self):
        result = []
        for i in range(PLAYER_HEIGHT + 1):
            result.append(self.y + i)
        return result

    def get_right_x(self):
        return self.x + PLAYER_WIDTH

    def is_crash(self, obj):
        is_x_equals = False
        for i in range(int(self.vx * DELTA_T+0.5)):
            if self.x + PLAYER_WIDTH + i in obj.get_x_list():
                is_x_equals = True
            if self.x - i in obj.get_x_list():
                is_x_equals = True

        is_y_equals = False
        for i in range(int(self.vy*DELTA_T+0.5)):
            if self.y + PLAYER_HEIGHT + i in obj.get_y_list():
                is_y_equals = True
            if self.y - i in obj.get_y_list():
                is_y_equals = True

        return is_x_equals and is_y_equals

class Monster:
    def __init__(self, x, y, vx, vy, monster_type):
        self.__images = [pygame.image.load(monster_type + '1.png').convert(),
                         pygame.image.load(monster_type + '2.png').convert()]
        if monster_type == "mushroom":
            self.__width = MUSHROOM_WIDTH
            self.__height = MUSHROOM_HEIGHT

        self.__current_image = self.__images[0]
        self.__FIRST_X = x
        self.__FIRST_Y = y
        self.__x = x
        self.__y = y
        self.__vx = vx
        self.__vy = vy
        self.__direction = "left"
        self.__monster_type = monster_type
        self.__counter = 0

    def get_image(self):
        self.__counter += 1
        if self.__counter > FRAMES_DELAY:
            self.__counter = 0

        if self.__monster_type == "mushroom" and self.__counter == FRAMES_DELAY:
            if self.__current_image == self.__images[1]:
                self.__current_image = self.__images[0]
                self.__current_image.set_colorkey(PINK)
            else:
                self.__current_image = self.__images[1]
                self.__current_image.set_colorkey(PINK)
            return self.__current_image
        elif self.__monster_type == "mushroom" and self.__counter != FRAMES_DELAY:
            self.__current_image.set_colorkey(PINK)
            return self.__current_image

    def get_x(self):
        return self.__x

    def get_vx(self):
        return self.__vx

    def get_vy(self):
        return self.__vx

    def get_y(self):
        return self.__y

    def step(self):
        if self.__direction == "right":
            self.__x += self.__vx * DELTA_T
        elif self.__direction == "left":
            self.__x -= self.__vx * DELTA_T

    def get_direction(self):
        return self.__direction

    def turn_around(self):
        if self.__direction == "right":
            self.__direction = "left"
        elif self.__direction == "left":
            self.__direction = "right"

    def get_x_list(self):
        result = []
        for i in range(MUSHROOM_WIDTH + 1):
            result.append(self.__x + i)
        return result

    def get_y_list(self):
        result = []
        for i in range(MUSHROOM_HEIGHT + 1):
            result.append(self.__y + i)
        return result

    def is_crash(self, obj):
        is_x_equals = False
        for i in range(int(self.__vx * DELTA_T+0.5)):
            if self.__x + self.__width + i in obj.get_x_list():
                is_x_equals = True
            if self.__x - i in obj.get_x_list():
                is_x_equals = True

        is_y_equals = False
        for i in range(int(self.__vy * DELTA_T+0.5)):
            if self.__y + self.__height + i in obj.get_y_list():
                is_y_equals = True
            if self.__y - i in obj.get_y_list():
                is_y_equals = True

        return is_x_equals and is_y_equals

class Block:
    def __init__(self, x, y, width=1, height=1):
        self.__image = pygame.image.load("block.png")
        self.__x = x
        self.__y = y
        self.__width = width
        self.__height = height

    def load_image(self, background_x):
        for i in range(self.__width):
            for j in range(self.__height):
                SCREEN.blit(self.__image, (self.__x + (i * BLOCK_WIDTH) + background_x, self.__y + (j * BLOCK_HEIGHT)))

    def get_x_list(self):
        result = []
        for i in range(BLOCK_WIDTH * self.__width + 1):
            result.append(self.__x + i)
        return result

    def get_y_list(self):
        result = []
        for i in range(BLOCK_HEIGHT * self.__height + 1):
            result.append(self.__y + i)
        return result

    def get_y(self):
        return self.__y

    def get_x(self):
        return self.__x

    def get_width(self):
        return self.__width

    def get_height(self):
        return self.__height

    def get_bottom_y(self):
        return self.__y + (BLOCK_HEIGHT * self.__height)

    def get_right_x(self):
        return self.__x + self.__width * BLOCK_WIDTH

player = Player(140, FLOOR_Y - PLAYER_HEIGHT)
blocks = [Block(270, 280, 1, 1), Block(301, FLOOR_Y - BLOCK_HEIGHT * 8, 1, 8),
          Block(600, FLOOR_Y - BLOCK_HEIGHT * 8, 2, 8)]
monsters = [Monster(380, FLOOR_Y - MUSHROOM_HEIGHT, 3/33., 3/33., "mushroom")]

def main():
    global DELTA_T

    finish = False
    background_x = 0
    background_y = 0
    background = pygame.image.load(BACKGROUND_IAMGE)

    while not finish:
        DELTA_T = clock.tick(REFRESH_RATE)
        SCREEN.blit(background, (background_x, background_y))
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                finish = True

        block_on_right = False
        block_on_left = False
        is_player_on_block = False
        for block in blocks:
            block.load_image(background_x)
            for i in range(int(player.vy * DELTA_T+0.5)):
                for x in player.get_x_list():
                    if player.get_bottom_y() + i == block.get_y() and x in block.get_x_list():
                        is_player_on_block = True
                        player.y += i
                    if player.y - i == block.get_bottom_y() and x in block.get_x_list():
                        player.set_jump(False)
                        player.y -= i

            for i in range(int(player.vx*DELTA_T+0.5)):
                for y in player.get_y_list():
                    if player.get_right_x() + i == block.get_x() and y in block.get_y_list():
                        block_on_right = True
                        player.x += (i - 1)
                        background_x -= (i - 1)
                    if player.x - i == block.get_right_x() and y in block.get_y_list():
                        block_on_left = True
                        player.x -= (i - 1)
                        background_x += (i - 1)
            for monster in monsters:
                if monster.is_crash(block):
                    monster.turn_around()

        keys = pygame.key.get_pressed()

        if (keys[pygame.K_UP] or keys[pygame.K_SPACE] or keys[pygame.K_w]) and not player.is_mid_air():
            player.set_jump(True)

        if (keys[pygame.K_RIGHT] or keys[pygame.K_d]) and not block_on_right:
            if player.get_direction() != "right":
                player.turn_around()
            player.set_walk(True)

            background_x -= player.vx * DELTA_T
            player.x += player.vx * DELTA_T

        if (keys[pygame.K_LEFT] or keys[pygame.K_a]) and not block_on_left:
            if player.get_direction() != "left":
                player.turn_around()
            player.set_walk(True)

            if background_x != 0:
                background_x += player.vx * DELTA_T
                player.x -= player.vx * DELTA_T

        if not any(keys):
            player.stand_still()
            player.set_walk(False)

        for monster in monsters:
            monster.step()
            SCREEN.blit(monster.get_image(), (background_x + monster.get_x(), monster.get_y()))

        is_player_on_ground = False
        for i in range(int(player.vy * DELTA_T+0.5)):
            if player.get_bottom_y() + i == FLOOR_Y:
                player.y += i
                is_player_on_ground = True

        if is_player_on_block or is_player_on_ground:
            player.set_is_mid_air(False)
        else:
            player.set_is_mid_air(True)
            player.fall()

        player.check_jump()

        player_image = player.get_image().convert()
        player_image.set_colorkey(PINK)

        SCREEN.blit(player_image, (player.x + background_x, player.y))
        pygame.display.flip()

    pygame.quit()

if __name__ == '__main__':
    main()