Climbing Ladder

aledg

New member
I've implemented a new ability for Climbing Ladder.


I have some problems with managing the character's movement during the ascent and descent of the staircase and when I have to finish the animation execution.
Following the documentation this is my code:


C#:
    [SerializeField] protected float m_MaxRotationAngle = 0f;
    [SerializeField] protected float m_SpeedClimbing = 0.05f;

    public override void ApplyRotation()
    {
        var angle = Quaternion.Angle(Quaternion.identity, m_CharacterLocomotion.Torque);
        
        if (angle > m_MaxRotationAngle)
        {
            m_CharacterLocomotion.Torque = Quaternion.Slerp(Quaternion.identity, m_CharacterLocomotion.Torque, 0);
        }
    }

    public override void ApplyPosition()
    {
        
        m_CharacterLocomotion.MoveDirection = new Vector3(0, m_SpeedClimbing, 0);

    }



If I press the up / down keys the animations starts correctly and the effects seems to be the one wanted but in reality even without pressing the keys the character continues to go up (look the end of video). Now the direction is always the one in upward direction also with the down button.

1) I would like to understand how to intercept the up / down direction to change the function like this to solve the problem:

m_DirectionUpDown = ..... (-1 / +1)
m_CharacterLocomotion.MoveDirection = new Vector3(0, m_SpeedClimbing * m_DirectionUpDown, 0);



2) how can I stop the animation and the climb when the key up/down is not press?
3) how can I interrupt the ability when the collider ends?
 

Attachments

  • ladder_collider.png
    ladder_collider.png
    371.4 KB · Views: 30
1. Instead of changing the MoveDirection you should modify the AbilityMotor component.
2. This completely depends on how you have the animator setup but I'd set it up so there is an idle animation for climbing that gets activated when forward input is 0.
3. You can call StopAbility.
 
@Kiu just today I went ahead, I completed the climb and the descent, I can start the PULLUP animation at the end of the trigger and I call a StopAbility() via an EventTrigger when the animation PullUp is finish... but I don't understand why even if I call the StopAbility () at the end of the animation it always falls in ability and it doesn't stop on the ...

@Justin any suggestion? This is the complete code an in the video you can see the Animator Setup


C#:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Opsive.UltimateCharacterController.Utility;
using Opsive.UltimateCharacterController.Events;
using Opsive.UltimateCharacterController.Character.Abilities;
using Opsive.UltimateCharacterController.Character.Abilities.Items;


public class ClimbingLadder : DetectObjectAbilityBase
{


    [Tooltip("The speed number of climbing.")]
    [SerializeField] protected float m_SpeedClimbing = 0.02f;

    //[Tooltip("Should the ability stop if the jump button is pressed?")]
    //[SerializeField] protected bool m_StopClimbOnJump = true;
    //public bool StopClimbOnJump { get { return m_StopClimbOnJump; } set { m_StopClimbOnJump = value; } }


    private bool isPullingUp;

    [Tooltip("Specifies if the ability should wait for the OnAnimatorDamageVisualizationComplete animation event or wait for the specified duration before interacting with the item.")]
    [SerializeField] protected AnimationEventTrigger m_PullUpCompleteEvent = new AnimationEventTrigger(false, 0f);
  
    public override void Awake()
    {
        base.Awake();
        isPullingUp = false;
        EventHandler.RegisterEvent(m_GameObject, "OnAnimatorPullUpComplete", OnPullUpVisualizationComplete);
    }

    private void OnPullUpVisualizationComplete()
    {
        isPullingUp = false;
        StopAbility();
    }

    public override void ApplyRotation()
    {

        m_CharacterLocomotion.Torque = Quaternion.Slerp(Quaternion.identity, m_CharacterLocomotion.Torque, 0);

    }

    public override void ApplyPosition()
    {
        base.ApplyPosition();
        
        //just for test the StopAbility()
        if (Input.GetKeyDown("space"))
        {
            StopAbility();
        }
        else {

            if (!isPullingUp)
            {

                if (Input.GetAxis("Vertical") > 0)
                {
                    m_CharacterLocomotion.MoveDirection = new Vector3(0, m_SpeedClimbing, 0);
                }
                else if (Input.GetAxis("Vertical") < 0)
                {

                    m_CharacterLocomotion.MoveDirection = new Vector3(0, m_SpeedClimbing * (-1), 0);
                }
                else
                {
                    m_CharacterLocomotion.MoveDirection = new Vector3(0, 0, 0);
                }
            }
            else
            {
                m_CharacterLocomotion.MoveDirection = new Vector3(0, 0, 0);
            }
                
          
        }
        

    }

