// Event handling - animation state changed. // We need to capture that because: // 1: state can be changed over time. // 2: synchronization of states private void AnimationStateChanged(MyStateMachineTransitionWithStart transitionWithStart) { var animationTransition = transitionWithStart.Transition as MyAnimationStateMachineTransition; var animationSourceNode = transitionWithStart.StartNode as MyAnimationStateMachineNode; var animationTargetNode = animationTransition.TargetNode as MyAnimationStateMachineNode; if (animationSourceNode != null && animationTransition != null) { // synchronization if (animationTargetNode != null) { switch (animationTransition.Sync) { case MyAnimationTransitionSyncType.Restart: if (animationTargetNode.RootAnimationNode != null) { animationTargetNode.RootAnimationNode.SetLocalTimeNormalized(0); } break; case MyAnimationTransitionSyncType.Synchronize: if (animationSourceNode.RootAnimationNode != null && animationTargetNode.RootAnimationNode != null) { float normalizedTimeInSource = animationSourceNode.RootAnimationNode.GetLocalTimeNormalized(); animationTargetNode.RootAnimationNode.SetLocalTimeNormalized(normalizedTimeInSource); } break; case MyAnimationTransitionSyncType.NoSynchonization: // do nothing break; default: Debug.Fail("Unknown synchronization option."); break; } } // transition over time if (animationTransition.TransitionTimeInSec > 0.0) { MyStateTransitionBlending stateTransitionBlending = new MyStateTransitionBlending(); // struct stateTransitionBlending.SourceState = animationSourceNode; stateTransitionBlending.TimeLeftInSeconds = animationTransition.TransitionTimeInSec; stateTransitionBlending.InvTotalTime = 1.0 / animationTransition.TransitionTimeInSec; m_stateTransitionBlending.Insert(0, stateTransitionBlending); } else { m_stateTransitionBlending.Clear(); // instantly play new animation, forget about all blending stuff! } CurrentUpdateData.Controller.Variables.SetValue(m_variableAnimationFinished, 0.0f); } }
// Update state machine. That means: // Change state if transition conditions are fulfilled. // Update skeleton. public void Update(ref MyAnimationUpdateData data) { if (data.CharacterBones == null) { return; // safety } CurrentUpdateData = data; // local copy CurrentUpdateData.VisitedTreeNodesCounter = 0; CurrentUpdateData.VisitedTreeNodesPath = m_lastVisitedTreeNodesPath; CurrentUpdateData.VisitedTreeNodesPath[0] = 0; if (BoneMask == null) // rebuild bone mask array if not done yet { RebuildBoneMask(); } data.LayerBoneMask = CurrentUpdateData.LayerBoneMask = BoneMask; // pass bone mask array to all subnodes and result Debug.Assert(data.LayerBoneMask != null); // setting animation finished flag MyAnimationStateMachineNode currentAnimationNode = CurrentNode as MyAnimationStateMachineNode; if (currentAnimationNode != null && currentAnimationNode.RootAnimationNode != null) { float finishedPercent = currentAnimationNode.RootAnimationNode.GetLocalTimeNormalized(); data.Controller.Variables.SetValue(MyAnimationVariableStorageHints.StrIdAnimationFinished, finishedPercent); } else { data.Controller.Variables.SetValue(MyAnimationVariableStorageHints.StrIdAnimationFinished, 0); } base.Update(); int[] swapVisitedTreeNodesPath = VisitedTreeNodesPath; VisitedTreeNodesPath = m_lastVisitedTreeNodesPath; m_lastVisitedTreeNodesPath = swapVisitedTreeNodesPath; CurrentUpdateData.VisitedTreeNodesPath = null; // disconnect our array // blended transitions // - from most recent to oldest // - please preserve order of blending for three or more states // when CurrentState changes float weightMultiplier = 1.0f; for (int i = 0; i < m_stateTransitionBlending.Count; i++) { MyStateTransitionBlending stateTransitionBlending = m_stateTransitionBlending[i]; float localWeight = (float)(stateTransitionBlending.TimeLeftInSeconds * stateTransitionBlending.InvTotalTime); // 1 to 0 over time weightMultiplier *= localWeight; // update nodes that we are just leaving var lastResult = CurrentUpdateData.BonesResult; CurrentUpdateData.BonesResult = null; stateTransitionBlending.SourceState.OnUpdate(this); if (lastResult != null && CurrentUpdateData.BonesResult != null) { for (int j = 0; j < lastResult.Count; j++) { if (data.LayerBoneMask[j]) { // these nodes lose their weight, it goes from 1 to 0 // we need to blend them to last result (current node or another node that we are leaving) float w = ComputeEaseInEaseOut(MathHelper.Clamp(weightMultiplier, 0, 1)); CurrentUpdateData.BonesResult[j].Rotation = Quaternion.Slerp(lastResult[j].Rotation, CurrentUpdateData.BonesResult[j].Rotation, w); CurrentUpdateData.BonesResult[j].Translation = Vector3.Lerp(lastResult[j].Translation, CurrentUpdateData.BonesResult[j].Translation, w); } } // give back last result (free list of bones), we dont need it anymore data.Controller.ResultBonesPool.Free(lastResult); } // update, decrease remaining time stateTransitionBlending.TimeLeftInSeconds -= data.DeltaTimeInSeconds; m_stateTransitionBlending[i] = stateTransitionBlending; if (stateTransitionBlending.TimeLeftInSeconds <= 0) { // skip older blended states and mark them for deletion, because their (global) weight is now zero for (int j = i + 1; j < m_stateTransitionBlending.Count; j++) { var temp = m_stateTransitionBlending[j]; temp.TimeLeftInSeconds = 0; // m_stateTransitionBlending[j] = temp; } break; } } m_stateTransitionBlending.RemoveAll(s => s.TimeLeftInSeconds <= 0.0); data.BonesResult = CurrentUpdateData.BonesResult; // local copy contains resulting list of bones }
// Event handling - animation state changed. // We need to capture that because: // 1: state can be changed over time. // 2: synchronization of states private void AnimationStateChanged(MyStateMachineTransitionWithStart transitionWithStart) { var animationTransition = transitionWithStart.Transition as MyAnimationStateMachineTransition; if (animationTransition == null) { return; } var animationSourceNode = transitionWithStart.StartNode as MyAnimationStateMachineNode; var animationTargetNode = animationTransition.TargetNode as MyAnimationStateMachineNode; if (animationSourceNode != null) { // synchronization if (animationTargetNode != null) { bool targetAnimationStillPlaying = false; foreach (var blending in m_stateTransitionBlending) { if (blending.SourceState == animationTargetNode) { targetAnimationStillPlaying = true; break; } } // synchronization options... use them only if target animation is not playing already if (!targetAnimationStillPlaying) { switch (animationTransition.Sync) { case MyAnimationTransitionSyncType.Restart: if (animationTargetNode.RootAnimationNode != null) { animationTargetNode.RootAnimationNode.SetLocalTimeNormalized(0); } break; case MyAnimationTransitionSyncType.Synchronize: if (animationSourceNode.RootAnimationNode != null && animationTargetNode.RootAnimationNode != null) { float normalizedTimeInSource = animationSourceNode.RootAnimationNode.GetLocalTimeNormalized(); animationTargetNode.RootAnimationNode.SetLocalTimeNormalized(normalizedTimeInSource); } break; case MyAnimationTransitionSyncType.NoSynchonization: // do nothing break; default: Debug.Fail("Unknown synchronization option."); break; } } } // transition over time if ((animationTransition.TransitionTimeInSec > 0.0 || transitionWithStart.Transition.TargetNode.PassThrough) && !transitionWithStart.StartNode.PassThrough) { MyStateTransitionBlending stateTransitionBlending = new MyStateTransitionBlending // struct { SourceState = animationSourceNode, TimeLeftInSeconds = animationTransition.TransitionTimeInSec, InvTotalTime = 1.0 / animationTransition.TransitionTimeInSec }; if (!stateTransitionBlending.InvTotalTime.IsValid()) { stateTransitionBlending.InvTotalTime = 1.0f; } m_stateTransitionBlending.Insert(0, stateTransitionBlending); } else { if (transitionWithStart.StartNode.PassThrough && m_stateTransitionBlending.Count > 0) { // node1 -> pass -> node2, take maximum transition time MyStateTransitionBlending stateTransitionBlending = m_stateTransitionBlending[0]; // struct stateTransitionBlending.TimeLeftInSeconds = Math.Max(animationTransition.TransitionTimeInSec, m_stateTransitionBlending[0].TimeLeftInSeconds); stateTransitionBlending.InvTotalTime = 1.0 / stateTransitionBlending.TimeLeftInSeconds; if (!stateTransitionBlending.InvTotalTime.IsValid()) { stateTransitionBlending.InvTotalTime = 1.0f; } m_stateTransitionBlending[0] = stateTransitionBlending; } else if (animationTransition.TransitionTimeInSec <= MyMathConstants.EPSILON) { m_stateTransitionBlending.Clear(); // instantly play new animation, forget about all blending stuff! } } } }
// Event handling - animation state changed. // We need to capture that because: // 1: state can be changed over time. // 2: synchronization of states private void AnimationStateChanged(MyStateMachineTransitionWithStart transitionWithStart) { var animationTransition = transitionWithStart.Transition as MyAnimationStateMachineTransition; var animationSourceNode = transitionWithStart.StartNode as MyAnimationStateMachineNode; var animationTargetNode = animationTransition.TargetNode as MyAnimationStateMachineNode; if (animationSourceNode != null && animationTransition != null) { // synchronization if (animationTargetNode != null) { switch (animationTransition.Sync) { case MyAnimationTransitionSyncType.Restart: if (animationTargetNode.RootAnimationNode != null) animationTargetNode.RootAnimationNode.SetLocalTimeNormalized(0); break; case MyAnimationTransitionSyncType.Synchronize: if (animationSourceNode.RootAnimationNode != null && animationTargetNode.RootAnimationNode != null) { float normalizedTimeInSource = animationSourceNode.RootAnimationNode.GetLocalTimeNormalized(); animationTargetNode.RootAnimationNode.SetLocalTimeNormalized(normalizedTimeInSource); } break; case MyAnimationTransitionSyncType.NoSynchonization: // do nothing break; default: Debug.Fail("Unknown synchronization option."); break; } } // transition over time if (animationTransition.TransitionTimeInSec > 0.0) { MyStateTransitionBlending stateTransitionBlending = new MyStateTransitionBlending(); // struct stateTransitionBlending.SourceState = animationSourceNode; stateTransitionBlending.TimeLeftInSeconds = animationTransition.TransitionTimeInSec; stateTransitionBlending.InvTotalTime = 1.0 / animationTransition.TransitionTimeInSec; m_stateTransitionBlending.Insert(0, stateTransitionBlending); } else { m_stateTransitionBlending.Clear(); // instantly play new animation, forget about all blending stuff! } CurrentUpdateData.Controller.Variables.SetValue(m_variableAnimationFinished, 0.0f); } }
// Update state machine. That means: // Change state if transition conditions are fulfilled. // Update skeleton. public void Update(ref MyAnimationUpdateData data) { if (data.CharacterBones == null) { return; // safety } CurrentUpdateData = data; // local copy if (BoneMask == null) // rebuild bone mask array if not done yet { RebuildBoneMask(); } data.LayerBoneMask = CurrentUpdateData.LayerBoneMask = BoneMask; // pass bone mask array to all subnodes and result Debug.Assert(data.LayerBoneMask != null); base.Update(); // blended transitions // - from most recent to oldest // - please preserve order of blending for three or more states // when CurrentState changes for (int i = 0; i < m_stateTransitionBlending.Count; i++) { MyStateTransitionBlending stateTransitionBlending = m_stateTransitionBlending[i]; float weight = 1 - (float)(stateTransitionBlending.TimeLeftInSeconds * stateTransitionBlending.InvTotalTime); // update nodes that we are just leaving var lastResult = CurrentUpdateData.BonesResult; CurrentUpdateData.BonesResult = null; stateTransitionBlending.SourceState.OnUpdate(this); if (lastResult != null && CurrentUpdateData.BonesResult != null) { for (int j = 0; j < lastResult.Count; j++) { if (data.LayerBoneMask[j]) { // these nodes lose their weight, it goes from 1 to 0 // we need to blend them to last result (current node or another node that we are leaving) CurrentUpdateData.BonesResult[j].Rotation = Quaternion.Slerp(lastResult[j].Rotation, CurrentUpdateData.BonesResult[j].Rotation, 1 - weight); CurrentUpdateData.BonesResult[j].Translation = Vector3.Lerp(lastResult[j].Translation, CurrentUpdateData.BonesResult[j].Translation, weight); } } // give back last result (free list of bones), we dont need it anymore if (lastResult != null) { data.Controller.ResultBonesPool.Free(lastResult); } } // update, decrease remaining time stateTransitionBlending.TimeLeftInSeconds -= data.DeltaTimeInSeconds; m_stateTransitionBlending[i] = stateTransitionBlending; } m_stateTransitionBlending.RemoveAll(s => s.TimeLeftInSeconds <= 0.0); data.BonesResult = CurrentUpdateData.BonesResult; // local copy contains resulting list of bones // setting animation finished flag MyAnimationStateMachineNode currentAnimationNode = CurrentNode as MyAnimationStateMachineNode; if (currentAnimationNode != null && currentAnimationNode.RootAnimationNode != null) { float finishedPercent = currentAnimationNode.RootAnimationNode.GetLocalTimeNormalized(); data.Controller.Variables.SetValue(m_variableAnimationFinished, finishedPercent); } }