One of my favorite features within Twisted — but also one of the least known — is LoopingCall.withCount
, which can be used in applications where you have some real-time thing happening, which needs to keep happening at a smooth rate regardless of any concurrent activity or pauses in the main loop. Originally designed for playing audio samples from a softphone without introducing a desync delay over time, it can also be used to play animations while keeping track of their appropriate frame.
LoopingCall
is all around a fun tool to build little game features with. I’ve built a quick little demo to showcase some discoveries I’ve made over a few years of small hobby projects (none of which are ready for an open-source release) over here: DrawSnek.
This little demo responds to 3 key-presses:
q
quits. Always a useful thing for full-screen apps which don’t always play nice with C-c :).s
spawns an additional snek. Have fun, make many sneks.h
introduces a random “hiccup” of up to 1 full second so you can see what happens visually when the loop is overburdened or stuck.
Unfortunately a fully-functioning demo is a bit lengthy to go over line by line in a blog post, so I’ll just focus on a couple of important features for stutter- and tearing-resistant animation & drawing with PyGame & Twisted.
For starters, you’ll want to use a very recent prerelease of PyGame 2, which recently added support for vertical sync even without OpenGL mode; then, pass the vsync=1
argument to set_mode
:
1 2 3 4 5 |
|
To allow for as much wall-clock time as possible to handle non-drawing work, such as AI and input handling, I also use this trick:
1 2 3 4 5 6 7 |
|
By deferring pygame.display.flip
to a thread1, the main loop can continue processing AI timers, animation, network input, and user input while blocking and waiting for the vertical blank. Since the time-to-vblank can easily be up to 1/120th of a second, this is a significant amount of time! We know that the draw
won’t overlap with flip
, because LoopingCall
respects Deferred
s returned from its callable and won’t re-invoke you until the Deferred
fires.
Drawing doesn’t use withCount
, because it just needs to repeat about once every refresh interval (on most displays, about 1/60th of a second); the vblank timing is what makes sure it lines up.
However, animation looks like this:
1 2 3 |
|
We move the index forward by however many frames it’s been, then be sure it wraps around by modding it by the number of frames.
Similarly, the core2 of movement looks like this:
1 2 3 |
|
Rather than moving based on the number of times we’ve been called, which can result in slowed-down movement when the framerate isn’t keeping up, we jump forward by however many frames we should have been called at this point in time.
One of these days, maybe I’ll make an actual game, but in the meanwhile I hope you all enjoy playing with these fun little basic techniques for using Twisted in your game engine.
from Planet Python
via read more
No comments:
Post a Comment