Align to Gravity Change Causing Issues

AntL

Member
Hi there, I was previously working in version 2.1.1 and made a simple abilty that inherited from AlignToGravity.

The ability basically changed the gravity of the character to align to the normal of a surface and caused the player character to fall and rotate to that surface (so the characters local 'up' matched the normal of the surface he's standing on, effectively making it the new 'floor'.

The problem is after upgrading to 2.1.8, the player still falls but no longer rotates to the normal of the surface.

Here is the rotation code from 2.1.1 within AlignToGravity:


Code:
        /// <summary>
        /// Rotates the character to be oriented with the specified normal.
        /// </summary>
        /// <param name="targetNormal">The direction that the character should be oriented towards on the vertical axis.</param>
        protected void Rotate(Vector3 targetNormal)
        {
            var rotation = m_Transform.rotation * m_CharacterLocomotion.Torque;
            var proj = (rotation * Vector3.forward) - (Vector3.Dot((rotation * Vector3.forward), targetNormal)) * targetNormal;
            if (proj.sqrMagnitude > 0.0001f) {
                var alignToGroundSpeed = m_CharacterLocomotion.Platform == null ? m_RotationSpeed * m_CharacterLocomotion.TimeScale * TimeUtility.FixedDeltaTimeScaled : 1;
                var targetRotation = Quaternion.Slerp(rotation, Quaternion.LookRotation(proj, targetNormal), alignToGroundSpeed);
                var rotationDelta = m_CharacterLocomotion.Torque * (Quaternion.Inverse(rotation) * targetRotation);
                var collisionRotationDelta = m_CharacterLocomotion.CheckRotation(rotationDelta, true);
                // If the collision rotation is the same as the rotation delta then there are no collisions with aligning to the ground and the maximum
                // rotation delta should be applied (the collision rotation delta). If, however, there is a collision then only the original rotation delta
                // should be applied so the character can still rotate from input/root motion.
                m_CharacterLocomotion.Torque = (collisionRotationDelta == rotationDelta ? collisionRotationDelta : m_CharacterLocomotion.Torque);
            }
        }


Looking through the release notes, a fix was made in 2.1.2 which Fixed AlignToGravityZone from not correctly detecting if it can stop.
I'm assuming that this caused the change in the rotate function which is currently used in 2.1.8 (in addition to other changes). Here is the updated AlignToGravity code in 2.1.8

Code:
        /// <summary>
        /// Rotates the character to be oriented with the specified normal.
        /// </summary>
        /// <param name="targetNormal">The direction that the character should be oriented towards on the vertical axis.</param>
        protected void Rotate(Vector3 targetNormal)
        {
            var deltaRotation = Quaternion.Euler(m_CharacterLocomotion.DeltaRotation);
            var rotation = m_Transform.rotation * deltaRotation;
            var proj = (rotation * Vector3.forward) - (Vector3.Dot((rotation * Vector3.forward), targetNormal)) * targetNormal;
            if (proj.sqrMagnitude > 0.0001f) {
                Quaternion targetRotation;
                if (m_CharacterLocomotion.Platform == null && !m_Stopping) {
                    var alignToGroundSpeed = m_RotationSpeed * m_CharacterLocomotion.TimeScale * Time.timeScale * m_CharacterLocomotion.DeltaTime;
                    targetRotation = Quaternion.Slerp(rotation, Quaternion.LookRotation(proj, targetNormal), alignToGroundSpeed);
                } else {
                    targetRotation = Quaternion.LookRotation(proj, targetNormal);
                }
                var rotationDelta = deltaRotation * (Quaternion.Inverse(rotation) * targetRotation);
                m_CharacterLocomotion.DeltaRotation = rotationDelta.eulerAngles;
            }
        }


And here was the ability I used to achieve the change in gravity and rotation of the character towards the normal of the surface I wanted him to rotate to:

Code:
using UnityEngine;
using Opsive.UltimateCharacterController.Character.Abilities;
using Opsive.UltimateCharacterController.Events;
using Opsive.UltimateCharacterController.Character;

public class AlignToGravityNormal : AlignToGravity
{
    private Vector3 m_targetNormal = Vector3.up;

    public override void Awake()
    {
        base.Awake();
        EventHandler.RegisterEvent<Vector3>( "ChangeGravity", UpdateTargetNormal);
    }

    private void UpdateTargetNormal(Vector3 newTargetNormal )
    {
        var characterLocomotion = GetComponent<UltimateCharacterLocomotion>();
        if ( characterLocomotion!=null )
        {
            characterLocomotion.GravityDirection = -newTargetNormal.normalized;
           
          //Make the player auto jump before fallign and rotating           
          AddForce(m_CharacterLocomotion.Up * 0.1f, 1, false, false);
        }

        m_targetNormal = newTargetNormal.normalized;
    }

    public override void OnDestroy()
    {
        base.OnDestroy();
        EventHandler.UnregisterEvent<Vector3>("ChangeGravity", UpdateTargetNormal);

    }
    public override void ApplyRotation()
    {
        Rotate(m_targetNormal);
    }
}

Additional, there are also other differences between AlignToGravity 2.11 and 2.1.8. AbilityStarted() has changed, there's an Update() in the 2.1.1 version but only a late update in 2.1.8 etc

I wonder if you could give me a pointer as to why the character may not rotate as he used to and a potential fix?
Thanks so much for any assistance.
 
Throughout version 2.1.x there have been many changes related to the execution order and handling of abilities. I don't remember details of those specific changes and the release notes are taken directly from git so that won't help.

Have you tried just using the standard align to gravity normal ability?
 
Yeah so, the code above (AlignToGravityNormal) inherits from AligntoGravity. I've got it working again but have a couple of questions (there aren't any docs on AlignToGravity to apologies)

