Pyglet 图像渲染

Pyglet Image Rendering

我正在为我的第一个深度 Pyglet 项目开发一种 2D Minecraft 克隆,我 运行 遇到了一个问题。每当我在屏幕上有相当数量的块时,帧率就会急剧下降。

这是我的渲染方法: 我使用一个字典,键是一个元组(代表块的坐标),项目是一个纹理。

我遍历整个字典并渲染每个块:

for key in self.blocks:
    self.blocks[key].blit(key[0] * 40 + sx,key[1] * 40+ sy)

P.S。 sx 和 sy 是屏幕滚动的坐标偏移

我想知道是否有更有效地渲染每个块的方法。

我将尽我所能解释为什么以及如何优化您的代码,而无需实际了解您的代码是什么样子。

我假设您有以下内容:

self.blocks['monster001'] = pyglet.image.load('./roar.png')

如果你想加载一张你不想做太多事情的静态图像,这一切都很好而且花花公子。但是,您正在制作一款游戏,您将使用大量的精灵和对象,而不仅仅是一个简单的图像文件。

这就是共享对象、批处理和精灵派上用场的地方。 首先,将您的图像输入到精灵中,这是一个好的开始。

sprite = pyglet.sprite.Sprite(pyglet.image.load('./roar.png'))
sprite.draw() # This is instead of blit. Position is done via sprite.x = ...

现在,出于多种原因,绘制速度比 .blit() 快得多,但我们暂时跳过原因,只坚持 超快的速度升级.

同样,这只是朝着成功的帧速率迈出的一小步(除了有限的硬件 ofc.. duh)。

无论如何,回到 pew 你的代码升级。
现在您还想将 sprite 添加到批处理中,这样您就可以同时渲染 LOT 的东西(读取:批处理),而不是手动将东西推送到显卡。 显卡的灵魂目的是为了能够在一次疯狂的快速运行中处理千兆位的计算吞吐量,而不是处理多个小 I/O 的

为此,您需要创建批处理容器。并添加 "layers" 到它。
真的很简单,你需要做的就是:

main_batch = pyglet.graphics.Batch()
background = pyglet.graphics.OrderedGroup(0)
# stuff_above_background = pyglet.graphics.OrderedGroup(1)
# ...

我们现在将一个批处理,您可能不需要更多用于此学习目的。
好的,所以你得到了你的批次,现在呢?好吧,现在我们尽最大努力从你的显卡中扼杀那个活生生的地狱,看看我们是否能在压力下扣住它(在这个过程中没有图形汽车受到伤害,请不要扼杀东西.. )

还有一件事,还记得关于共享对象的注释吗?好吧,我们将在这里创建一个共享图像对象,我们将其推送到精灵中,而不是每次加载一个新图像.. 单...时间.. monster_image 我们称之为。

monster_image = pyglet.image.load('./roar.png')
for i in range(100): # We'll create 100 test monsters
    self.blocks['monster'+str(i)] = pyglet.sprite.Sprite(imgage=monster_image, x=0, y=0, batch=main_batch, group=background)

现在您已创建 100 个怪物并添加到 main_batch 批次 sub-group background 中。像馅饼一样简单。

这是关键,我们现在可以调用 main_batch.draw() 而不是调用 self.blocks[key].blit().draw(),它会把每一个怪物开到显卡上并产生奇迹。

好的,现在您已经优化了代码的速度,但如果您正在制作游戏,那么从长远来看这对您没有帮助 运行。或者在这种情况下,您的游戏的图形引擎。你想要做的是加入大联盟并使用 classes。如果您之前感到惊讶,您可能会忘记您的代码在完成后看起来多么棒。

好的,首先,您要为屏幕上的对象创建一个基础 class,让我们调用 baseSprite
现在 Pyglet 需要解决一些问题和问题,例如,当继承 Sprite 对象时试图设置 image 会在处理这些问题时导致各种不稳定的故障和错误,所以我们'我会直接设置 self.texture,这基本上是一样的,但我们改为挂接到 pyglet 库变量;D pew pew 呵呵。

