[Bug] Subclassed Character IK applies IK on equipping despite weight of 0

Cheo

Active member
Hello, this is going to be yet another one of those "please bear with me" reports ! When trying to make sure that IK is not applied when equipping an item with a Non Dominant Hand IK Target, the Character IK script does that very well, however a simple subclassed version like this one -

C#:
using Opsive.UltimateCharacterController.Character;

public class SubclassedCharacterIK : CharacterIK
{

}

Causes IK to be applied for 2 frames, creating a quick weird arm movement. Here is a video showing all that :


This echoes back to the subclassed ragdoll issues I reported about 2 weeks ago : http://www.opsive.com/forum/index.p...ues-with-roll-and-vertical-cast-buffer.10934/ I will look into this one again soon. It is not normal that such simple subclassings cause issues, and I fear that other niche issues like these do exist. Hope this one can be reproduced and fixed, thanks in advance.
 
This isn't doesn't look like it is related to the component being subclassed, rather it is the timing of when the state is applied which changes the IK weight. I will need to think about a possible fix, since the IK update is occurring before the animation event fires and is causing what you are describing.
 
Hold on - have you watched the full video and tested this for yourself ? At 3:35 you can see it working correctly with Character IK, that's the whole point ! I was thinking of sending you a repro project for the ragdoll issue, I'll do the same for this one if you can't reproduce it, just please try it and tell me if you get the issue.
 
I can do that this afternoon, but to be clear did you try it on your end without the bug happening ?
 

I was wrong about subclassing, it's not what's causing the issue - would have been very strange otherwise - using a body preset setting the hand weight to zero fixed the transition from body to sword. However the transition from pistol to wakizashi in my game still suffers from that IK issue, which doesn't happen when hand weight goes from 0 to 1, but does when it goes from 1 to 0. There must be a timing issue with the ik as you say, I hope you can find a way to fix that, even if it only lasts for 2 frames we don't want such oddities in a game.
 

After some experimenting today I've reached satisfying results by using the Scheduler to delay the assignment of the hand target through CharacterIK.SetItemIKTargets. I used a subclassed version of Third Person Perspective Item for that, here's the code if someone wants to try for themselves :

C#:
using Opsive.Shared.Game;
using Opsive.Shared.Utility;
using Opsive.UltimateCharacterController.Character;
using Opsive.UltimateCharacterController.Character.Abilities.Items;
using Opsive.UltimateCharacterController.Objects;
using Opsive.UltimateCharacterController.ThirdPersonController.Items;
using UnityEngine;

public class SubclassedThirdPersonPerspectiveItem : ThirdPersonPerspectiveItem
{

    [SerializeField] protected bool m_UseIKDelay;
    public bool UseIKDelay { get { return m_UseIKDelay; } set { m_UseIKDelay = value; } }

    [SerializeField] protected float m_IKDelay = 0.1f;
    public float IKDelay { get { return m_IKDelay; } set { m_IKDelay = value; } }

    [SerializeField] protected float m_AimIKDelay = 0.35f;
    public float AimIKDelay { get { return m_AimIKDelay; } set { m_AimIKDelay = value; } }

    ScheduledEventBase m_IKDelayEvent;
    private bool m_StoredActive;

    private Aim AimAbility;

    public override bool Initialize(GameObject character)
    {
        AimAbility = character.GetComponent<UltimateCharacterLocomotion>().GetAbility<Aim>();
        return base.Initialize(character);
    }

    public override void SetActive(bool active, bool hasItem, bool setIKTargets)
    {
        base.SetActive(active, hasItem, setIKTargets);

        if (active)
        {
            // When the item activates or deactivates it should specify the IK target of the non-dominant hand (if any).
            if (m_CharacterIK != null && setIKTargets)
            {
                if (m_UseIKDelay)
                {
                    m_StoredActive = active;
                    if (AimAbility.IsActive)
                    {
                        m_IKDelayEvent = Scheduler.Schedule(m_IKDelay, DelayedSetItemIKTargets);
                    }
                    else
                    {
                        m_IKDelayEvent = Scheduler.Schedule(m_AimIKDelay, DelayedSetItemIKTargets);
                    }
                    Debug.Log(transform.name + " IKDelay = " + m_IKDelay);
                    m_CharacterIK.SetItemIKTargets(m_CharacterItem, active ? m_ObjectTransform : null, m_ParentBone, null, active ? NonDominantHandIKTargetHint : null);
                }
                else
                {
                    m_CharacterIK.SetItemIKTargets(m_CharacterItem, active ? m_ObjectTransform : null, m_ParentBone, active ? NonDominantHandIKTarget : null, active ? NonDominantHandIKTargetHint : null);
                }
            }
        }
        else
        {
            Scheduler.Cancel(m_IKDelayEvent);
        }

    }

    public void DelayedSetItemIKTargets()
    {
        m_CharacterIK.SetItemIKTargets(m_CharacterItem, m_StoredActive ? m_ObjectTransform : null, m_ParentBone, m_StoredActive  ? NonDominantHandIKTarget : null, m_StoredActive ? NonDominantHandIKTargetHint : null);
    }

}

In the video I explain the need for a separate aim delay value, the State System apparently can't update values before Set Active is called.

I think my changes will do the trick for now, but I really hope all of this can be enhanced in the future so that other devs don't have to go through this kind of trouble.
 
I took a closer look at this and in general you have a good solution. I cleaned it up a bit and am now only waiting a single frame.

Code:
            // When the item activates or deactivates it should specify the IK target of the non-dominant hand (if any).
            if (m_CharacterIK != null && setIKTargets) {
                if (active) {
                    m_ScheduledSetIKTransform = Scheduler.Schedule(Time.deltaTime - 0.001f, SetItemIKTargets, m_ObjectTransform, NonDominantHandIKTarget, NonDominantHandIKTargetHint);
                } else {
                    SetItemIKTargets(null, null, null);
                }
            }
        }

Should be:
Code:
            // When the item activates or deactivates it should specify the IK target of the non-dominant hand (if any).
            if (m_CharacterIK != null && setIKTargets) {
                if (active) {
                    m_ScheduledSetIKTransform = Scheduler.Schedule(Time.deltaTime - 0.0001f, SetItemIKTargets, m_ObjectTransform, NonDominantHandIKTarget, NonDominantHandIKTargetHint);
                } else {
                    SetItemIKTargets(null, null, null);
                }
            }
        }

        /// <summary>
        /// Specifies the location of the left or right hand IK target and IK hint target.
        /// </summary>
        /// <param name="objectTransform">The transform of the item.</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>
        private void SetItemIKTargets(Transform objectTransform, Transform nonDominantHandIKTarget, Transform nonDominantHandIKTargetHint)
        {
            if (m_ScheduledSetIKTransform != null) {
                Scheduler.Cancel(m_ScheduledSetIKTransform);
                m_ScheduledSetIKTransform = null;
            }
            m_CharacterIK.SetItemIKTargets(m_CharacterItem, objectTransform, m_ParentBone, nonDominantHandIKTarget, nonDominantHandIKTargetHint);
        }
 
Back
Top