【问题标题】:pygame - Player creating bullets causes noticable FPS dropspygame - 玩家创建子弹会导致明显的 FPS 下降
【发布时间】:2021-10-06 16:48:42
【问题描述】:

我需要我的游戏能够在屏幕上处理数百发子弹。虽然游戏似乎并没有因为子弹数量较少(大约 50 颗左右)而没有大幅减速,但它确实有它们,当屏幕上同时出现大约 100-200 颗子弹时问题变得更加明显。这引起了我特别担心的一个问题:我希望敌人也发射大量子弹,但如果玩家的射弹已经导致这样的减速,那么这是不可能的。

在我开始展示相关代码之前:

  • 我正在使用 pygame 2.0.1(SDL 2.0.14,Python 3.9.6)
  • 我在加载的每张图片上都使用 convert_alpha()
  • 我为玩家、子弹、敌人和 HUD 类使用脏精灵,并使用 pygame.sprite.LayeredDirty() 组来存储当前游戏中的所有精灵;敌人、子弹和 HUD 有额外的 pygame.sprite.Group() 用于碰撞检测(用于敌人和子弹)和重新排序精灵以在所有内容之上渲染(用于 HUD)。这似乎提高了性能,但大量子弹仍然会导致减速
  • 我使用 PyCharm 作为我的 IDE

我对编程很陌生,我不太确定究竟是什么导致了这个问题,所以我会尽量提供尽可能多的相关代码。如果没有一个罪魁祸首似乎是问题的原因,我会提供更多。如果信息不足,我提前道歉。

减速变得很荒谬,主要是因为我正在使用调试超级武器,尽管当我打开许多应用程序时,即使产生的子弹数量几乎没有(28 颗或更多),它有时也很明显。到目前为止,这是一个非常小的游戏,所以我不完全明白为什么。

def init_player_weapon_level(self):
    # debug mode
    if self.lev_weapon == -1:
        self.set_weapon_values(100, 5, 100, True)
        self.firing_pattern = [[self.bullet_images["blue"], 0, 0, 90, -24],
                               [self.bullet_images["blue"], 0, 0, 92, -24],
                               [self.bullet_images["blue"], 0, 0, 88, -24],
                               ...]
...

下面使用此函数中定义的触发模式列表。它很长。此列表根据玩家的武器等级替换,-1 为最高。该函数仅在创建播放器对象或通过按键手动启动时调用。

def fire(self, now):
    if self.check_firing_conditions(now):
        self.last_shot = now
        for o in self.firing_pattern:
            bullet = PlayerBullet01(o[0], self.rect.centerx + o[1], self.rect.top + o[2], o[3], o[4])
            all_sprites.add(bullet)
            bullets.add(bullet)
            self.generate_heat(now)

这是负责发射武器的函数。它创建与self.firing_pattern 中的参数列表一样多的子弹对象,具有图像 [0]、X [1] 和 Y [2] 生成偏移量(然后从玩家的位置添加或减去这些偏移量以产生错觉它们是从船上的大炮发射的),子弹发射的角度 [3](90 度是直线向上)和它穿过的速度 [4](它在发射模式中是负数,因为子弹向上移动) . 因为self.firing_pattern 有 35 个参数列表(我省略了,因为这会占用空间),所以一次创建了 35 个子弹,如果玩家按下开火键,则在两次射击之间有 100 毫秒的延迟发射。这使得在玩家的武器过热之前屏幕上会出现 245 颗子弹,从而导致武器停止射击。很多,但速度变慢似乎是由于屏幕上已经有一些子弹造成的。