1. In AlignToGravity, you check m_Stopping in lateupdate. As I inherit from AlignToGravity, should I set this flag to true as opposed to calling StopAbility?
2. My AlignToGravityNormal script won't trigger while AlignToGround is running. Should I disable this at the start of the script then reenable at the end?
3. What does m_CharacterLocomotion.AlignToGravity do?
4. Could you explain ResetAlignToGravity()?

Cheers
 
1. m_Stopping is called so the character can reorient back to the stop gravity direction when the ability is done.
2. You could either have your ability be concurrent so it can trigger, or manually enabling it would also work.
3. This tells the base locomotion controller that the up direction will change.
4. This method resets the locomotion controller back to the starting values.
 
One final question @Justin. So as I mentioned, the ability basically changes the gravity of the character to align to the normal of a specific surface. It causes the player to fall towards that surface (via your Fall ability) and rotates the character to align with the normal of that surface (my AlignToGravityNormal) ability. So the characters local 'up' matches the normal of the surface he's standing on, effectively making it the new 'floor'.

This all works great now, except when I change Unity physics to match the new gravity direction. When I do this, the character no longer falls (ie fall no longer changes the characters position) and gets stuck in the fall ability.

Any ideas what could be causing this issue?
Thanks
 
You're changing the gravity direction of Unity's physics system? The controller does not use this property, it uses the CharacterLocomotion.GravityDirection property instead.
 
No I'm using CharacterLocomotion.GravityDirection for the player, but as I need other rigidbodies in the scene (boxes, crates etc) to be affected by gravity, I'm changing Unity's physics.gravity for those objects.
 
I'm not sure - I just tried to reproduce it within the gravity room of the demo scene and when I changed the Unity physics direction the fall ability still played correctly.
 
So I found the problem. It's in Character Locomotion | DeflectVerticalCollisions:

Code:
            if (accumulateGravity) { // The character is in the air.
                // Accumulate gravity.
               m_GravityAmount += (Physics.gravity.y * m_GravityModifier * 0.001f) / Time.timeScale; // HERE!
              
            } else { // The character is on the ground.
                // If the character is standing on a rigidbody then a downward force should be applied.
                if (m_GroundRaycastHit.rigidbody != null) {
                    m_GroundRaycastHit.rigidbody.AddForceAtPosition(-m_Up * ((m_Mass / m_GroundRaycastHit.rigidbody.mass) + m_GravityAmount) / m_DeltaTime,
                                                            m_Transform.position + m_MoveDirection, ForceMode.Force);
                }

                // No gravity is applied when the character is grounded.
                m_GravityAmount = 0;
            }

The code uses physics.gravity.y to calculate that gravity amount. Obviously if the gravity direction is being changed (say to -9.81,0f 0f ) then it returns 0, so my character wasn't falling.

Would it be possible in a future update to change this code so it checks for the current gravity direction?

Thanks
Ant
 
Top