Fixed timestep with pyglet event loop

526 views
Skip to first unread message

Baitshop

unread,
Oct 26, 2016, 3:19:47 PM10/26/16
to pyglet-users
I was wondering if a fixed timestep is possible with pyglet's current event loop? When using schedule_interval for 1/60.0 it's not always consistent since it is using a variable time step.

How is one supposed to separate the rendering from the updates considering adjusting the position of the sprite will move it next time it is drawn.  I am also having trouble understanding how interpolation would be configured using the pyglet app system as the on_draw does not get passed any dt. I have tried an older implementation that I found, but it seems like it fires the scheduled function faster than the dt actually is. Would anyone happen to have a proper example of how a fixed time step would work using a sprite and pyglet?

Benjamin Moran

unread,
Nov 2, 2016, 1:15:14 AM11/2/16
to pyglet-users
This is not really answering the question, but I sometimes set vsync on, and set the update to 1/61 instead of 1/60. This works smoothly for the most part, since it's always capped by the vsync.

Josh

unread,
Nov 2, 2016, 3:52:15 PM11/2/16
to pyglet-users
Smooth motion in pyglet should always take place inside an event loop specified by an interval scheduled function. On_draw will never fire in the middle of that loop unless you have code in the loop that tells it to. Since your loop function must accept a dt parameter, you can ensure your motion is correctly interpolated. For example:

import pyglet
window
= pyglet.window.Window(800, 800)
mypic
= pyglet.resource.image('mypic.jpg')
sprite
= pyglet.sprite.Sprite(mypic, x=0, y=0, subpixel=True)

def move_sprite(dt):
    sprite
.x += 50*dt

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

pyglet
.clock.schedule_interval(move_sprite, 1.0/60)

pyglet
.app.run()



In this code, on_draw() will be called every time pyglet's clock has completed move_sprite. If the interval isn't exactly one sixtieth of a second it doesn't matter, the clock will pass the dt of when it actually firs, not when it was scehduled.

Baitshop

unread,
Nov 2, 2016, 4:26:04 PM11/2/16
to pyglet-users
Thanks for the response guys, I have it capped at vsync, but it's not reliable enough, especially on slower machines or if there are slowdowns due to many objects.

My main concern is making sure movement is deterministic, so things like replays and speed checking are able to be calculated properly and reliably. So for instance even if the simulation ran at 10 FPS, even if it's choppy or a 'slideshow', things are triggering and happening at the correct points in time. Maybe movement would be based on time rather than delta time. If it's variable, this won't be possible if the game runs too fast or too slow or drops frames. I hope this makes sense on what I'm trying to accomplish.

Greg Ewing

unread,
Nov 2, 2016, 6:16:35 PM11/2/16
to pyglet...@googlegroups.com
Baitshop wrote:
> My main concern is making sure movement is deterministic, so things like
> replays and speed checking are able to be calculated properly and
> reliably.

The way this is usually handled is to run the physics with
a *fixed* time step, which is independent of the display
frame rate. In that case, the physics update routine doesn't
need or want a dt parameter -- you always advance the game
time by the same amount, even if it's not exactly the
amount of real time that has passed.

Then if you want the frame rate to be higher than the
physics rate, the display routine is the one that takes
a dt parameter, and interpolates between two physics
steps accordingly.

--
Greg

Baitshop

unread,
Nov 2, 2016, 7:00:05 PM11/2/16
to pyglet-users
Well you've hit on the issue I'm trying to resolve. How can I schedule movement at a fixed time step if the scheduling functions are all variable time step no matter what? Let's say I want to move a sprite, if I create an update function for it Example:
def move_sprite(dt):
    sprite
.x += 50*(1/60.0)

pyglet
.clock.schedule_interval(move_sprite, 1.0/60)

 Even if I progress movement at a fixed paced, the schedule is not scheduling it at correct times. I was also under the impression the on_draw is the display routine, which doesn't take a dt parameter. Not to mention how are you going to separate movement if the sprite position is directly related to its render position and interpolate? I am having trouble understanding how this would even look with pyglet.

magu...@gmail.com

unread,
Nov 2, 2016, 10:54:59 PM11/2/16
to pyglet-users
Digging in a few places[1][2][3], one potential solution could be to use your own event loop. Here's an example borrowing from dewitters that with a little tweaking might help:

import pyglet
from pyglet.window import key


class example(pyglet.window.Window):
   
def __init__(self):
       
super(example, self).__init__(640, 480, resizable=False, fullscreen=False, caption="Test")
       
self.clear()

       
self.box = pyglet.image.load('sprite.png').get_texture()
       
self.x = 0
       
self.y = 0

       
self.max_fps = 5
       
self.fps = 25
       
self.skip_ticks = 1000 / self.fps

       
self.interpolation = 0.0

       
self.GetTickCount = pyglet.clock.tick()
       
self.next_game_tick = self.GetTickCount
       
self.fps_display = pyglet.clock.ClockDisplay()


   
def update(self):
       
while not self.has_exit:
            dt
= pyglet.clock.tick()
           
self.GetTickCount += dt

            loops
= 0
           
while (self.GetTickCount > self.next_game_tick and loops < self.max_fps):
               
#update stuff
               
self.next_game_tick += self.skip_ticks
                loops
+= 1

           
self.interpolation = float(self.GetTickCount + self.skip_ticks - self.next_game_tick) / float(self.skip_ticks)

           
self.draw()

           
self.dispatch_events()

 
   
def draw(self):
       
self.clear()
       
self.box.blit(self.x,self.y)
       
self.fps_display.draw()

       
self.flip()
     
   
def on_key_press(self,symbol,modifiers):
       
if symbol == key.ESCAPE:
           
self.close()

if __name__ == '__main__':
    window
= example()
    window
.update()


Reply all
Reply to author
Forward
0 new messages