Thank you, I have been working hard on a system to make this work. I will share what I come up with in case anyone else finds it useful.
Here's some code I wrote for this, to help anyone who might be trying to do the same thing. Basically it's a function that plays an animation from starting time to end time, over duration. If the animation is already playing, it will simply set the parameters of the track to the proper values. If the animation is not playing yet, it will set it on the input track.
// animRange.x is where the animation should start (in seconds),
// and animRange.y is where the animation should end (in seconds)
// Pass in -1 for animRange.x to have the animation start wherever it is currently.
// Pass in -1 for animRange.y to have the animation end at its natural duration.
spTrackEntry*startAnimation(const Char* animName, int track, Point2 animRange,
double duration, bool looping)
{
spTrackEntry* trackEntry = spAnimationState_getCurrent(spineAnimState, track);
// start a new animation
if (trackEntry == NULL ||
strcmp(trackEntry->animation->name, animName) != 0)
{
trackEntry = spAnimationState_setAnimationByName(spineAnimState, track, animName,
looping ? 1 : 0);
// cout << "starting new track " << animName << ", track time " << trackEntry->trackTime << endl;
}
// this track is already playing this animation
else
{
trackEntry->loop = looping ? 1 : 0;
// cout << "continuing track " << animName << ", track time " << trackEntry->trackTime << endl;
}
double naturalDuration = getAnimationDuration(animName);
// Animation low range wasn't set.
// Use either the current playback time or the end of the previously set animation,
// whichever is lower (since trackTime keeps running after the animation is done)
if (animRange.x < -0.01)
{
animRange.x = std::min(trackEntry->trackTime, trackEntry->animationEnd);
}
// Animation high range wasn't set. Use the natural duration of the animation
if (animRange.y < -0.01)
{
animRange.y = naturalDuration;
}
// clamp the animation range from 0.0 - naturalDuration
animRange.x = std::max(animRange.x, 0.0);
animRange.y = std::min(animRange.y, naturalDuration);
// don't let the start be higher than the stop
if (animRange.y <= animRange.x)
{
animRange.y = animRange.x + 1.0 / 60.0;
}
// cout << "trackEntry->animationStart " << trackEntry->animationStart << endl;
// trackTime is the current position of the animation, but it keeps running even after the animation
// stops moving because it passed getAnimationDuration()
// animationLast is related to timeline keys and even triggering
// trackEnd is when the track will actually being rendered, which is normally infinity.
// trackTime keeps going even after the animation stops playing at animationEnd.
trackEntry->trackTime = animRange.x;
trackEntry->animationLast = animRange.x;
// cout << "was starting at " << trackEntry->trackTime << ", setting start to " << animRange.x << endl;
// enforce the end of the range so that the animation doesn't keep playing
trackEntry->animationEnd = animRange.y;
// cout << "old mix duration " << trackEntry->mixDuration << endl;
double trackTimeMult = (animRange.y - animRange.x) / duration;
trackEntry->timeScale = trackTimeMult;
trackEntry->mixDuration *= trackTimeMult;
// change the mix times for all tracks mixing into this one
spTrackEntry* currMixTrack = trackEntry->mixingFrom;
while (currMixTrack != NULL)
{
currMixTrack->timeScale = trackTimeMult;
currMixTrack = currMixTrack->mixingFrom;
}
// cout << "new mix duration " << trackEntry->mixDuration << endl;
// cout << "range " << animationRange << " desired duration " << targetDuration << " timeScale " << trackEntry->timeScale << endl;
return trackEntry;
}
This allows us to use it in a script to solve the example problem, by wrapping that call in a little object and feeding it into a script.
script.enqueue(new StartSpineAnimationCommand("swing_sword", 0, Point2(0.0, 0.333), 0.75));
script.wait(0.25);
script.enqueue(new StartSpineAnimationCommand("swing_sword", 0, Point2(0.333, 0.666), 0.1));
script.enqueue(new StartSpineAnimationCommand("swing_sword", 0, Point2(0.666, 1.0), 0.5));
As you can see, this method is quite clumsy. It's clear that Spine was not really designed to divide a single animation into parts like this and give each part a playback duration. I also believe that there may be some bugs in this code because the timing is sometimes off, could be due to mixing or something. But at least the basic idea is possible!