FinalIK VRIK Integration with CharacterLocomotion.UpdateLocation == UpdateLocation.FixedUpdate

airoll

Member
Hello!

I am working currently trying to transition my game to use FixedUpdate for all characters (for determinism purposes), and am now in the process of trying to transition my player characters in VR to use FixedUpdate instead of Update.

Currently, I have a character setup with UltimateCharacterLocomotion and a VRIK component. When I set CharacterLocomotion.UpdateLocation = UpdateLocation.Update, then the character works fine and I am able to perform actions like moving my hand and shooting my weapon. See the expected behavior in this video.

However, as soon as I switch the CharacterLocomotion.UpdateLocation = UpdateLocation.FixedUpdate, my character model goes berserk and my weapon flickers like crazy (with the position changing). See the behavior for the game view in this video and the scene view in this video.

Is there a way I can make VRIK compatible with FixedUpdate? Should I create a class that inherits CharacterIK and manually calls the IK solver update in the Move() method?

Thanks!
 
The FinalIK integration does currently require the character to be updated within Update. In version 2.3.4 there were some FixedUpdate changes so it may work with FixedUpdate now, but I can also add it to my list of things to look at for future support.
 
Hi @Justin I updated UCC to v.2.3.4 and tried to create a class that inherits from CharacterIKBase that would control when the VRIK updates. My implementation is below. However, it looks like there are still some issues, including jittering, flickering (which can't be seen in the video but can be seen in the HUD in VR), and it looks like the upper body is completely separated from the lower body as you can see in this video.

Any immediate things that come to mind to try? Note my character has a UltimateCharacterLocomotion, PlayerFinalIKBridge, and VRIK components. I do not use the existing FinalIKBridge implementation, nor do I use anything from the VR add-on. Separately, I was able to get a similar class working for my AI characters that is using FixedUpdate.

C#:
public class PlayerFinalIKBridge : CharacterIKBase
    {
        [Header("Configuration")]
        [Tooltip("The VR IK to use.")]
        [SerializeField] protected VRIK m_VRIK;

        private GameObject m_GameObject;
        private bool m_FixUpdated;

        /// <summary>
        /// Initialize the default values.
        /// </summary>
        protected override void Awake()
        {
            base.Awake();

            m_GameObject = gameObject;

            // Initialize the IK components. Disable the FinalIK components as they will be updated manually.
            if (m_VRIK != null)
            {
                if (m_VRIK.references.isFilled)
                {
                    m_VRIK.solver.SetToReferences(m_VRIK.references);
                }

                m_VRIK.enabled = false;
            }

            EventHandler.RegisterEvent<Vector3, Vector3, GameObject>(m_GameObject, "OnDeath", OnDeath);
            EventHandler.RegisterEvent(m_GameObject, "OnRespawn", OnRespawn);
        }

        /// <summary>
        /// Sets the target that the character should look at.
        /// </summary>
        /// <param name="active">Should the character look at the target position?</param>
        /// <param name="position">The position that the character should look at.</param>
        public override void SetLookAtPosition(bool active, Vector3 position)
        {
        }

        /// <summary>
        /// Returns the default position that the character should look at.
        /// </summary>
        /// <returns>The default position that the character should look at.</returns>
        public override Vector3 GetDefaultLookAtPosition()
        {
            return m_VRIK.references.head.position + Vector3.forward;
        }

        /// <summary>
        /// Specifies the location of the left or right hand IK target and IK hint target.
        /// </summary>
        /// <param name="itemTransform">The transform of the item.</param>
        /// <param name="itemHand">The hand that the item is parented to.</param>
        /// <param name="nonDominantHandTarget">The target of the left or right hand. Can be null.</param>
        /// <param name="nonDominantHandElbowTarget">The target of the left or right elbow. Can be null.</param>
        public override void SetItemIKTargets(Transform itemTransform, Transform itemHand, Transform nonDominantHandTarget, Transform nonDominantHandElbowTarget)
        {
        }

        /// <summary>
        /// Specifies the target location of the limb.
        /// </summary>
        /// <param name="target">The target location of the limb.</param>
        /// <param name="ikGoal">The limb affected by the target location.</param>
        /// <param name="duration">The amount of time it takes to reach the goal.</param>
        public override void SetAbilityIKTarget(Transform target, IKGoal ikGoal, float duration)
        {
        }

        /// <summary>
        /// Indicates that the animator has updated.
        /// </summary>
        /// <param name="fixedUpdate">Is the IK being updated within the FixedUpdate loop?</param>
        public override void Move(bool fixedUpdate)
        {
            if (!fixedUpdate)
            {
                if (m_FixUpdated)
                {
                    m_VRIK.solver.Update();
                    m_FixUpdated = false;
                }
            }
            else
            {
                m_VRIK.solver.FixTransforms();
                m_FixUpdated = true;
            }
        }

        /// <summary>
        /// The character has died.
        /// </summary>
        /// <param name="position">The position of the force.</param>
        /// <param name="force">The amount of force which killed the character.</param>
        /// <param name="attacker">The GameObject that killed the character.</param>
        private void OnDeath(Vector3 position, Vector3 force, GameObject attacker)
        {
            enabled = false;
        }

        /// <summary>
        /// The character has respawned.
        /// </summary>
        private void OnRespawn()
        {
            enabled = true;
        }

        /// <summary>
        /// The character has been destroyed.
        /// </summary>
        private void OnDestroy()
        {
            EventHandler.UnregisterEvent<Vector3, Vector3, GameObject>(m_GameObject, "OnDeath", OnDeath);
            EventHandler.UnregisterEvent(m_GameObject, "OnRespawn", OnRespawn);
        }
 
At a high level that looks good (particularly your FixedUpdate flag), but with these type of situations it's really hard to debug unless I can step through it. I will let you know as soon as I am able to take a closer look at it.
 
Sounds good! When you get a chance to take a look at it, if you need a build I'd be happy to provide you with one (I can PM you a link). Thanks again for all your help Justin!
 
Top