在一个 Pyglet 中包含多个精灵 Window

Having multiple sprites in one Pyglet Window

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

我的代码如下,它只显示一张图片 - 我觉得它卡在了绘图部分的某个地方。

import pyglet
import time
import random

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)

    pyglet.app.run()

如果您能为此提供任何帮助或建议,我们将不胜感激。

一些 issues/suggestions 与您的代码。 首先,以下代码是多余的:

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

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

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

计算机的资源成本很高,包括设置和 memory/hard 驱动器等。因此建议尽可能早地创建您的列表,并在任何循环之外。例如:

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 中。至少与所有其他逻辑保持分离,因此它不在 "adding random image" 之间和 "loop".

内部

现在,您的代码失败的主要原因是您认为这会循环,但事实并非如此。同样,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 = pyglet.sprite.Sprite(rawImage) 将旧的 sprite 对象替换为新的对象。所以你想要做的是,在所有可见图像的循环之外保留一个 list/dictionary,然后添加到它并且只渲染添加的图像。

很像这样:

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()

现在,只有当您在 window 中按下一个键或按下鼠标时,这才有效。这是因为那是事件将被分派的唯一时间。 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 中的事件,它会为您处理并执行您安排的事情。

接下来,是我的一个小优化。这是一个奖励,并且会加快速度。它称为 批量渲染 ,当您渲染大量图像和 sprite 时,您目前一次将一张图像发送到显卡。这是非常 CPU 密集的。您要做的是将工作放在 GPU 上。因为毕竟,您是在处理图形,对吗?

因此,在这种情况下,批量渲染非常简单。每次调用 pyglet.sprite.Sprite 时,它都有一个名为 batch=None 的参数(默认)。如果您向 sprite 对象添加一个批次,则可以通过调用 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 调用中)。