在一个Pyglet窗口中有多个精灵

时间:2018-01-09 22:22:20

标签: python pyglet

我有下面的代码,它当前在空白的Pyglet窗口上输出图像,但是只输出了一个图像。我真的需要每两秒钟添加一个新图像,之前的图像也保持完整。例如,添加一个图像,两秒钟后添加另一个图像,并在添加另一个图像两秒后添加。我添加了随机库,因此可以添加随机图像。

我所拥有的代码如下所示,它只显示一张图片 - 我觉得这会卡在绘图部分的某处。

this.value

我们非常感谢您提供的任何帮助或建议。

1 个答案:

答案 0 :(得分:2)

您的代码有一些问题/建议。 首先,以下代码是多余的:

while True:
     images = ["Image 1.png", "Image 2.png", "Image 3.png"]
     ....
     pyglet.app.run()

因为pyglet.app.run()是一个阻塞调用,意思是,循环永远不会循环 - 因为pyglet.app.run()本身就是一个循环(稍后会详细介绍)。
除非您的应用程序崩溃,但您没有处理这些异常,所以即使在这种情况下也不会重新运行/循环代码。

其次,你永远不应该在循环中定义数组/列表或任何内容。循环通常用于逻辑操作,而不是创建事物。最有趣的是,有时候在循环中创建东西很有用,但那些时候它们往往伴随着if语句。

资源为计算机花费了很多,无论是设置还是内存/硬盘驱动器等。因此建议尽可能早地创建列表并在任何循环之外创建列表。例如:

window = pyglet.window.Window()
images = ["Image 1.png", "Image 2.png", "Image 3.png"]
while True:
     choice = images[random.randint(0,2)]

本来是一个更好的选择,如果 - 再次 - 循环实际循环。在这种情况下,这只是一个整理的问题。

此外,这段代码:

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

也不应该在循环中创建,它意味着替换您的window变量on_draw函数。所以应该尽快将其移出并放入您的逻辑IMO中。至少保持与所有其他逻辑分开,因此它不在“添加随机图像”和“循环”内部。

现在,你的代码失败的主要原因是你认为这会循环,但事实并非如此。同样,pyglet.app.run()将锁定您在该行上的代码执行,因为它是该函数调用中的永无止境的循环。

您可以扩展代码并从pyglet.py的源代码中复制粘贴代码,它看起来像这样(只是为了让您了解正在发生的事情):

window = pyglet.window.Window()
while True:
    images = ["Image 1.png", "Image 2.png", "Image 3.png"]
     choice = images[random.randint(0,2)]
     rawImage = pyglet.resource.image(choice)
     sprite = pyglet.sprite.Sprite(rawImage)

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

    time.sleep(2)

    def run(self):
        while True:
            event = self.dispatch_events()
            if event:
                self.on_draw()

注意pyglet.app.run()如何扩展到另一个while True循环,从未真正中断。 有点过于简单,但这基本上就是发生了什么。

因此,您的sprite = pyglet.sprite.Sprite(rawImage)永远不会被重新触发 那么,对于你的第二大问题,为什么这段代码永远不会起作用:

你在做:

def on_draw():
    sprite.draw()

但是每个循环你都会用sprite替换旧的sprite = pyglet.sprite.Sprite(rawImage)对象,换一个新对象。所以你想要做的是将一个列表/字典保留在循环之外,包含所有可见图像,然后添加它并仅渲染图像。

很像这样:

import pyglet
import time
import random

width, height = 800, 600
window = pyglet.window.Window(width, height)

## Image options are the options we have,
## while `images` are the visible images, this is where we add images
## so that they can be rendered later
image_options = ["Image 1.png", "Image 2.png", "Image 3.png"]
images = {}

## Keep a timer of when we last added a image
last_add = time.time()

## Just a helper-function to generate a random image and return it
## as a sprite object (good idea to use sprites, more on that later)
def get_random_image():
    choice = image_options[random.randint(0, len(image_options)-1)]
    return pyglet.sprite.Sprite(pyglet.image.load(choice))

## Here, we define the `on_draw` replacement for `window.on_draw`,
## and it's here we'll check if we should add a nother image or not
## depending on how much time has passed.
@window.event
def on_draw():
    window.clear()

    ## If two seconds have passed, and the ammount of images added are less/equal
    ## to how many images we have in our "database", aka `image_options`, then we'll
    ## add another image somewhere randomly in the window.
    if time.time() - last_add > 2 and len(images) < len(image_options):
        last_add = time.time()

        image = get_random_image()
        image.x = random.randint(0, width)
        image.y = random.randint(0, height)
        images[len(images)] = image

    ## Then here, is where we loop over all our added images,
    ## and render them one by one.
    for _id_ in images:
        images[_id_].draw()

