void AnimationBase::updateStateMachine(AnimStateInput input, double param) { if (!m_compAnim) return; // If we get AnimationStateInputRestartAnimation then we force a new animation, regardless of state. if (input == AnimationStateInputMakeNew) { if (m_animState == AnimationStateStartWaitStyleAvailable) m_compAnim->animationController()->removeFromAnimationsWaitingForStyle(this); LOG(Animations, "%p AnimationState %s -> New", this, nameForState(m_animState)); m_animState = AnimationStateNew; m_startTime = 0; m_pauseTime = -1; m_requestedStartTime = 0; m_nextIterationDuration = -1; endAnimation(); return; } if (input == AnimationStateInputRestartAnimation) { if (m_animState == AnimationStateStartWaitStyleAvailable) m_compAnim->animationController()->removeFromAnimationsWaitingForStyle(this); LOG(Animations, "%p AnimationState %s -> New", this, nameForState(m_animState)); m_animState = AnimationStateNew; m_startTime = 0; m_pauseTime = -1; m_requestedStartTime = 0; m_nextIterationDuration = -1; endAnimation(); if (!paused()) updateStateMachine(AnimationStateInputStartAnimation, -1); return; } if (input == AnimationStateInputEndAnimation) { if (m_animState == AnimationStateStartWaitStyleAvailable) m_compAnim->animationController()->removeFromAnimationsWaitingForStyle(this); LOG(Animations, "%p AnimationState %s -> Done", this, nameForState(m_animState)); m_animState = AnimationStateDone; endAnimation(); return; } if (input == AnimationStateInputPauseOverride) { if (m_animState == AnimationStateStartWaitResponse) { // If we are in AnimationStateStartWaitResponse, the animation will get canceled before // we get a response, so move to the next state. endAnimation(); updateStateMachine(AnimationStateInputStartTimeSet, beginAnimationUpdateTime()); } return; } if (input == AnimationStateInputResumeOverride) { if (m_animState == AnimationStateLooping || m_animState == AnimationStateEnding) { // Start the animation startAnimation(beginAnimationUpdateTime() - m_startTime); } return; } // Execute state machine switch (m_animState) { case AnimationStateNew: ASSERT(input == AnimationStateInputStartAnimation || input == AnimationStateInputPlayStateRunning || input == AnimationStateInputPlayStatePaused); if (input == AnimationStateInputStartAnimation || input == AnimationStateInputPlayStateRunning) { m_requestedStartTime = beginAnimationUpdateTime(); LOG(Animations, "%p AnimationState %s -> StartWaitTimer", this, nameForState(m_animState)); m_animState = AnimationStateStartWaitTimer; } else { // We are pausing before we even started. LOG(Animations, "%p AnimationState %s -> AnimationStatePausedNew", this, nameForState(m_animState)); m_animState = AnimationStatePausedNew; } break; case AnimationStateStartWaitTimer: ASSERT(input == AnimationStateInputStartTimerFired || input == AnimationStateInputPlayStatePaused); if (input == AnimationStateInputStartTimerFired) { ASSERT(param >= 0); // Start timer has fired, tell the animation to start and wait for it to respond with start time LOG(Animations, "%p AnimationState %s -> StartWaitStyleAvailable", this, nameForState(m_animState)); m_animState = AnimationStateStartWaitStyleAvailable; m_compAnim->animationController()->addToAnimationsWaitingForStyle(this); // Trigger a render so we can start the animation if (m_object && m_object->element()) m_compAnim->animationController()->addElementChangeToDispatch(*m_object->element()); } else { ASSERT(!paused()); // We're waiting for the start timer to fire and we got a pause. Cancel the timer, pause and wait m_pauseTime = beginAnimationUpdateTime(); LOG(Animations, "%p AnimationState %s -> PausedWaitTimer", this, nameForState(m_animState)); m_animState = AnimationStatePausedWaitTimer; } break; case AnimationStateStartWaitStyleAvailable: ASSERT(input == AnimationStateInputStyleAvailable || input == AnimationStateInputPlayStatePaused); if (input == AnimationStateInputStyleAvailable) { // Start timer has fired, tell the animation to start and wait for it to respond with start time LOG(Animations, "%p AnimationState %s -> StartWaitResponse", this, nameForState(m_animState)); m_animState = AnimationStateStartWaitResponse; overrideAnimations(); // Start the animation if (overridden()) { // We won't try to start accelerated animations if we are overridden and // just move on to the next state. LOG(Animations, "%p AnimationState %s -> StartWaitResponse", this, nameForState(m_animState)); m_animState = AnimationStateStartWaitResponse; m_isAccelerated = false; updateStateMachine(AnimationStateInputStartTimeSet, beginAnimationUpdateTime()); } else { double timeOffset = 0; // If the value for 'animation-delay' is negative then the animation appears to have started in the past. if (m_animation->delay() < 0) timeOffset = -m_animation->delay(); bool started = startAnimation(timeOffset); m_compAnim->animationController()->addToAnimationsWaitingForStartTimeResponse(this, started); m_isAccelerated = started; } } else { // We're waiting for the style to be available and we got a pause. Pause and wait m_pauseTime = beginAnimationUpdateTime(); LOG(Animations, "%p AnimationState %s -> PausedWaitStyleAvailable", this, nameForState(m_animState)); m_animState = AnimationStatePausedWaitStyleAvailable; } break; case AnimationStateStartWaitResponse: ASSERT(input == AnimationStateInputStartTimeSet || input == AnimationStateInputPlayStatePaused); if (input == AnimationStateInputStartTimeSet) { ASSERT(param >= 0); // We have a start time, set it, unless the startTime is already set if (m_startTime <= 0) { m_startTime = param; // If the value for 'animation-delay' is negative then the animation appears to have started in the past. if (m_animation->delay() < 0) m_startTime += m_animation->delay(); } // Now that we know the start time, fire the start event. onAnimationStart(0); // The elapsedTime is 0. // Decide whether to go into looping or ending state goIntoEndingOrLoopingState(); // Dispatch updateStyleIfNeeded so we can start the animation if (m_object && m_object->element()) m_compAnim->animationController()->addElementChangeToDispatch(*m_object->element()); } else { // We are pausing while waiting for a start response. Cancel the animation and wait. When // we unpause, we will act as though the start timer just fired m_pauseTime = beginAnimationUpdateTime(); pauseAnimation(beginAnimationUpdateTime() - m_startTime); LOG(Animations, "%p AnimationState %s -> PausedWaitResponse", this, nameForState(m_animState)); m_animState = AnimationStatePausedWaitResponse; } break; case AnimationStateLooping: ASSERT(input == AnimationStateInputLoopTimerFired || input == AnimationStateInputPlayStatePaused); if (input == AnimationStateInputLoopTimerFired) { ASSERT(param >= 0); // Loop timer fired, loop again or end. onAnimationIteration(param); // Decide whether to go into looping or ending state goIntoEndingOrLoopingState(); } else { // We are pausing while running. Cancel the animation and wait m_pauseTime = beginAnimationUpdateTime(); pauseAnimation(beginAnimationUpdateTime() - m_startTime); LOG(Animations, "%p AnimationState %s -> PausedRun", this, nameForState(m_animState)); m_animState = AnimationStatePausedRun; } break; case AnimationStateEnding: #if !LOG_DISABLED if (input != AnimationStateInputEndTimerFired && input != AnimationStateInputPlayStatePaused) LOG_ERROR("State is AnimationStateEnding, but input is not AnimationStateInputEndTimerFired or AnimationStateInputPlayStatePaused. It is %d.", input); #endif if (input == AnimationStateInputEndTimerFired) { ASSERT(param >= 0); // End timer fired, finish up onAnimationEnd(param); LOG(Animations, "%p AnimationState %s -> Done", this, nameForState(m_animState)); m_animState = AnimationStateDone; if (m_object) { if (m_animation->fillsForwards()) { LOG(Animations, "%p AnimationState %s -> FillingForwards", this, nameForState(m_animState)); m_animState = AnimationStateFillingForwards; } else resumeOverriddenAnimations(); // Fire off another style change so we can set the final value if (m_object->element()) m_compAnim->animationController()->addElementChangeToDispatch(*m_object->element()); } } else { // We are pausing while running. Cancel the animation and wait m_pauseTime = beginAnimationUpdateTime(); pauseAnimation(beginAnimationUpdateTime() - m_startTime); LOG(Animations, "%p AnimationState %s -> PausedRun", this, nameForState(m_animState)); m_animState = AnimationStatePausedRun; } // |this| may be deleted here break; case AnimationStatePausedWaitTimer: ASSERT(input == AnimationStateInputPlayStateRunning); ASSERT(paused()); // Update the times m_startTime += beginAnimationUpdateTime() - m_pauseTime; m_pauseTime = -1; // we were waiting for the start timer to fire, go back and wait again LOG(Animations, "%p AnimationState %s -> New", this, nameForState(m_animState)); m_animState = AnimationStateNew; updateStateMachine(AnimationStateInputStartAnimation, 0); break; case AnimationStatePausedNew: case AnimationStatePausedWaitResponse: case AnimationStatePausedWaitStyleAvailable: case AnimationStatePausedRun: // We treat these two cases the same. The only difference is that, when we are in // AnimationStatePausedWaitResponse, we don't yet have a valid startTime, so we send 0 to startAnimation. // When the AnimationStateInputStartTimeSet comes in and we were in AnimationStatePausedRun, we will notice // that we have already set the startTime and will ignore it. ASSERT(input == AnimationStateInputPlayStateRunning || input == AnimationStateInputStartTimeSet || input == AnimationStateInputStyleAvailable || input == AnimationStateInputStartAnimation); ASSERT(paused()); if (input == AnimationStateInputPlayStateRunning) { if (m_animState == AnimationStatePausedNew) { // We were paused before we even started, and now we're supposed // to start, so jump back to the New state and reset. LOG(Animations, "%p AnimationState %s -> AnimationStateNew", this, nameForState(m_animState)); m_animState = AnimationStateNew; updateStateMachine(input, param); break; } // Update the times if (m_animState == AnimationStatePausedRun) m_startTime += beginAnimationUpdateTime() - m_pauseTime; else m_startTime = 0; m_pauseTime = -1; if (m_animState == AnimationStatePausedWaitStyleAvailable) { LOG(Animations, "%p AnimationState %s -> StartWaitStyleAvailable", this, nameForState(m_animState)); m_animState = AnimationStateStartWaitStyleAvailable; } else { // We were either running or waiting for a begin time response from the animation. // Either way we need to restart the animation (possibly with an offset if we // had already been running) and wait for it to start. LOG(Animations, "%p AnimationState %s -> StartWaitResponse", this, nameForState(m_animState)); m_animState = AnimationStateStartWaitResponse; // Start the animation if (overridden()) { // We won't try to start accelerated animations if we are overridden and // just move on to the next state. updateStateMachine(AnimationStateInputStartTimeSet, beginAnimationUpdateTime()); m_isAccelerated = true; } else { bool started = startAnimation(beginAnimationUpdateTime() - m_startTime); m_compAnim->animationController()->addToAnimationsWaitingForStartTimeResponse(this, started); m_isAccelerated = started; } } break; } if (input == AnimationStateInputStartTimeSet) { ASSERT(m_animState == AnimationStatePausedWaitResponse); // We are paused but we got the callback that notifies us that an accelerated animation started. // We ignore the start time and just move into the paused-run state. LOG(Animations, "%p AnimationState %s -> PausedRun", this, nameForState(m_animState)); m_animState = AnimationStatePausedRun; ASSERT(m_startTime == 0); m_startTime = param; m_pauseTime += m_startTime; break; } ASSERT(m_animState == AnimationStatePausedWaitStyleAvailable); // We are paused but we got the callback that notifies us that style has been updated. // We move to the AnimationStatePausedWaitResponse state LOG(Animations, "%p AnimationState %s -> PausedWaitResponse", this, nameForState(m_animState)); m_animState = AnimationStatePausedWaitResponse; overrideAnimations(); break; case AnimationStateFillingForwards: case AnimationStateDone: // We're done. Stay in this state until we are deleted break; } }
void GAFObject::realizeFrame(cocos2d::Node* out, uint32_t frameIndex) { const AnimationFrames_t& animationFrames = m_timeline->getAnimationFrames(); if (animationFrames.size() <= frameIndex) { return; } GAFAnimationFrame *currentFrame = animationFrames[frameIndex]; const GAFAnimationFrame::SubobjectStates_t& states = currentFrame->getObjectStates(); for (const GAFSubobjectState* state : states) { GAFObject* subObject = m_displayList[state->objectIdRef]; CCASSERT(subObject, "Error. SubObject with current ID not found"); if (!subObject) continue; if (state->colorMults()[GAFColorTransformIndex::GAFCTI_A] >= 0.f && subObject->m_isInResetState) { subObject->m_currentFrame = subObject->m_currentSequenceStart; } subObject->m_isInResetState = state->colorMults()[GAFColorTransformIndex::GAFCTI_A] < 0.f; if (!state->isVisible()) continue; if (subObject->m_charType == GAFCharacterType::Timeline) { if (!subObject->m_isInResetState) { cocos2d::AffineTransform stateTransform = state->affineTransform; float csf = m_timeline->usedAtlasScale(); stateTransform.tx *= csf; stateTransform.ty *= csf; cocos2d::AffineTransform t = GAF_CGAffineTransformCocosFormatFromFlashFormat(state->affineTransform); subObject->setAdditionalTransform(t); subObject->m_parentFilters.clear(); const Filters_t& filters = state->getFilters(); subObject->m_parentFilters.insert(subObject->m_parentFilters.end(), filters.begin(), filters.end()); const float* cm = state->colorMults(); subObject->m_parentColorTransforms[0] = cocos2d::Vec4( m_parentColorTransforms[0].x * cm[0], m_parentColorTransforms[0].y * cm[1], m_parentColorTransforms[0].z * cm[2], m_parentColorTransforms[0].w * cm[3]); subObject->m_parentColorTransforms[1] = cocos2d::Vec4(state->colorOffsets()) + m_parentColorTransforms[1]; if (m_masks[state->objectIdRef]) { rearrangeSubobject(out, m_masks[state->objectIdRef], state->zIndex); } else { //subObject->removeFromParentAndCleanup(false); if (state->maskObjectIdRef == IDNONE) { rearrangeSubobject(out, subObject, state->zIndex); } else { // If the state has a mask, then attach it // to the clipping node. Clipping node will be attached on its state auto mask = m_masks[state->maskObjectIdRef]; CCASSERT(mask, "Error. No mask found for this ID"); if (mask) rearrangeSubobject(mask, subObject, state->zIndex); } } subObject->step(); } } else if (subObject->m_charType == GAFCharacterType::Texture) { cocos2d::Vect prevAP = subObject->getAnchorPoint(); cocos2d::Size prevCS = subObject->getContentSize(); #if ENABLE_RUNTIME_FILTERS if (subObject->m_objectType == GAFObjectType::MovieClip) { // Validate sprite type (w/ or w/o filter) const Filters_t& filters = state->getFilters(); GAFFilterData* filter = NULL; GAFMovieClip* mc = static_cast<GAFMovieClip*>(subObject); if (m_parentFilters.size() > 0) { filter = *m_parentFilters.begin(); } else if (filters.size() > 0) { filter = *filters.begin(); } if (filter) { filter->apply(mc); } if (!filter || filter->getType() != GAFFilterType::GFT_Blur) { mc->setBlurFilterData(nullptr); } if (!filter || filter->getType() != GAFFilterType::GFT_ColorMatrix) { mc->setColorMarixFilterData(nullptr); } if (!filter || filter->getType() != GAFFilterType::GFT_Glow) { mc->setGlowFilterData(nullptr); } if (!filter || filter->getType() != GAFFilterType::GFT_DropShadow) { GAFDropShadowFilterData::reset(mc); } } #endif cocos2d::Size newCS = subObject->getContentSize(); cocos2d::Vect newAP = cocos2d::Vect(((prevAP.x - 0.5f) * prevCS.width) / newCS.width + 0.5f, ((prevAP.y - 0.5f) * prevCS.height) / newCS.height + 0.5f); subObject->setAnchorPoint(newAP); if (m_masks[state->objectIdRef]) { rearrangeSubobject(out, m_masks[state->objectIdRef], state->zIndex); } else { //subObject->removeFromParentAndCleanup(false); if (state->maskObjectIdRef == IDNONE) { rearrangeSubobject(out, subObject, state->zIndex); } else { // If the state has a mask, then attach it // to the clipping node. Clipping node will be attached on its state auto mask = m_masks[state->maskObjectIdRef]; CCASSERT(mask, "Error. No mask found for this ID"); if (mask) rearrangeSubobject(mask, subObject, state->zIndex); } } cocos2d::AffineTransform stateTransform = state->affineTransform; float csf = m_timeline->usedAtlasScale(); stateTransform.tx *= csf; stateTransform.ty *= csf; cocos2d::AffineTransform t = GAF_CGAffineTransformCocosFormatFromFlashFormat(state->affineTransform); if (isFlippedX() || isFlippedY()) { float flipMulX = isFlippedX() ? -1 : 1; float flipOffsetX = isFlippedX() ? getContentSize().width - m_asset->getHeader().frameSize.getMinX() : 0; float flipMulY = isFlippedY() ? -1 : 1; float flipOffsetY = isFlippedY() ? -getContentSize().height + m_asset->getHeader().frameSize.getMinY() : 0; cocos2d::AffineTransform flipCenterTransform = cocos2d::AffineTransformMake(flipMulX, 0, 0, flipMulY, flipOffsetX, flipOffsetY); t = AffineTransformConcat(t, flipCenterTransform); } subObject->setExternalTransform(t); if (subObject->m_objectType == GAFObjectType::MovieClip) { GAFMovieClip* mc = static_cast<GAFMovieClip*>(subObject); float colorMults[4] = { state->colorMults()[0] * m_parentColorTransforms[0].x, state->colorMults()[1] * m_parentColorTransforms[0].y, state->colorMults()[2] * m_parentColorTransforms[0].z, state->colorMults()[3] * m_parentColorTransforms[0].w }; float colorOffsets[4] = { state->colorOffsets()[0] + m_parentColorTransforms[1].x, state->colorOffsets()[1] + m_parentColorTransforms[1].y, state->colorOffsets()[2] + m_parentColorTransforms[1].z, state->colorOffsets()[3] + m_parentColorTransforms[1].w }; mc->setColorTransform(colorMults, colorOffsets); } } else if (subObject->m_charType == GAFCharacterType::TextField) { GAFTextField *tf = static_cast<GAFTextField*>(subObject); rearrangeSubobject(out, subObject, state->zIndex); cocos2d::AffineTransform stateTransform = state->affineTransform; float csf = m_timeline->usedAtlasScale(); stateTransform.tx *= csf; stateTransform.ty *= csf; cocos2d::AffineTransform t = GAF_CGAffineTransformCocosFormatFromFlashFormat(state->affineTransform); if (isFlippedX() || isFlippedY()) { float flipMulX = isFlippedX() ? -1 : 1; float flipOffsetX = isFlippedX() ? getContentSize().width - m_asset->getHeader().frameSize.getMinX() : 0; float flipMulY = isFlippedY() ? -1 : 1; float flipOffsetY = isFlippedY() ? -getContentSize().height + m_asset->getHeader().frameSize.getMinY() : 0; cocos2d::AffineTransform flipCenterTransform = cocos2d::AffineTransformMake(flipMulX, 0, 0, flipMulY, flipOffsetX, flipOffsetY); t = AffineTransformConcat(t, flipCenterTransform); } subObject->setExternalTransform(t); } if (state->isVisible()) { subObject->m_lastVisibleInFrame = frameIndex + 1; } } GAFAnimationFrame::TimelineActions_t timelineActions = currentFrame->getTimelineActions(); for (GAFTimelineAction action : timelineActions) { switch (action.getType()) { case GAFActionType::Stop: pauseAnimation(); break; case GAFActionType::Play: resumeAnimation(); break; case GAFActionType::GotoAndStop: gotoAndStop(action.getParam(GAFTimelineAction::PI_FRAME)); break; case GAFActionType::GotoAndPlay: gotoAndPlay(action.getParam(GAFTimelineAction::PI_FRAME)); break; case GAFActionType::DispatchEvent: _eventDispatcher->dispatchCustomEvent(action.getParam(GAFTimelineAction::PI_EVENT_TYPE), &action); break; case GAFActionType::None: default: break; } } }