class PlayerBullet01(pygame.sprite.DirtySprite):
    def __init__(self, image, x, y, deg, speed):
        pygame.sprite.DirtySprite.__init__(self)

        self.image = image
        self.rect = self.image.get_rect()
        self.center = self.rect.center

        self.position = pygame.math.Vector2(x, y)

        self.speed_mod = 0.065
        # deg - 90 because we don't want to rotate the image
        # by the amount of degrees it's supposed to face,
        # we want to rotate it by a much smaller amount
        # we also want to rotate it only once
        # since player bullets probably shouldn't follow
        # any other movement patterns than going straight to the target
        if deg != 0:
            self.image = pygame.transform.rotate(self.image, -deg + 90)
        self.rect = self.image.get_rect()
        self.rect.center = self.center

        # we only want the bullet to move in a particular direction at a particular speed
        self.direction = pygame.math.Vector2(0, 0)
        self.direction += self.move(deg, speed)

        # spawn bullet at the center of the spaceship
        self.rect.centerx = self.position.x
        self.rect.bottom = self.position.y

    def update(self):
        self.dirty = 1
        self.position.x += self.direction.x * self.speed_mod * x_clock.delay
        self.position.y += self.direction.y * self.speed_mod * x_clock.delay
        self.rect.center = (self.position.x, self.position.y)

        # die if out of bounds
        if self.rect.bottom < 0:
            self.kill()

    def move(self, degrees, offset):
        rads = math.radians(degrees)
        x = math.cos(rads) * offset
        y = math.sin(rads) * offset
        return x, y

这是子弹类。一开始我以为每次更新都会调用move 函数会减慢游戏速度(这是在早期版本的代码中,此处未显示),但实际上并没有改变。

    hits_enemy = pygame.sprite.groupcollide(enemies, bullets, True, True)
    for hit in hits_enemy:
        create_generic_enemy(GFX_ENEMY)

最后,这是检查子弹和敌人(屏幕上总是只有 10 个)之间碰撞的行。我禁用了子弹和敌人的碰撞,但它似乎仍然没有提高游戏的性能。

dirtyupdate = all_sprites.draw(screen)
pygame.display.update(dirtyupdate)

哦,这就是游戏在主游戏循环中在屏幕上绘制所有内容的方式。

最后一个可能的罪魁祸首: 我在 Stack Overflow 上找到了一种方法来阻止 pygame 出现微卡顿。这是一个非常烦人的问题,似乎无缘无故发生。它需要使用自定义时钟。我从这里将这段代码引入我的游戏:Simple Pygame animation stuttering

previous = time.time() * 1000

# this is so it's easy to get out of the game loop
# (you just set GAME_RUNNING to false)
GAME_RUNNING = True
while GAME_RUNNING:
    # CLOCK
    # a pretty advanced clock that stops pygame from stuttering like a -----
    current = time.time() * 1000
    elapsed = current - previous
    previous = current
    x_clock.delay = 1000.0 / MAX_FPS - elapsed
    x_clock.delay = max(int(x_clock.delay), 0)

    ...

    # delays the game by delay
    # using pygame.time.wait() instead of pygame.time.delay()
    # because pygame.time.delay() makes my system fan go -------
    pygame.time.wait(x_clock.delay)

为了记录,x_clock 文件中的delay 也被导入到类文件中。这是因为这些类需要将它们的移动调整为 FPS 下降。我不相信这段代码是导致延迟的原因,但我记得当我使用 pygame.time.delay() 而不是 wait() 时,我的粉丝疯了。

是什么导致了这种减速,我该如何解决?我已经考虑了一些可能性(其中一个可能是 Pygame 无法处理这么多弹丸),但我已经看到了一款游戏,它在屏幕上始终如一地创建了类似数量的子弹并且它没有任何减速.看了那个游戏的代码我也想不通。