    public override bool ShouldBlockAbilityStart(Ability startingAbility)
    {
        return startingAbility is ItemAbility;
    }


    public override void OnTriggerExit(Collider other)
    {
        // The detected object will be set when the ability starts and contains a reference to the object that allowed the ability to start.
        if (other.gameObject == m_DetectedObject)
        {
            isPullingUp = true;
            SetAbilityIntDataParameter((int)1);
        }

        base.OnTriggerExit(other);
    }

    public override void OnDestroy()
    {
        base.OnDestroy();
        EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorPullUpComplete", OnPullUpVisualizationComplete);
    }

}


 
My guess is that you are not using root motion so the actual character transform isn't moving.
 
aledg, is there any chance this may be a step in the right direction?

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



/// <summary>
/// The ClimbLadder ability will allow the character to climb up and down a ladder with a trigger positioned in front of it.
/// </summary>
[DefaultAbilityIndex(2001)]
[DefaultStartType(AbilityStartType.ButtonDown)]
[DefaultAllowRotationalInput(false)]
[DefaultInputName("Action")]
[DefaultUseRootMotionPosition(AbilityBoolOverride.True)]
[DefaultUseLookDirection(false)]

public class ClimbLadder : DetectObjectAbilityBase
{
    [Tooltip("A reference to the Ultimate Character Controller character.")]
    [SerializeField] private GameObject m_Character;
    [Tooltip("Here you can set how fast the character can climb.")]
    [SerializeField] private float m_SpeedClimbing = 1.0f;
    [SerializeField] protected AnimationEventTrigger m_PullUpCompleteEvent = new AnimationEventTrigger(false, 0f);
    private bool isPullingUp;

    private Animator anim;

    public override void Awake()
    {
        base.Awake();
        anim = m_GameObject.GetComponent<Animator>();
        isPullingUp = false;
        EventHandler.RegisterEvent(m_GameObject, "OnAnimatorPullUpComplete", OnPullUpVisualizationComplete);
    }

    protected override void AbilityStarted()
    {
        base.AbilityStarted();
        Debug.Log("Make `mounting from top or bottom` code here.");

        var characterLocomotion = m_Character.GetComponent<UltimateCharacterLocomotion>();
        if (characterLocomotion != null)
        {
            //Look directly at the ladder and move up in the air slightly to mount it.
//This code isn't quite right, because it flips the character on their side I think. Must have something wrong here.
            Vector3 dirToLadder = m_DetectedObject.transform.position - m_Character.transform.position;
           /* characterLocomotion.SetPositionAndRotation( m_Character.transform.position + new Vector3(0, 0.2f, 0), Quaternion.LookRotation( dirToLadder, Vector3.up ), true);*/
        }
    }

    private void OnPullUpVisualizationComplete()
    {
        isPullingUp = false;
        anim.SetInteger("AbilityIntData", 0);
        StopAbility();
    }

    public override void ApplyPosition()
    {
        base.ApplyPosition();
        float verticalInput = Input.GetAxis("Vertical");
        if  (!isPullingUp)
        {
        if  ( verticalInput != 0)
        {
        //Saw Justin's post after writing mine, so...
        //Still need to change these to the AbilityMotor  component. Will update you soon with that change.
            m_CharacterLocomotion.MoveDirection = new Vector3(0, verticalInput > 0 ? m_SpeedClimbing : m_SpeedClimbing * (-1), 0);
        }
        else
        {
            m_CharacterLocomotion.MoveDirection = new Vector3(0, 0, 0);
        }

        anim.SetFloat("ForwardMovement", verticalInput);
        }
    }

    public override bool ShouldBlockAbilityStart(Ability startingAbility)
    {
        return startingAbility is ItemAbility;
    }


    /*
//You may want to test this out too.
public void OnCollisionEnter(Collider other)
    {
        if (other.gameObject.transform.position.y < m_GameObject.transform.position.y)
        {
            //There is an object underneath, presumatbly the ground under the ladder.
            StopAbility();
        }
    }
*/

    public override void OnTriggerExit(Collider other)
    {
        if (other.gameObject == m_DetectedObject)
        {
            isPullingUp = true;
            anim.SetInteger("AbilityIntData", 1);
        }

        base.OnTriggerExit(other);
    }

