Ability. Stop player rotating and change movement

buhu

New member
When ability starts I want player to align with detected object (this is working) and then stop camera from controlling rotation of player and change movement so vertical inputs move player up and down - I want ofc player to stop reacting to gravity when thats happening



C#:
using Opsive.Shared.Events;
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;



[DefaultAbilityIndex(2001)]
[DefaultStartType(AbilityStartType.ButtonDown)]
[DefaultAllowRotationalInput(false)]
[DefaultInputName("Action")]
[DefaultUseRootMotionPosition(AbilityBoolOverride.True)]
[DefaultUseLookDirection(false)]
[DefaultDetectVerticalCollisions(AbilityBoolOverride.True)]

public class Climb : 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_Speed = 1.0f;
    
  

    private Animator anim;
    
    public override void Awake()
    {
        base.Awake();
        anim = m_GameObject.GetComponent<Animator>();
      
    }
    
    
    protected override void AbilityStarted()
    {
        base.AbilityStarted();
        TryMount();
    }

    private void TryMount()
    {
        

        var characterLocomotion = m_Character.GetComponent<UltimateCharacterLocomotion>();
        if (characterLocomotion != null)
        {

            
            characterLocomotion.SetPositionAndRotation(m_DetectedObject.transform.position, m_DetectedObject.transform.rotation, false);
            
            anim.SetInteger("AbilityIndex", 120);
            anim.SetTrigger("AbilityChange");
        }
    }


    public override void ApplyRotation()
    {
        base.ApplyRotation();
        
        m_CharacterLocomotion.transform.rotation = m_DetectedObject.transform.rotation;
        
    }

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

        }
        else
        {
            m_CharacterLocomotion.MoveDirection = new Vector3(0, 0, 0);
        }

        anim.SetFloat("ForwardMovement", verticalInput);
        
    }
}
 
For a ladder ability I would approach it as:

- Disable the character gravity
- Disable the character horizontal and vertical collisions.
- Rotate the character towards the ladder within UpdateRoation.
- Move the character by adjusting the AbilityMotor within UpdatePosition. Take a look at the Ride ability for an example of this.
 
Ok. Looks like it's working.
Now I have a question about ending this ability.
First:
C#:
public override bool ShouldBlockAbilityStart(Ability startingAbility)
    {
        if (startingAbility is Jump)
        { StopAbility(); }

        if(startingAbility is Fall)
        {
            return startingAbility is null;
        }
        return startingAbility is ItemAbility;
    }

This code not working. I have set StopType to Manual. Its not working with any combination in hierarchy of abilities.

Second:
How to make Ability be possible to trigger only when player is in specific (different) ability and otherwise ignore it completely.
 
This code not working. I have set StopType to Manual. Its not working with any combination in hierarchy of abilities.
ShouldBlockAbilityStart will prevent other abilities from starting, but not the current ability. You can call TryStopAbility on the Ultimate Character Locomotion component to stop the ability. You should not call that within ShouldBlockAbilityStart. I normally do it within the Update loop of the ability.

How to make Ability be possible to trigger only when player is in specific (different) ability and otherwise ignore it completely.
You can override CanStartAbility to provide different conditions for when the current ability should start.
 
Ok, TryStartAbility worked... Where I can find documentation with descriptions - how and when I suppose to use all your functions ? I don't see it in docs on a page nor in pdf.
Now I would like to know how to stop ability automatically... I guess best would be simple script that stores endpoints of ladder and if player get close to it then stop ability. Problem is I don't want to store those positions in ability. Rather have a script on the object that stores endpoints for every ladder separately... but I can't get access to the monobehaviour from m_DetectedObject - how I should do it...
Or maybe there is a better way to make endpoints for ladder?
 
Ok, TryStartAbility worked... Where I can find documentation with descriptions - how and when I suppose to use all your functions ? I don't see it in docs on a page nor in pdf.
Take a look at Ability.cs for the comments on what each method does. TryStart/StopAbility is listed here: https://opsive.com/support/documentation/ultimate-character-controller/character/abilities/

but I can't get access to the monobehaviour from m_DetectedObject - how I should do it...
You can access any MonoBehaviour from the DetectedObject - DetectedObject is a GameObject so the regular GetComponent methods work.
 
heh... we are almost there. I have one more problem... Do you have idea why when pressing Jump button ability ends properly but when I get to the positions things get wierd
- on up position player starts to jump and looks like gravity dont work at all (inputs works)
- on down position inputs stops working but gravity is ok
in both rotation works...
I'm confused.

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

public class LadderClimbAbility : DetectObjectAbilityBase
{
    [SerializeField] protected float rotateTowardsSpeed = 1f;
    [SerializeField] protected float m_SpeedClimbing = 1f;
    private Animator anim;
    private float verticalInput;

    Transform up;
    Transform down;
    public override void Awake()
    {
        base.Awake();
        anim = m_GameObject.GetComponent<Animator>();

    }

