Pyglet 应用 运行 慢慢来

Pyglet App Running Slowly

我有这个应用程序,它正在创建一个名为 pizza clicker 的 cookie clicker 的替代版本。它非常基本,但 运行 真的很慢,我不明白为什么。

import pyglet
window = pyglet.window.Window(fullscreen=True, caption="Click For Pizzas", resizable=True)
win_width = window.width
win_height = window.height
window.set_fullscreen(False)
window.set_size(win_width, win_height)
image_height = round(int(win_width/5)/1.4, 1)
class Main(object):
    def __init__(self):
        self.label = pyglet.text.Label('Pizzas: 0', font_size=100, color=(0, 0, 0, 255),
                                   x=win_width//2, y=win_height - 100,
                                   anchor_x='left', anchor_y='top')
        self.points = 0
        self.number = 1
    def background(self):
        background_img = pyglet.resource.image('pizza_clicker.png')
        background_img.width = (win_width/5)*4
        background_img.height = win_height
        background_img.blit(int(win_width/5), 0, 0.5)
    def drawSidebar(self):
        width = int(win_width/5)
        height = int(win_height)
        sidebar_pattern = pyglet.image.SolidColorImagePattern(color=(100, 100, 100, 100))
        sidebar = sidebar_pattern.create_image(width, height)
        sidebar.blit(0, 0)
        pizza = []
        images = ('pizza_1.png', 'pizza_5.png', 'pizza_5.png', 'pizza_5.png')
        for i in range (0, len(images)):
          divideby = 1.4 / (i + 1)
          pizza.append(pyglet.resource.image(images[i]))
          pizza[i].width = width
          pizza[i].height = round(width/1.4, 1)
          pizza[i].blit(0, window.height - (round(width/divideby, 1)))
    def getNumber(self, y):
        if y > window.height - int(image_height):
            self.number = 1
        elif y > window.height - (int(image_height)*2):
            self.number = 5
        elif y > window.height - (int(image_height)*3):
            self.number = 10
        elif y > window.height - (int(image_height)*4):
            self.number = 20
    def addPoint(self):
       self.points += self.number
       self.label.text = 'Pizzas: %s' %self.points


@window.event
def on_mouse_press(x, y, buttons, modifiers):
    if buttons & pyglet.window.mouse.LEFT and x > win_width/5:
        main.addPoint()
    elif buttons & pyglet.window.mouse.LEFT and x < win_width/5:
        main.getNumber(y)

@window.event
def on_draw():
    window.clear()
    main.background()
    main.label.draw()
    main.drawSidebar()

main = Main()

pyglet.app.run()

所以问题是,当我点击 window 的右侧时,它应该会立即添加一个(或多个)点,但它会滞后几秒钟。另外,为了避免有人感到困惑,代码确实有效,但速度很慢。我应该怎么做才能解决?

在每 draw() 次迭代中,您正在做:

background_img = pyglet.resource.image('pizza_clicker.png')

这意味着您正在从硬盘驱动器加载同一张图片,每个渲染序列。您还对不同的比萨饼图像进行了 for 循环,您还可以从硬盘驱动器中获取它们:

for i in range (0, len(images)):
    divideby = 1.4 / (i + 1)
    pizza.append(pyglet.resource.image(images[i]))

我强烈建议您阅读资源加载方式,并使用 cProfiler 分析器。

关于如何分析代码的一个很好的例子是 here。 由于这是一个外部来源,我将包括两个 SO 的链接,它们同样好(但不是那么有效或自我解释):

  • Using cProfile results with KCacheGrind

这是一个 tl-dr 版本:

python -m cProfile -o profile_data.pyprof your_script.py
pyprof2calltree -i profile_data.pyprof -k

这应该呈现一个所谓的 "call tree",其中包含您的代码执行的所有操作、它们花费了多长时间以及它们耗尽了多少内存。从头到尾的应用程序。

但是,我强烈建议您执行 1 次渲染序列 并在第一次渲染后添加一个 exit(1)。就像你分析 1 运行,而不是每秒 60 次。

搜索关键字以了解您的代码运行缓慢的原因:Python、profiling、kcachegrind、cprofile、cprofiling、callstack。

剧透警告

要解决您的大部分问题,请将所有 I/O 密集型操作(加载图像、创建形状等)移动到主 class 的 __init__ 调用中。

最终产品看起来像这样:

class Main(object):
    def __init__(self):
        self.label = pyglet.text.Label('Pizzas: 0', font_size=100, color=(0, 0, 0, 255),
                                   x=win_width//2, y=win_height - 100,
                                   anchor_x='left', anchor_y='top')
        self.points = 0
        self.number = 1

        self.background_img = pyglet.resource.image('pizza_clicker.png')
        self.background_img.width = (win_width/5)*4
        self.background_img.height = win_height

        sidebar_pattern = pyglet.image.SolidColorImagePattern(color=(100, 100, 100, 100))
        self.sidebar = sidebar_pattern.create_image(width, height)

        self.pizzas = []
        width = int(win_width/5)
        height = int(win_height)
        self.pizza_images = ('pizza_1.png', 'pizza_5.png', 'pizza_5.png', 'pizza_5.png')
        for i in range (0, len(pizza_images)):
            resource = pyglet.resource.image(pizza_images[i])
            resource.width = width
            resource.height = round(width/1.4, 1) # Not sure why you're using width here.. meh.. keeping it -yolo-
            self.pizzas.append(resource)

    def background(self):
        self.background_img.blit(int(win_width/5), 0, 0.5)

    def drawSidebar(self):
        width = int(win_width/5)
        height = int(win_height) # You're using win_height here, but window.height later. It's strange.
        self.sidebar.blit(0, 0)
        for i in range (0, len(self.pizza_images)):
            divideby = 1.4 / (i + 1)
            self.pizzas[i].blit(0, window.height - (round(width/divideby, 1)))

    def getNumber(self, y):
        if y > window.height - int(image_height):
            self.number = 1
        elif y > window.height - (int(image_height)*2):
            self.number = 5
        elif y > window.height - (int(image_height)*3):
            self.number = 10
        elif y > window.height - (int(image_height)*4):
            self.number = 20

    def addPoint(self):
       self.points += self.number
       self.label.text = 'Pizzas: %s' %self.points

但为什么要停在这里,这里大量使用 blit。 Blit 适用于一两个对象。但是很快就很难跟踪您 "blitting" 的所有内容和位置。您还在循环和其他内容中进行大量除法、加法和其他类型的计算。

请记住,循环是渲染中的魔鬼。
如果你在某个地方有一个循环,你几乎可以肯定地开始在那里寻找性能问题 (任何人看着这个评论然后去 "pff he has no clue what he's saying".. 是的我知道,但这是一个很好的初学者提示).

我强烈建议你将你的图片放入pyglet.sprite.Sprite() objects instead. They keep track of positions, rendering and most importantly, they support batched rendering。那是你祖国的圣杯!如果有什么可以在 pyglet 中节省您的性能......好吧......通常是 3D 渲染,它是批处理渲染。

看,图形卡的设计只考虑了一件事情。把一个巨大的数学方程式全部吞下。它特别擅长获取大量信息并将其直接投射到屏幕上。它不擅长多个命令。这意味着如果您将许多较小的数据包来回发送到显卡,由于开销和其他因素,它不会在接近最佳状态时执行磨损。

因此,将您的图像放入 sprite 中,并将这些 sprite 放入批次中,并且不使用任何 for 循环和渲染资源加载..

这就是您的代码的样子:

class Main(object):
    def __init__(self):
        self.label = pyglet.text.Label('Pizzas: 0', font_size=100, color=(0, 0, 0, 255),
                                   x=win_width//2, y=win_height - 100,
                                   anchor_x='left', anchor_y='top')
        self.points = 0
        self.number = 1

        self.background_layer = pyglet.graphics.OrderedGroup(0)
        self.foreground_layer = pyglet.graphics.OrderedGroup(1)
        self.batch = pyglet.graphics.Batch()

        self.background_img = pyglet.sprite.Sprite(pyglet.resource.image('pizza_clicker.png'), batch=self.batch, group=self.background_layer)
        self.background_img.width = (win_width/5)*4
        self.background_img.height = win_height
        self.background.x = int(win_width/5)
        self.background.y = 0

        sidebar_pattern = pyglet.image.SolidColorImagePattern(color=(100, 100, 100, 100))
        self.sidebar = pyglet.sprite.Sprite(sidebar_pattern.create_image(width, height), batch=self.batch, group=self.background_layer)
        self.sidebar.x = 0
        self.sidebar.y = 0

        self.pizzas = []
        width = int(win_width/5)
        height = int(win_height)
        self.pizza_images = ('pizza_1.png', 'pizza_5.png', 'pizza_5.png', 'pizza_5.png')
        for i in range (0, len(pizza_images)):
            divideby = 1.4 / (i + 1)

            resource = pyglet.sprite.Sprite(pyglet.resource.image(pizza_images[i]), batch=self.batch, group=self.foreground_layer)
            resource.width = width
            resource.height = round(width/1.4, 1) # Not sure why you're using width here.. meh.. keeping it -yolo-
            resource.x = 0
            resource.y = window.height - (round(width/divideby, 1))

            self.pizzas.append(resource)

    def draw(self):
        # This is instead of doing:
        # - self.background.draw()
        # - self.sidebar.draw()
        # - self.pizzas[i].draw()
        self.batch.draw()
        self.label.draw() # You could put this in a batch as well :)

    def getNumber(self, y):
        if y > window.height - int(image_height):
            self.number = 1
        elif y > window.height - (int(image_height)*2):
            self.number = 5
        elif y > window.height - (int(image_height)*3):
            self.number = 10
        elif y > window.height - (int(image_height)*4):
            self.number = 20

    def addPoint(self):
        self.points += self.number
        self.label.text = 'Pizzas: %s' %self.points

@window.event
def on_draw():
    window.clear()
    main.draw()

现在,代码并不完美。但它有望让您了解应该朝着什么方向前进。我也没有执行这段代码,主要是因为我没有所有的披萨图像或时间。我可能会回到这里,并整理我遇到的(最有可能的)拼写错误。