class baseSprite(pyglet.sprite.Sprite):
    def __init__(self, texture, x, y, batch, subgroup):
        self.texture = texture

        super(baseSprite, self).__init__(self.texture, batch=batch, group=subgroup)
        self.x = x
        self.y = y

    def move(self, x, y):
        """ This function is just to show you
            how you could improve your base class
            even further """
        self.x += x
        self.y += y

    def _draw(self):
        """
        Normally we call _draw() instead of .draw() on sprites
        because _draw() will contains so much more than simply
        drawing the object, it might check for interactions or
        update inline data (and most likely positioning objects).
        """
        self.draw()

现在这是你的基地,你现在可以通过以下方式创建怪物:

main_batch = pyglet.graphics.Batch()
background = pyglet.graphics.OrderedGroup(0)
monster_image = pyglet.image.load('./roar.png')
self.blocks['monster001'] = baseSprite(monster_image, 10, 50, main_batch, background)
self.blocks['monster002'] = baseSprite(monster_image, 70, 20, main_batch, background)

...
main_batch.draw()

如何,您可能会使用 其他人 正在使用的默认 @on_window_draw() 示例,这很好,但我发现它缓慢、丑陋并且在长 运行。你想做面向对象编程..对吧?
就是这么叫的,我把它叫做你喜欢看一整天的可读代码。简称RCTYLTWADL.

为此,我们需要创建一个模仿 Pyglet 行为的 class 并按顺序调用它的后续函数并轮询事件处理程序,否则 sh** 会卡住,相信我。 . 做了几次,瓶颈很容易产生。
但是我的错误够多了,这里有一个基本的 main class,你可以使用它使用 poll-based 事件处理,从而限制 刷新率到你的编程 而不是 Pyglet 中的内置行为。

class main(pyglet.window.Window):
    def __init__ (self):
        super(main, self).__init__(800, 800, fullscreen = False)
        self.x, self.y = 0, 0
        self.sprites = {}
        self.batches = {}
        self.subgroups = {}

        self.alive = 1

    def on_draw(self):
        self.render()

    def on_close(self):
        self.alive = 0

    def render(self):
        self.clear()

        for batch_name, batch in self.batches.items():
            batch.draw()

        for sprite_name, sprite in self.sprites.items():
            sprite._draw()

        self.flip() # This updates the screen, very much important.

    def run(self):
        while self.alive == 1:
            self.render()

            # -----------> This is key <----------
            # This is what replaces pyglet.app.run()
            # but is required for the GUI to not freeze.
            # Basically it flushes the event pool that otherwise
            # fill up and block the buffers and hangs stuff.
            event = self.dispatch_events()

x = main()
x.run()

现在这又只是一个基本的 main class,除了渲染黑色背景和 self.spritesself.batches.[=48 中的任何内容外,它什么都不做=]

注意!我们在精灵上调用 ._draw() 是因为我们之前创建了自己的精灵 class?是的,这是很棒的基础精灵 class,您可以在 draw() 对每个单独的精灵完成之前挂接您自己的东西。

任何人,这都归结为几件事。

  1. 制作游戏时使用精灵,您的生活会更轻松
  2. 使用批处理,您的 GPU 会爱上您,刷新率会惊人
  3. 使用 classes 和东西,你的眼睛和代码魔力最终会爱上你。

这是一个工作正常的前任将所有拼凑在一起的部分放在一起:

import pyglet
from pyglet.gl import *

glEnable(GL_BLEND)
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glEnable(GL_LINE_SMOOTH)
glHint(GL_LINE_SMOOTH_HINT, GL_DONT_CARE)

pyglet.clock.set_fps_limit(60)

class baseSprite(pyglet.sprite.Sprite):
    def __init__(self, texture, x, y, batch, subgroup):
        self.texture = texture

        super(baseSprite, self).__init__(self.texture, batch=batch, group=subgroup)
        self.x = x
        self.y = y

    def move(self, x, y):
        """ This function is just to show you
            how you could improve your base class
            even further """
        self.x += x
        self.y += y

    def _draw(self):
        """
        Normally we call _draw() instead of .draw() on sprites
        because _draw() will contains so much more than simply
        drawing the object, it might check for interactions or
        update inline data (and most likely positioning objects).
        """
        self.draw()