【问题讨论】:

    标签: python pygame


    【解决方案1】:

    我不知道这是否真的是减速的根源,但以下方法可能有效:

    使用pygame.time.Clock() 作为您的计时器。 clock.tick(framerate)(或clock.tick_busy_loop(framerate),根据文档更精确)将限制最大帧速率。 clock.get_time() 将为您提供最后两次调用 clock.tick() / clock.tick_busy_loop() 之间的毫秒数。

    编辑:

    1 这是一个更高效的__init__ 方法:

    class PlayerBullet01(pygame.sprite.DirtySprite):
        images = {}
        for color in ['blue']:  # and your other bullet colors
            color_dict = {}
            for angle in range(360):
                img = pygame.transform.rotate(bullet_images[color], angle)
                color_dict[angle] = img, img.get_rect()
            images[color] = color_dict
    
        def __init__(self, color, x, y, deg, speed):
            pygame.sprite.DirtySprite.__init__(self)
    
            self.image, self.rect = self.images[color][deg]
    
            self.position = pygame.math.Vector2(x, y)
    
            self.speed_mod = 0.065
            # deg - 90 because we don't want to rotate the image
            # by the amount of degrees it's supposed to face,
            # we want to rotate it by a much smaller amount
            # we also want to rotate it only once
            # since player bullets probably shouldn't follow
            # any other movement patterns than going straight to the target
    
            # this code is now unnecessary
    
            # we only want the bullet to move in a particular direction at a particular speed
            self.direction = pygame.math.Vector2(0, 0)
            self.direction += self.move(deg, speed)
    
            # spawn bullet at the center of the spaceship
            self.rect.centerx = self.position.x
            self.rect.bottom = self.position.y
    

    通过预加载旋转的图像,无需在每次播放器开火时执行 35 次。

    2 使用以下脚本获取函数的运行时间:

    from time import time
    
    
    def timed(name):
        func_timer = FunctionTimer(name)
    
        def decorator(func):
    
            def wrapper(*args, **kwargs):
                start_time = time()
                return_value = func(*args, **kwargs)
                func_timer.add_time((time() - start_time) * 1000)
                return return_value
    
            return wrapper
    
        return decorator
    
    
    class FunctionTimer:
        all = []
        old_time = time()
        frame_time = 0
    
        @classmethod
        def get_frame_summary(cls):
            new_time = time()
            cls.frame_time = new_time - cls.old_time
            cls.old_time = new_time
            print(f'------------------------------\n'
                  f'FRAME TIME SUMMARY:\n'
                  f'total frame time: {cls.frame_time * 1000:.5}\n'
                  f'calls:\n'
                  f'name                  calls  average     min         max         total')
            for t in sorted(cls.all, key=lambda x: x.avg_time, reverse=True):
                if len(t.frame_list):
                    print(t)
    
            for t in cls.all:
                t.clear()
    
        def __init__(self, name):
            self.name = name
            self.frame_list = []
            self.frame_calls = 0
            self.total = 0
            self.min = 0
            self.max = 0
            self.avg = 0
            self.all.append(self)
    
        def __repr__(self):
            return f'{self.name: <20}  {self.frame_calls: <5}  {self.avg_time: <10.5}  ' \
                   f'{self.min_time: <10.5}  {self.max_time: <10.5}  {self.total_time: <10.5}'
    
        def add_time(self, added_time):
            self.frame_list.append(added_time)
            self.frame_calls += 1
    
        def clear(self):
            self.frame_list = []
            self.frame_calls = 0
    
        @property
        def min_time(self):
            self.min = min(self.frame_list)
            return self.min
    
        @property
        def max_time(self):
            self.max = max(self.frame_list)
            return self.max
    
        @property
        def avg_time(self):
            try:
                self.avg = sum(self.frame_list) / self.frame_calls
            except ZeroDivisionError:
                self.avg = 0
            return self.avg
    
        @property
        def total_time(self):
            self.total = sum(self.frame_list)
            return self.total
    
    

    用法:

    @timed('name of the function')
    def function(...):
        ...
    

    将定时装饰器放在您想要定时的任何函数之前。
    打电话

     FunctionTimer.get_frame_summary()
    

    在主循环中一次。
    输出:

    ------------------------------
    FRAME TIME SUMMARY:
    total frame time: [total time during this frame in ms]
    calls:
    name                  calls  average     min         max         total
    [name of the function = parameter you passed into the decorator]  
    [amount of times this function was called]  
    [average runtime of the function, the output will be sorted by this value]
    [minimal runtime]
    [maximal runtime]
    [total time spent in function (sum of all function call times)]
    -- all functions that have been called will print this summary
    sorted by their average runtime (descending) --
    

    【讨论】:

    • 这个答案与问题无关。你是不是把答案放错了?请更正这一点并将问题的答案放在它所属的位置。
    • 我以前用过这个时钟。 pygame 时钟会导致我上面提到的许多烦人的微口吃。然而,使用这个时钟实际上似乎可以解决滞后问题!似乎自定义时钟可能是最接近罪魁祸首的东西。但是,我真的不想使用 pygame 之一。在使用它时,您会注意到微小的口吃,这非常可怕。我可能不得不问一个类似于我提供链接的问题,但针对的是我的游戏。
    • 感谢您的回答,无论如何!这不完全是我想要的,但它似乎有效。我会等一段时间,但如果我没有找到更令人满意的,我会和你一起去,试着弄清楚如何摆脱口吃。
    • @Rabbid76 我看不出答案有什么问题。
    • @Ox0zOrwa 我刚刚更新了答案以(希望)解决这两个问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多