cdlane has an awesome idea here of using ontimer
and a set of currently-pressed keys, but I thought I'd try to extend and refine it a bit.
The problem with a secondary loop with ontimer
is that it seems to fight the main turtle loop, both in terms of computation and also in terms of thread safety/interleaving, where you can begin iterating the key pressed set and find that a handler has pulled a key out during iteration, raising an error.
The (seemingly poorly-named) tracer(0)
function lets you disable turtle's loop so you can call it manually from within the hand-rolled ontimer
loop using update()
. This reduces some of the choppiness of the competing loops, although I imagine the timer resolution on rolling your own loop with repeated calls to ontimer
is less precise than the built-in loop. But I haven't really looked at the source yet -- feel free to leave a comment if you have any insight.
Here's the proof of concept:
import turtle
def tick():
for action in keys_pressed:
actions[action]()
turtle.update()
win.ontimer(tick, frame_delay_ms)
t = turtle.Turtle()
turtle.tracer(0)
frame_delay_ms = 1000 // 30 # default for turtle is 10 in _CFG["delay"]
step_speed = 10
actions = dict(
l=lambda: t.left(step_speed),
r=lambda: t.right(step_speed),
u=lambda: t.forward(step_speed),
)
win = turtle.Screen()
keys_pressed = set()
win.onkeypress(lambda: keys_pressed.add("u"), "Up")
win.onkeypress(lambda: keys_pressed.add("l"), "Left")
win.onkeypress(lambda: keys_pressed.add("r"), "Right")
win.onkeyrelease(lambda: keys_pressed.remove("u"), "Up")
win.onkeyrelease(lambda: keys_pressed.remove("l"), "Left")
win.onkeyrelease(lambda: keys_pressed.remove("r"), "Right")
win.listen()
tick()
win.exitonclick()
Ultimately, though, if you want to go much further into realtime graphics and games, Pygame is better equipped.
cdlane has a few good posts on tracer
: 1, 2, 3.