Ren'Py Blog - Synchronize ATL Animations
If you have two sprites on screen and you want their transform animations to sync up (like say because they're both riding the same horse or engaging in other activities that require rhythmic physical contact), Ren'Py doesn't really have an obvious way to do this.
As a result, despite being extremely careful about making sure the timing for both transforms is identical, they may fall out of sync, like turn signals at a stoplight. A quirk of the engine, I'm told.
Well I ain't given to submittin' to quirkiness.
My solution involves exploiting transform functions.
One function sets a pulse on an interval and runs every frame. On the frame that the interval expires, it 'emits' a pulse, and then resets the interval.
Another function waits on a pulse. That function repeats infinitely and blocks the animation until a pulse is detected.
Here's a working example (and as always, apologies for the weird formatting. Tumblr is not code-friendly):
default sync_pulse = (0, 0, 0)
init python:
def make_pulse(last_pulse, pulse_frames=1):
current_time = time.time()
pulse_frames_remaining, interval_in_seconds, last_pulse_time = last_pulse
if pulse_frames_remaining > 0:
return (pulse_frames_remaining - 1, interval_in_seconds, last_pulse_time)
elif last_pulse_time + interval_in_seconds < current_time:
return (pulse_frames, interval_in_seconds, current_time)
else:
return last_pulse
def sync_pulser(*args, interval=1, variable_name="sync_pulse", pulse_frames=1, now=False, **kwargs):
"""
Generates a 'pulse' on a variable every interval
The pulse lasts pulse_frames ticks
If now is True, will generate a pulse now
"""
pulse_frames_remaining, original_interval, last_pulse_time = getattr(store, variable_name) pulse = make_pulse(
(pulse_frames+1, interval, 0) if now
else (pulse_frames_remaining, interval, last_pulse_time)
)
setattr(store, variable_name, pulse)
return 0
def is_pulsing(pulse):
return pulse[0] > 0
def wait_on_pulse(*args, pulse_variable_name="sync_pulse", **kwargs):
if is_pulsing(getattr(store, pulse_variable_name)):
return None
else:
return 0
show expression Text("pacing", color="#fff") at Transform(pos=(.75, .5)):
parallel:
function renpy.partial(sync_pulser, interval=3)
parallel:
function renpy.partial(wait_on_pulse)
zoom 1.5
pause 1
zoom 1.0
repeat
show expression Text("waiting", color="#fff") at Transform(pos=(.25, .5)):
function renpy.partial(wait_on_pulse)
zoom 1.5
pause 1
zoom 1.0
repeat
Note that only one sprite needs to set the pace. Also note that both need to wait for the pulse.
I hope that's helpful for someone out there.