## Ok, lets start off by adding one image.
image = get_random_image()
image.x = random.randint(0, width)
image.y = random.randint(0, height)
images[len(images)] = image

## And then enter the never ending render loop.
pyglet.app.run()

现在,只有当你按下一个键或在窗口内按下鼠标时,这才有效。这是因为这是调度事件的唯一时间。如果有一个被触发的事件,Pyglet只会渲染事物。有两种方法可以解决这个问题,即硬核OOP方式,我现在将跳过它。

第二种是使用所谓的Pyglet时钟,在那里你可以安排一些间隔发生的事情。我并不擅长这部分,因为我倾向于使用我自己的调度程序等。

但这是它的要点:

def add_image():
    images[len(images)] = get_random_image()

pyglet.clock.schedule_interval(add_image, 2) # Every two seconds

这比做if time.time() - last_add > 2更清洁 结果应如下所示:

import pyglet
import time
import random

width, height = 800, 600
window = pyglet.window.Window(width, height)

## Image options are the options we have,
## while `images` are the visible images, this is where we add images
## so that they can be rendered later
image_options = ["Image 1.png", "Image 2.png", "Image 3.png"]
images = {}

## Just a helper-function to generate a random image and return it
## as a sprite object (good idea to use sprites, more on that later)
def get_random_image():
    choice = image_options[random.randint(0, len(image_options)-1)]
    return pyglet.sprite.Sprite(pyglet.image.load(choice))

def add_image(actual_time_passed_since_last_clock_tick):
    image = get_random_image()
    image.x = random.randint(0, width)
    image.y = random.randint(0, height)
    images[len(images)] = image

## Here, we define the `on_draw` replacement for `window.on_draw`,
## and it's here we'll check if we should add a nother image or not
## depending on how much time has passed.
@window.event
def on_draw():
    window.clear()

    ## Then here, is where we loop over all our added ima ges,
    ## and render them one by one.
    for _id_ in images:
        images[_id_].draw()

## Ok, lets start off by adding one image.
image = get_random_image()
image.x = random.randint(0, width)
image.y = random.randint(0, height)
images[len(images)] = image

## Add the schedule interval of adding a image every two seconds.
pyglet.clock.schedule_interval(add_image, 2)

## And then enter the never ending render loop.
pyglet.app.run()

这样,您无需按任何键或鼠标来触发Pyglet中的事件,它将为您处理该事件并执行您计划执行的操作。

接下来,是我的一个小优化。这是一个奖励,并会加快速度。它被称为批量渲染,当您渲染大量图像和精灵时,您当前一次向显卡发送一个图像。这非常 CPU 密集型。你想要做的就是把工作放在 GPU 上。因为毕竟你正在使用图形,对吗?

因此,在这种情况下,批量渲染非常简单。每次调用pyglet.sprite.Sprite时,它都有一个名为batch=None的参数(默认值)。如果您向精灵对象添加批处理,则可以通过调用batch.draw()而不是每个sprite.draw()来渲染整个批处理。

解决方案看起来像这样:

import pyglet
import time
from random import randint

width, height = 800, 600
window = pyglet.window.Window(width, height)
main_batch = pyglet.graphics.Batch()

## Image options are the options we have,
## while `images` are the visible images, this is where we add images
## so that they can be rendered later
image_options = ["Image 1.png", "Image 2.png", "Image 3.png"]
images = {}

## Just a helper-function to generate a random image and return it
## as a sprite object (good idea to use sprites, more on that later)
def get_random_image(x=0, y=0):
    choice = image_options[randint(0, len(image_options)-1)]
    return pyglet.sprite.Sprite(pyglet.image.load(choice), x=x, y=y, batch=main_batch)

def add_image(actual_time_passed_since_last_clock_tick=0):
    image = get_random_image(x=randint(0, width), y=randint(0, height))
    images[len(images)] = image

## Here, we define the `on_draw` replacement for `window.on_draw`,
## and it's here we'll check if we should add a nother image or not
## depending on how much time has passed.
@window.event
def on_draw():
    window.clear()

    ## Instead of looping over each image in `images`,
    ## just do:
    main_batch.draw()

## Ok, lets start off by adding one image.
## Instead of doing it manually, use the function add_image.
add_image()

## Add the schedule interval of adding a image every two seconds.
pyglet.clock.schedule_interval(add_image, 2)

## And then enter the never ending render loop.
pyglet.app.run()

我还对add_imageget_random_image进行了一些修改,主要是为了让你可以知道图片应该在函数内部的位置,因为pyglet.sprite.Sprite还有两个其他参数, xy。因此,在创建精灵后更改xy是没有意义的,除非您想在之后移动它们(例如,在pyglet.clock.schedule_interval调用中)。