I have the below code which currently outputs an image on a blank Pyglet window, however there is only one image outputted. I really need there to be a new image added every two second, with the previous images remaining intact also. For example, one image is added, two seconds later another image is added and two seconds after this another image is added. I have added the random library so an image at random can be added.
The code I have for this is below, it only displays the one image - I feel that this is getting stuck somewhere around the draw section.
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()
Any help or advice you can offer with this would be really appreciated.
A few issues/suggestions with your code. First of all, the following code is redundant:
while True:
images = ["Image 1.png", "Image 2.png", "Image 3.png"]
....
pyglet.app.run()
Because pyglet.app.run()
is a blocking call, meaning, the loop will never loop - because pyglet.app.run()
is in itself, a loop (more on this later).
Except if your application crashes, but you don't handle those exceptions so not even in that case will the code be re-run/looped.
Secondly, you should never define arrays/lists or anything really inside a loop. Loops are usually meant for logical operations, not creating things. Most contently some times it's useful to create things inside a loop, but those times they are more often than not accompanied by a if
statement.
Resources cost a lot for the computer, both to setup and for the memory/hard drives etc. So trying to create your lists as early as possible and outside of any loops are suggested. for instance:
window = pyglet.window.Window()
images = ["Image 1.png", "Image 2.png", "Image 3.png"]
while True:
choice = images[random.randint(0,2)]
Would have been a better option, if - again - the loop actually looped. In this case it's just a matter of tidying things up.
Also, this block of code:
@window.event
def on_draw():
window.clear()
sprite.draw()
Should not be created in a loop either, it's meant to replace your window
variables on_draw
function. So that should be moved out and put as early as possible in your logic IMO. At least kept separated by all other logic so it's not in between a "adding random image" and inside a "loop".
Now, the main reason your code fails, is that you thought this would loop, it doesn't. Again, pyglet.app.run()
will lock your code execution on that row, because it's, well a never ending loop inside that function call.
You could expand your code and copy paste the code from pyglet.py
's source code and it would look something like this (just to give you an idea of what's happening):
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()
note how pyglet.app.run()
expands into another while True
loop, that never really breaks. It's a bit oversimplified, but that's essentially what happens.
So your sprite = pyglet.sprite.Sprite(rawImage)
will never be re-triggered.
Then, to your second biggest issue why this code would never work:
You're doing:
def on_draw():
sprite.draw()
But each loop you would have replaced the old sprite
object, with a new one by doing sprite = pyglet.sprite.Sprite(rawImage)
. So what you would want to do, is keep a list/dictionary outside of the loop with all your visible image, and add to it and only render the images added.
Much like this:
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()
Now, this only works when you press a key or press the mouse inside the window. This is because that's the only time a event will be dispatched. And Pyglet will only render things if there's a event that is being triggered. There's two ways you can get around this, the hard core OOP way which I'll skip for now.
The second is to use what's called a Pyglet Clock, where you schedule something to happen at a interval. I'm not really good at this part, since I tend to use my own scheduler etc.
But here's the gist of it:
def add_image():
images[len(images)] = get_random_image()
pyglet.clock.schedule_interval(add_image, 2) # Every two seconds
This is a lot cleaner than doing the if time.time() - last_add > 2
.
The result should look something like this:
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()
This way, you won't need to press any keys or the mouse to trigger a event in Pyglet, it will handle that for you and do what you scheduled it to do.
Next up, is a small optimization from my part. It's a bonus, and will speed things up. It's called batched rendering, when you're rendering a lot of images and sprites, you're currently sending one image at a time to the graphics card. This is very CPU intensive. What you want to do is put the labor on the GPU. Because after all, you're working with graphics, right?
So, batched rendering is pretty easy in this case. Every time you call pyglet.sprite.Sprite
, it has a parameter called batch=None
(default). if you add a batch to the sprite object, you can render the entire batch by calling batch.draw()
instead of each individual sprite.draw()
.
The solution would look something like this:
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()
I also made some alterations to add_image
and get_random_image
, mainly so that you can tell what position the image should be in inside the function, because pyglet.sprite.Sprite
also takes two other parameters, x
and y
. So it makes no sense to change x
and y
after you've created the sprite, unless you want to move them afterwards (for instance, in a pyglet.clock.schedule_interval
call).