一尘不染

Pyglet图像渲染

python

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

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

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

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

PS sx和sy是屏幕滚动的坐标偏移

我想知道是否有一种方法可以更有效地渲染每个块。


阅读 364

收藏
2021-01-20

共1个答案

一尘不染

我将尽力解释为什么以及如何在不真正了解代码外观的情况下优化代码。

我将假设您具有以下特点:

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 ..其他 )。

无论如何,回到pew升级您的代码。
现在,您还希望将精灵添加到批处理中,这样就可以一次性渲染 很多 东西(读取:批处理),而无需手动将它们推到图形卡上。
显卡的灵魂目的被设计成能够处理吞吐量的千兆位计算在一个速度飞快走,而不是小的I / O的处理多个

为此,您需要创建一个批处理容器。并添加“图层”。
确实很简单,您需要做的就是:

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

现在,我们将批量生产一个,您可能不需要更多的学习资源。
好的,所以您有了批次,现在呢?好吧,现在我们尽最大努力将您的图形卡从地狱中抽出来,看看我们是否可以在压力下将其扣紧
(在此过程中,没有图形车受到伤害,请不要cho锁。)

还有一件事,还记得有关共享库的注释吗?好吧,我们将在此处创建一个共享图像对象,然后将其推入精灵中,而不是每次都加载一个新图像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到了子组中background。简单如馅饼。

这是踢球者,而不是呼叫self.blocks[key].blit().draw(),我们现在可以呼叫main_batch.draw(),它将把每个怪物都射向图形卡并产生奇迹。

好的,所以现在您已经优化了代码的速度,但是从长远来看,如果您要制作游戏,那对您没有帮助。或者在这种情况下,为您的游戏提供图形引擎。您要做的是加入大联盟并使用
课程 。如果您对之前的使用感到惊讶,那么在完成之后,您可能会松懈一下代码的外观。

好的,首先,您要为屏幕上的对象创建一个基类,让其在中调用baseSprite
现在,您需要使用Pyglet解决一些问题,例如,当继承Sprite尝试设置的对象image时,使用这些东西时会引起各种棘手的故障和错误,因此我们将self.texture直接进行设置,这基本上是相同的,但是我们将其挂接到pyglet库变量中;
D pew pew hehe。

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的简称。 __

为此,我们需要创建一个class模仿Pyglet行为并调用它的后续函数的命令,并轮询事件处理程序,否则sh 会卡住,请相信我。易于创建。
但是我有足够多的错误,main您可以使用一个基于类的基本类,该类使用基于轮询的事件处理,从而将
刷新率 限制在 您的编程中,**
而不是在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,做什么比呈现一个黑色的背景和任何归到其它类self.spritesself.batches

注意
._draw()之所以调用Sprite,是因为我们早先创建了自己的Sprite类?是的,这是很棒的基本sprite类,您可以在draw()对每个单独的sprite完成之前将其挂接到自己的东西中。

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

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

这是一个完整的例子,说明了所有困惑的部分:

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:
            #   http://stackoverflow.com/questions/16548833/pyglet-not-running-properly-on-amd-hd4250/16548990#16548990

x = main()
x.run()

一些奖励内容,我添加了GL选项,这些选项通常为您提供一些有益的内容。我还添加了一个FPS限制器,您可以修改它并使用它。

编辑:

批量 更新

由于可以通过将精灵对象全部发送到图形卡来一次完成大量渲染,因此类似地,您希望进行批量更新。例如,如果您要更新每个对象的位置,颜色或任何可能的颜色。

这是聪明的编程发挥作用的地方,而不是漂亮的小工具。
看,我在编程中所涉及的一切。如果您愿意的话。

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

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()因为批处理渲染将调用drawnot
_draw),因此必须对每个渲染序列进行更新和位置更新。那或者您可以创建一个继承baseSprite并仅更新那些类型的Sprite的新类:

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

因而只能调用.update()monster类型类/精灵。
要使其达到最佳状态有些棘手,并且有一些方法可以解决它并仍然使用批处理渲染,但是遵循这些思路的某个地方可能是一个好的开始。

重要说明: 我只是从脑子里写了很多(不是我第一次用Pyglet编写GUI类),无论出于何种原因,我的*
Nix实例都找不到我的X服务器。因此无法测试代码。

下班后的一个小时内,我会对其进行测试,但这可以使您大致了解在Pyglet中制作游戏时该做什么和该怎么想。记住,在玩的时候玩得开心,否则在开始之前就已经退出了,因为
游戏需要时间来制作 ^^

皮尤拉子剃刀和东西,祝你好运!

2021-01-20