    protected override void AbilityStarted()
    {
        base.AbilityStarted();
        var cameraController = GameObject.FindObjectOfType<CameraController>();
        if (cameraController.ActiveViewType.FirstPersonPerspective)
        {
            cameraController.TogglePerspective();
        }
        up = m_DetectedObject.GetComponent<LadderInteractionTrigger>().up;
        down = m_DetectedObject.GetComponent<LadderInteractionTrigger>().down;
    }


    public override void UpdateRotation()
    {
        base.UpdateRotation();

        Vector3 LadderDirection = Vector3.RotateTowards(m_Transform.forward, m_DetectedObject.transform.forward, rotateTowardsSpeed * Time.deltaTime, 0.0f);
        m_Transform.rotation = Quaternion.LookRotation(LadderDirection);
    }



    public override void UpdatePosition()
    {
        base.UpdatePosition();
        m_Transform.position = new Vector3(m_DetectedObject.transform.position.x, m_Transform.position.y, m_DetectedObject.transform.position.z);
        m_CharacterLocomotion.MotorThrottle = Vector3.zero;
        verticalInput = Input.GetAxis("Vertical");
        if (m_DetectedObject == null)
        {
            verticalInput = 0;
        }

        float distancetoUp = Vector3.Distance(m_Transform.position, up.position);
        float distancetoDown = Vector3.Distance(m_Transform.position, down.position);
      
        if (verticalInput != 0)
        {
            anim.SetBool("Moving", true);

            m_CharacterLocomotion.AbilityMotor = new Vector3(0, verticalInput > 0 ? m_SpeedClimbing * Time.deltaTime : m_SpeedClimbing * (-1) * Time.deltaTime, 0);
        }
        else
        {
            anim.SetBool("Moving", false);
            m_CharacterLocomotion.AbilityMotor = new Vector3(0, 0, 0);
        }
        anim.SetFloat("ladderClimbing", verticalInput);

        i
if (Input.GetButtonDown("Jump") || distancetoDown < 1 && verticalInput < -0.9f || distancetoUp < 1 && verticalInput > 0.9f)
        {
            m_CharacterLocomotion.TryStopAbility(this);
            anim.SetInteger("AbilityIndex", 0);
            anim.SetTrigger("AbilityChange");
        }
    }




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

  

}
 
Last edited:
I'm not sure if this is the cause but instead of checking the input directly and stopping the ability you should instead subscribe to the OnAbilityActive callback (described here). Within that callback you should then stop the ability if Jump activates.
 
I don't understand what you mean... Where should I add it? How that will help?
Its working as it should when I check input directly... it's dont work as it should in this part
" || distancetoDown < 1 && verticalInput < -0.9f || distancetoUp < 1 && verticalInput > 0.9f) "
My guess is that's because when pressing button Jump ability gets activated and he knows what to do and in other two posibilities he dont really know what to do... so my guess is actually this part is not working:

{
m_CharacterLocomotion.TryStopAbility(this);
anim.SetInteger("AbilityIndex", 0);
anim.SetTrigger("AbilityChange");
}
 
As described on the page Justin linked, you would first register to the OnAbilityActive callback:

C#:
EventHandler.RegisterEvent<Ability, bool>(gameObject, "OnCharacterAbilityActive", OnAbilityActive);

Then create the OnAbilityActive method:

C#:
void OnAbilityActive(Ability ability, bool active) {
    if (ability == jumpAbility) { // get a reference to the Jump ability earlier, e.g. in Awake
        m_CharacterLocomotion.TryStopAbility(this);
    }
}

And then make sure to un-register the event in OnDestroy:

C#:
EventHandler.UnregisterEvent<Ability, bool>(gameObject, "OnCharacterAbilityActive", OnAbilityActive);

The reason for this is because simply listening to the character's Jump key input is not necessarily going to coincide exactly with when the ability itself starts, depending on execution orders, update timings, etc.

edit: When calling TryStopAbility, you may also want to pass in force = true.
 
Ok, noted... but I still dont know how to stop ability without activating another one... just go to normal state.
 
TryStopAbility will stop the current ability. It does not start another ability. What could happen though is that another ability starts up because the conditions are now true for being able to start the automatic ability because the stopped ability is no longer active.

Do you have the Agility Pack? The Hang ability is a great example for similar steps that you'd take with a ladder climb ability.
 
No, I don't have Agility pack.
Whole ladder climbing logic works. Currently I made a hack and when player is close to the end point and try to move closer I set speed to 0 so he dont go farther than he should. But still only way to stop this ability is pressing jump and that makes player jump off the ladder - its works but I really want to make it automatic... but I already waste so much time working on such a simple thing like climbing ladder. Mehhh...
 
Within the Update method I would check if the character is above the end point, and if they are you should then stop the current ability. An example of an ability that does this is Drive.
 
Top