    public override void OnDestroy()
    {
        base.OnDestroy();
        EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorPullUpComplete", OnPullUpVisualizationComplete);
    }

}


Let me know if it helps at all.
 
Last edited:
@JacobDSiler , thank for your support, I tried your code but It doesn't work...
If you want we can work together on the script.

these are the key points and goals:

  1. start the ability via the trigger on the ladder (done)
  2. start the climbing up & down animation by pressing the buttons up/down (done)
  3. stop the animation climbing and start the idle state when moving = false (done)
  4. block the rotation of the character (done)
  5. align character rotation to ladder (to do)
  6. block abilities if you jump and move away from the ladder (to do)
  7. block abilities if character touches the ground or finishes the DOWN ladder (to do)
  8. start the pulling-up animation when the ladder TOP ends (to do)

Let me know if you need this abilities we can help each other...
 
I did actually test my code, and you're right!
Sorry for the false lead.
My Quaternion look rotation was thinking in three planes, but we only need the rotation on the y axis.
I reworked that snippet, got it working and decided it was over complicated and we don't need anything that difficult so I simplified it.

Anyway, I am more than happy to work on it with you. Yes please! We both need it in our games, and so do others.
Updates on the code to come.

Okay, a small update, with more to come soon:

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



/// <summary>
/// The ClimbLadder ability will allow the character to climb up and down a ladder with a trigge positioned in front of it.
/// </summary>
[DefaultAbilityIndex(2001)]
[DefaultStartType(AbilityStartType.ButtonDown)]
[DefaultAllowRotationalInput(false)]
[DefaultInputName("Action")]
[DefaultUseRootMotionPosition(AbilityBoolOverride.True)]
[DefaultUseLookDirection(false)]
[DefaultDetectVerticalCollisions(AbilityBoolOverride.True)]

public class ClimbLadder : DetectObjectAbilityBase
{
    [Tooltip("A reference to the Ultimate Character Controller character.")]
    [SerializeField] private GameObject m_Character;
    [Tooltip("Here you can set how fast the character can climb.")]
    [SerializeField] private float m_SpeedClimbing = 1.0f;
    [SerializeField] protected AnimationEventTrigger m_PullUpCompleteEvent = new AnimationEventTrigger(false, 0f);
    [SerializeField] private bool m_CanMountFromBehind = true;  // If your ladder can only be mounted from the front, uncheck this in the inspector.
    private bool isPullingUp;
 
    private Animator anim;

    public override void Awake()
    {
        base.Awake();
        anim = m_GameObject.GetComponent<Animator>();
        isPullingUp = false;
        EventHandler.RegisterEvent(m_GameObject, "OnAnimatorPullUpComplete", OnPullUpVisualizationComplete);
    }

    /*
     * //This is sort of pseudocxode so far.
     * public void CanStartAbility(bool canStart)
    {
        Vector3 facing = m_Character.transform.forward - m_DetectedObject.transform.forward;
        // If the character is trying to mount the back of the ladder
        if (-facing.z > 1)
        {
            //If the character is not allowed to mount from behind, then exit.
            if (!m_CanMountFromBehind)
                 false;
        }
         true;
    }*/

    protected override void AbilityStarted()
    {
        base.AbilityStarted();
        TryMountLadder();
    }

    private void TryMountLadder()
    {
        // Mounting logic here

        var characterLocomotion = m_Character.GetComponent<UltimateCharacterLocomotion>();
        if (characterLocomotion != null)
        {
            //Check if the character is on the front of back of the ladder
            Vector3 facing = m_Character.transform.forward - m_DetectedObject.transform.forward;
            Debug.Log(facing.z);
            if (-facing.z < 1)
            {
                Debug.Log("Mounting from the front of the ladder");
                //Look in the same direction as the ladder and move up in the air slightly to mount it.
                Vector3 mountOffset = new Vector3(0, 0.3f, 0);
                characterLocomotion.SetPositionAndRotation(m_Character.transform.position + mountOffset, m_DetectedObject.transform.rotation, false);
                Debug.Log(m_DetectedObject.transform.position);
            }
            else
            if (-facing.z > 1)
            {
                //If the character can mount from behind, then go ahead.
                Debug.Log("Mounting from the back of the ladder");
                //Look in the same direction as the ladder and move up in the air slightly to mount it.
                Vector3 mountOffset = new Vector3(0, 0.3f, 0);
                characterLocomotion.SetPositionAndRotation(m_Character.transform.position + mountOffset, Quaternion.Euler(0, m_DetectedObject.transform.rotation.y + 180 ,0) , false);
                Debug.Log(m_DetectedObject.transform.position);
            }
        }
    }