class main(pyglet.window.Window):
    def __init__ (self):
        super(main, self).__init__(800, 800, fullscreen = False)
        self.x, self.y = 0, 0
        self.sprites = {}
        self.batches = {}
        self.subgroups = {}

        self._handles = {}

        self.batches['main'] = pyglet.graphics.Batch()
        self.subgroups['base'] = pyglet.graphics.OrderedGroup(0)

        monster_image = pyglet.image.load('./roar.png')
        for i in range(100):
            self._handles['monster'+str(i)] = baseSprite(monster_image, randint(0, 50), randint(0, 50), self.batches['main'], self.subgroups['base'])

        # Note: We put the sprites in `_handles` because they will be rendered via
        # the `self.batches['main']` batch, and placing them in `self.sprites` will render
        # them twice. But we need to keep the handle so we can use `.move` and stuff
        # on the items later on in the game making process ;)

        self.alive = 1

    def on_draw(self):
        self.render()

    def on_close(self):
        self.alive = 0

    def render(self):
        self.clear()

        for batch_name, batch in self.batches.items():
            batch.draw()

        for sprite_name, sprite in self.sprites.items():
            sprite._draw()

        self.flip() # This updates the screen, very much important.

    def run(self):
        while self.alive == 1:
            self.render()

            # -----------> This is key <----------
            # This is what replaces pyglet.app.run()
            # but is required for the GUI to not freeze.
            # Basically it flushes the event pool that otherwise
            # fill up and block the buffers and hangs stuff.
            event = self.dispatch_events()

            # Fun fact:
            #   If you want to limit your FPS, this is where you do it
            #   For a good example check out this SO link:
            #   

x = main()
x.run()

一些额外的东西,我添加了 GL 选项,这些选项通常会为您做一些有益的事情。 我还添加了一个 FPS 限制器,您可以修改和使用它。

编辑:

批量更新

由于 sprite 对象可用于通过将其全部发送到图形卡来一次完成大量渲染,因此您同样希望进行批量更新。 例如,如果您想更新每个对象的位置、颜色或任何可能的内容。

这是聪明的编程而不是漂亮的小工具发挥作用的地方。
看,我与编程相关的一切.. 如果你想要的话。

假设您(在代码的顶部)有一个名为的变量:

global_settings = {'player position' : (50, 50)}
# The player is at X cord 50 and Y cord 50.

在您的基本精灵中,您可以简单地执行以下操作:

class baseSprite(pyglet.sprite.Sprite):
    def __init__(self, texture, x, y, batch, subgroup):
        self.texture = texture

        super(baseSprite, self).__init__(self.texture, batch=batch, group=subgroup)
        self.x = x + global_settings['player position'][0]#X
        self.y = y + global_settings['player position'][1]#Y

请注意,您必须稍微调整 draw()(注意,不是 _draw(),因为批处理渲染将调用 draw 而不是 _draw)功能位来尊重和更新每个渲染序列的位置更新。那或者你可以创建一个新的 class 继承 baseSprite 并且只更新那些类型的精灵:

class monster(baseSprite):
    def __init__(self, monster_image, main_batch, background):
        super(monster, self).__init__(imgage=monster_image, x=0, y=0, batch=main_batch, group=background)
    def update(self):
        self.x = x + global_settings['player position'][0]#X
        self.y = y + global_settings['player position'][1]#Y

因此仅在 monster 类型 classes/sprites 上调用 .update()
让它达到最佳状态有点棘手,有一些方法可以解决它并仍然使用批处理渲染,但沿着这些路线的某个地方可能是一个好的开始。



重要说明 我只是从头顶写了很多这样的东西(这不是我第一次在 Pyglet 中编写 GUI class)并且出于某种原因 *我的 Nix 实例找不到我的 X-server.. 所以无法测试代码。

下班后我会在一个小时内对其进行测试,但这可以让您大致了解在使用 Pyglet 制作游戏时应该做什么以及应该考虑什么。请记住,玩得开心,否则你还没开始就放弃了,因为游戏需要时间来制作 ^^

Pew pew 剃刀之类的东西,祝你好运!