我正在为我的第一个深入的Pyglet项目开发一种2D Minecraft克隆,我遇到了一个问题。每当我在屏幕上有相当数量的块时,帧速率就会急剧下降。
这是我的渲染方法: 我使用一个字典,其中键是元组(表示块的坐标),而项目是纹理。
我遍历整个字典并渲染每个块:
for key in self.blocks:
self.blocks[key].blit(key[0] * 40 + sx,key[1] * 40+ sy)
P.S。 sx和sy是屏幕滚动的坐标偏移
我想知道是否有办法更有效地渲染每个块。
答案 0 :(得分:4)
我会尽力解释为什么以及如何在不知道代码是什么的情况下确定代码。
我会假设你有以下几点:
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()
快得多,但我们现在就跳过原因并坚持<强烈>超速升级。< / p>
同样,这只是迈向成功帧率的一小步(除了有限的硬件之外...... duh )。
无论如何,回到pew pew你的代码升级。
现在您还想为批处理添加精灵,这样您就可以一次性渲染 LOT 的东西(读取:批处理),而不是手动将东西推送到图形卡。 图形卡灵魂目的旨在能够在一次疯狂快速的处理中处理吞吐量的吞吐量,而不是处理多个小I / O 。
为此,您需要创建批处理容器。并添加&#34;图层&#34;它 它真的很简单,你需要做的就是:
main_batch = pyglet.graphics.Batch()
background = pyglet.graphics.OrderedGroup(0)
# stuff_above_background = pyglet.graphics.OrderedGroup(1)
# ...
我们现在要批量生产一个,你可能不需要更多这个学习目的。
好的,所以你得到了你的批次,现在是什么?好了,现在我们尽最大努力扼杀你的显卡生活地狱,看看我们是否能在压力下扣紧(在这个过程中没有图形车受到伤害,请不要呛东西...... )强>
哦还有一件事,还记得有关共享对象的说明吗?好吧,我们在这里创建一个共享图像对象,我们将其推入精灵,而不是每隔一段时间加载一个新图像......单...时间.. monster_image
我们会调用它。 / p>
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
时会导致各种各样的错误和错误,所以我们&# 39; ll直接设置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简称。
要做到这一点,我们需要创建一个模仿Pyglet行为的class
并按顺序调用它的后续函数并轮询事件处理程序否则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.sprites
和self.batches
的任何内容之外什么都不做。
请注意!我们在sprite上调用._draw()
,因为我们之前创建了自己的sprite类?是的,这是一个很棒的基础精灵类,你可以在每个精灵上完成draw()
之前挂钩你自己的东西。
Anywho,这一切归结为几件事。
以下是所有困惑的作品的完整工作示例:
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()
,因为批量呈现会调用draw
而不是_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中制作游戏时该做什么以及该怎么想的一般想法。请记住,这样做很有趣,或者在你开始之前就已经退出了因为游戏需要时间来制作 ^^
Pew Pew lazors和东西,祝你好运!