    private void OnPullUpVisualizationComplete()
    {
        Debug.Log("Finished pulling up.");
        isPullingUp = false;
        anim.SetInteger("AbilityIntData", 0);
        StopAbility();
    }

    public override void ApplyPosition()
    {
        base.ApplyPosition();
        float verticalInput = Input.GetAxis("Vertical");
        if (!isPullingUp)
        {
            if (verticalInput != 0)
            {
                m_CharacterLocomotion.AbilityMotor = new Vector3(0, verticalInput > 0 ? m_SpeedClimbing * Time.deltaTime : m_SpeedClimbing * (-1) * Time.deltaTime, 0);
            }
            else
            {
                m_CharacterLocomotion.AbilityMotor = new Vector3(0, 0, 0);
            }

            anim.SetFloat("ForwardMovement", verticalInput);
        }
    }

    public override bool ShouldBlockAbilityStart(Ability startingAbility)
    {
        if (startingAbility is Jump)
        { StopAbility(); }
        return startingAbility is ItemAbility;
    }


    public void OnCollisionEnter(Collider other)
    {
        if (other.transform.position.y > m_Character.transform.position.y)
        {
            //There is an object underneath, presumably the ground under the ladder.
            Debug.Log("Collided with ground? Object: " +  other.gameObject.name);
            StopAbility();
        }
    }

    public override void OnTriggerExit(Collider other)
    {
        if (other.gameObject == m_DetectedObject)
        {
            if (anim.GetFloat("ForwardMovement") > 0 && other.transform.position.y < m_Character.transform.position.y)
            {
                isPullingUp = true;
                anim.SetInteger("AbilityIntData", 1);
                Debug.Log("Attempting to dismount at the top of the ladder.");
            }
            else
            {
                //Must be going down, so it is probably the end bottom of the ladder he's leaving.
                Debug.Log("Attempting to dismount at the side or bottom of the ladder?");
                StopAbility();
            }
        }

        base.OnTriggerExit(other);
    }

    public override void OnDestroy()
    {
        base.OnDestroy();
        EventHandler.UnregisterEvent(m_GameObject, "OnAnimatorPullUpComplete", OnPullUpVisualizationComplete);
    }

    protected override void AbilityStopped(bool force)
    {
        base.AbilityStopped(force);

        Debug.Log("Stopped climbing the ladder.");
    }

}
 
Last edited:
@Maximuz24 I am just learning as well. I haven't finished the ability yet, but I am sure something will be forthcoming on this thread soon. I know @aledg finished the ability and is testing it now. I'm not sure if he wants to share it here, but I will have to finish the ability soon also, so I'm sure one of us will help you out.
 
I was just thinking of doing this the other day! Hence why I am on this thread, to see if anyone has come up with a climbing ability! I was going to try and update the ability from the first version of the character but not too sure how that would go seeing the entire character has been redone!
 
I've had considerable success with using Playmaker (fully integrated into the Opsive systems) to create good, customizable ladders. My Playmaker FSM allows you to specify the number of rungs, the height between rungs, the transition time between rungs, where the ladder ends, IK for feet and hands, etc., and everything works pretty damned well with decent animations. The best thing is that the ladders can be dropped as prefabs into your world and they automatically work wherever they are placed (no need to add anything to the player). Working on rope-climbing and free-climbing now.

I may well share the systems, but I would urge Opsive to consider the following carefully:

A 'climbing addon' would be a very welcome addition to the Opsive systems, but if it's as unworkable as half the stuff in the Agilty pack (e.g. Ledge Strafe, Hang), forget it. If I can implement good climbing systems using Heath-Robinsonesque systems in Playmaker you should be able to deliver prefab, out-of-the-box climbing solutions. e.g. drag a ladder prefab into the world, tweak a few settings on the ladder's inspector, and the player can climb it.
 
Be aware, Playmaker doesn't offer any out-of-the-box solutions for climbing. You'll still need to set up some pretty complex FSMs and source some animations. The key step, for Opsive, is to set up a Climb state which PM can activate (using Set State). Presets will then be needed to disable gravity and the standard movement controls. At some time this week (assuming work doesn't get me) I'll outline the methods I used.
 
Top