In C, you need a public static callback function to receive the events. The problem with static callback functions is that they have no game object state. These callbacks are only receiving the spAnimationState.
The solution is that the spAnimationState being sent to the callback can have a 'rendererObject' which is really just a void* pointer to any game object that you desire. In my case, I have a 'SpineMovie' class that wraps my Spine Animations. I know that the 'state->rendererObject' is of this type because when I created the spAnimationState, I set this value.
pSkeleton = spSkeleton_create(pSpineData->skeletonData);
pAnimationState = spAnimationState_create(pSpineData->animationData);
pAnimationState->rendererObject = (void*)this; // Set this SpineMovie as event context
pAnimationState->listener = (spAnimationStateListener)&SpineMovie::spineAnimStateHandler;
My handling code looks something like:
void SexyAnimation::SpineMovie::spineAnimStateHandler( spAnimationState* state, int trackIndex, int type, spEvent* event,int loopCount )
{
if (state && state->rendererObject){
SpineMovie* pMovie = (SpineMovie*)state->rendererObject;
pMovie->OnSpineAnimationStateEvent(trackIndex, type, event, loopCount);
}
}
void SexyAnimation::SpineMovie::OnSpineAnimationStateEvent( int trackIndex, int type, spEvent* event, int loopCount )
{
switch (type)
{
case SP_ANIMATION_START:
if(mSpineMovieListener)
mSpineMovieListener->OnSpineMovieStarted(this, trackIndex);
if(trackIndex == 0)
mState = SPINESTATE_RUNNING;
break;
case SP_ANIMATION_END:
if(mSpineMovieListener)
mSpineMovieListener->OnSpineMovieStopped(this, trackIndex);
if(trackIndex == 0)
mState = SPINESTATE_FINISHED;
break;
case SP_ANIMATION_COMPLETE:
if(mSpineMovieListener)
mSpineMovieListener->OnSpineMovieLooped(this, trackIndex, loopCount);
if(trackIndex == 0)
mState = SPINESTATE_LOOPING;
break;
case SP_ANIMATION_EVENT:
{
SpineEventData data;
data.numInt = (event) ? event->intValue : 0;
data.numFloat = (event) ? event->floatValue : 0.0f;
data.str = (event) ? event->stringValue : NULL;
if(mSpineMovieListener)
mSpineMovieListener->OnSpineMovieEvent(this, trackIndex, (event->data) ? event->data->name : "unknown", data);
}
break;
default:
break;
}
}
For something like a sound trigger where it doesn't depend on a gameobject, you don't need the rendererObject. You could just grab the 'event->data->name' and assume it's a sound filename.
Or you could desire your events such that each event type is prepended with a label. E.g. 'sound:footstep' would tell your game to play a footstep sound. Just tell your animators the event types they can use {'sound', 'particles', 'action', etc...} and split the tokens on the colon.
case SP_ANIMATION_EVENT:{
string evClass = split(event->data->name, ':') .first(); // pseudo code
string evPayload = split(event->data->name, ':') .second(); // pseudo code
if( evClass == "sound"){
//play evPayload.wav
}
else if( evClass == 'particles'){
// where to spawn?? Check animation state for position? Ask state->rendererObject?
}
else if(evClass = 'action'){
if(evPayload == 'toggle'){
// Now, having the state->rendererObject is needed to 'toggle' it somehow
}
}
}