How to use Inputs in Abilities?

Leorid

New member
I have a anti-gravitation-elevator in my game - as soon as the player enters the trigger, the ability starts.
Using the DetectObject ability works fine. I also managed that the player kind of get dragged to the XZ-center (or horizontal center) of my elevator,
so the ability is working and enabled.

Now I want to use the Jump & Crouch Buttons to make the character float upwards and downwards while inside the elevator but it seems to ignore my inputs.

I had a look at the "New Ability" Example from the documentation, but inputs are not mentioned there.
Also I saw that inputs usually start or stop abilities, which is not the case here - a trigger starts the ability, user then controlls upward & downward movement.

Long story short, code is below, and despite the ability being (active) the fields "bool liftUp" and "bool liftDown" always stay "false" no matter what Buttons I press.
Also I feel like there should be an easier way to get inputs, without having to write so many boilerplate code? IDK - seems like I am doing something wrong here.


C#:
    private UltimateCharacterLocomotionHandler m_Handler;

    [SerializeField] string liftUpInputName = "Jump";
    [SerializeField] string liftDownInputName = "Crouch";

    ActiveInputEvent _liftUpStart;
    ActiveInputEvent _liftUpEnd;
    ActiveInputEvent _liftDownStart;
    ActiveInputEvent _liftDownEnd;

    [SerializeField] bool liftUp;
    [SerializeField] bool liftDown;

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

        m_Handler = m_GameObject.GetCachedComponent<UltimateCharacterLocomotionHandler>();

    }

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

        if (m_Handler != null && InputIndex != -1)
        {
            _liftUpStart = ObjectPool.Get<ActiveInputEvent>();
            _liftUpStart.Initialize(ActiveInputEvent.Type.ButtonDown, liftUpInputName, "OnLiftUpStart");
            m_Handler.RegisterInputEvent(_liftUpStart);
            EventHandler.RegisterEvent(m_GameObject, "OnLiftUpStart", OnLiftUpStart);

            _liftUpEnd = ObjectPool.Get<ActiveInputEvent>();
            _liftUpEnd.Initialize(ActiveInputEvent.Type.ButtonUp, liftUpInputName, "OnLiftUpEnd");
            m_Handler.RegisterInputEvent(_liftUpEnd);
            EventHandler.RegisterEvent(m_GameObject, "OnLiftUpEnd", OnLiftUpEnd);


            _liftDownStart = ObjectPool.Get<ActiveInputEvent>();
            _liftDownStart.Initialize(ActiveInputEvent.Type.ButtonDown, liftDownInputName, "OnLiftDownStart");
            m_Handler.RegisterInputEvent(_liftDownStart);
            EventHandler.RegisterEvent(m_GameObject, "OnLiftDownStart", OnLiftDownStart);

            _liftDownEnd = ObjectPool.Get<ActiveInputEvent>();
            _liftDownEnd.Initialize(ActiveInputEvent.Type.ButtonUp, liftDownInputName, "OnLiftDownEnd");
            m_Handler.RegisterInputEvent(_liftDownEnd);
            EventHandler.RegisterEvent(m_GameObject, "OnLiftDownEnd", OnLiftDownEnd);
        }

        // Reset the accumulated gravity
        m_CharacterLocomotion.GravityAmount = 0;
    }

    void OnLiftUpStart() { liftUp = true; }
    void OnLiftUpEnd() { liftUp = false; }
    void OnLiftDownStart() { liftDown = true; }
    void OnLiftDownEnd() { liftDown = false; }
 
Ok, seems like this one was on me, because the " && InputIndex != -1" was from the Jump.cs Ability and I don't need it, it's not set.
Now they are kind of working, but not reliable - sometimes the jumpAbility or the heightChangeAbility will start, even if my AntiGravLiftAbility is active and I set ShouldBlockAbilityStart() like this:

C#:
    public override bool ShouldBlockAbilityStart(Ability startingAbility)
    {
        // crouch will be used to move downwards
        if (startingAbility is HeightChange) return true;
        // jump will be used to move upwards
        if (startingAbility is Jump) return true;

        return base.ShouldBlockAbilityStart(startingAbility);
    }

as well as quite the same code for ShouldStopActiveAbility.

While this is working, I am still unsure if this is the correct way to go, or if there is a more simple approach to getting inputs inside abilities, something like Input.GetButton() which will fire as long as the button is held down - 4 seperate methods, variables, events just to get the input of 2 buttons?

Also as I said "not reliable" - when I hold the JumpButton down and it switches to the JumpAbility, the "liftUp" variable stays at "true" - thats like the worst case, because the player will keep ascending even if he doesn't press any button.


Here are the settings of the ability:

PpTMtZG.png
 
Last edited:
Using the ActiveInputEvent is correct. If you know that your ability won't be used for AI then you can just grab the PlayerInput component directly and check for inputs. All of the abilities are geared toward the possibility of being used by an AI agent so that's why the ActiveInputEvent object exists.

With a starting/block type set the jump/fall abilities will never start, just as long as your new ability is active. One way to debug this would be to place a breakpoint within Jump.AbilityStarted and see if your anti gravity ability is active.
 
Using the ActiveInputEvent is correct. If you know that your ability won't be used for AI then you can just grab the PlayerInput component directly and check for inputs. All of the abilities are geared toward the possibility of being used by an AI agent so that's why the ActiveInputEvent object exists.

With a starting/block type set the jump/fall abilities will never start, just as long as your new ability is active. One way to debug this would be to place a breakpoint within Jump.AbilityStarted and see if your anti gravity ability is active.

It actually was the Idle State
When entering the elevator, the character is still on the floor and because abilities are sorted by priority in this list and I just added mine without thinking about this, so it was at the very bottom = least priority, the idle ability had a higher priority and took over the anti-grav-lift ability.
then antiGrav was dissabled, idle enabled and I was able to jump and once I did, I re-entered the trigger, idle had it's timer ticking to activate again and I could repeat this over and over.

So I put my ability at a very high priority and everything works now.

Still, I am not totally happy with that code ... I may could use lambda expressions instead of seperate methods to get rid of _some_ of the code or write myself a wrapper class to take care of all redumdant lines.
So I can then write uccInputWraper.Init("Crouch", m_GameObject) get the input via uccInputWraper.IsHoldingDownButton or uccInputWraper.OnButtonDownEvent and unregister it via uccInputWraper.Destroy()

Ok while writing this, I wanted to test it, and yes it works.
So instead of the whole bunch of code above, my start function now looks like this:

C#:
    protected override void AbilityStarted()
    {
        base.AbilityStarted();
        _liftUpInput = ObjectPool.Get<UCCInputWrapper>();
        _liftUpInput.Init(_liftUpInputName, m_GameObject);
        _liftDownInput = ObjectPool.Get<UCCInputWrapper>();
        _liftDownInput.Init(_liftDownInputName, m_GameObject);

        // Reset the accumulated gravity
        m_CharacterLocomotion.GravityAmount = 0;
    }

using it like this (inside the UpdatePosition() method) :

C#:
        float verticalMovement = 0;
        if (_liftUpInput.IsHoldingDownButton) verticalMovement++;
        if (_liftDownInput.IsHoldingDownButton) verticalMovement--;

and finally on the AbilityStopped() method:

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

        _liftUpInput.Destroy();
        ObjectPool.Return(_liftUpInput);
        _liftDownInput.Destroy();
        ObjectPool.Return(_liftDownInput);
    }



So below here is the full wrapper, feel free to use the code however you want.
just without any warranty from my side - I guess thats basically the MIT Licence ^^
Could be helpful for others who are also writing custom abilities.

C#:
using UnityEngine;
using Opsive.UltimateCharacterController.Character;
using Opsive.UltimateCharacterController.Events;
using Opsive.UltimateCharacterController.Game;
using Opsive.UltimateCharacterController.Utility;

namespace Opsive.UltimateCharacterController.Input
{
    /// <summary>
    /// Wrapps UCC Inputs, sets up events, receives them -
    /// primarily meant to be used within abilities
    /// </summary>
    public class UCCInputWrapper
    {
        ActiveInputEvent m_buttonDown;
        ActiveInputEvent m_buttonUp;
        string m_uniqueEventName;
        string m_inputName;

        UltimateCharacterLocomotionHandler m_Handler;
        GameObject m_GameObject;

        bool m_isInitialised = false;

        bool m_isPressed;
        public bool IsHoldingDownButton
        {
            get
            {
                if (!m_isInitialised)
                    Debug.LogWarning("Trying to get input from uninitialised UCCInputWrapper");
                return m_isPressed;
            }
        }

        System.Action m_OnButtonDown;
        public System.Action OnButtonDown
        {
            get { return m_OnButtonDown; }
            set
            {
                if (!m_isInitialised)
                    Debug.LogWarning("Trying to get input from uninitialised UCCInputWrapper");
                m_OnButtonDown = value;
            }
        }
        System.Action m_OnButtonUp;
        public System.Action OnButtonUp
        {
            get { return m_OnButtonUp; }
            set
            {
                if (!m_isInitialised)
                    Debug.LogWarning("Trying to get input from uninitialised UCCInputWrapper");
                m_OnButtonUp = value;
            }
        }

        /// <summary>
        /// Init the wrapper - register all events
        /// </summary>
        /// <param name="inputName">Name of the UnityInput, Example: "Jump"</param>
        /// <param name="m_GameObject">The UCC Character GameObject</param>
        /// <param name="uniqueEventName">(optional) assign a unique event name so it can be called
        /// from outside the ability, ButtonDown & ButtonUp will be added to this name,
        /// for example: uniqueEventName = "Jump" --> "JumpButtonDown", "JumpButtonUp"</param>
        public void Init(string inputName, GameObject gameObject, string uniqueEventName = null)
        {
            if (m_isInitialised)
            {
                Debug.LogWarning("You are trying to initialize an already " +
                    "initialised UCCInputWrapper, this is not allowed");
                return;
            }
            m_isInitialised = true;

            m_inputName = inputName;
            m_GameObject = gameObject;
            m_isPressed = false;
            m_OnButtonDown = null;
            m_OnButtonUp = null;

            // if no uniqueEventName is set, assign a GUID
            if (string.IsNullOrEmpty(uniqueEventName))
                uniqueEventName = System.Guid.NewGuid().ToString();
            m_uniqueEventName = uniqueEventName;
            m_Handler = m_GameObject.GetCachedComponent<UltimateCharacterLocomotionHandler>();

            m_buttonDown = ObjectPool.Get<ActiveInputEvent>();
            m_buttonDown.Initialize(
                ActiveInputEvent.Type.ButtonDown, m_inputName, m_uniqueEventName + "ButtonDown");
            m_Handler.RegisterInputEvent(m_buttonDown);
            EventHandler.RegisterEvent(m_GameObject, m_uniqueEventName + "ButtonDown", OnButtonDownInternal);

            m_buttonUp = ObjectPool.Get<ActiveInputEvent>();
            m_buttonUp.Initialize(
                ActiveInputEvent.Type.ButtonUp, m_inputName, m_uniqueEventName + "ButtonUp");
            m_Handler.RegisterInputEvent(m_buttonUp);
            EventHandler.RegisterEvent(m_GameObject, m_uniqueEventName + "ButtonUp", OnButtonUpInternal);
        }

        void OnButtonDownInternal()
        {
            m_isPressed = true;
            OnButtonDown?.Invoke();
        }

        void OnButtonUpInternal()
        {
            m_isPressed = false;
            OnButtonUp?.Invoke();
        }

        /// <summary>
        /// Unregister all events
        /// </summary>
        public void Destroy()
        {
            if (!m_isInitialised) return;

            m_isInitialised = false;

            m_Handler.UnregisterInputEvent(m_buttonDown);
            ObjectPool.Return(m_buttonDown);
            m_Handler.UnregisterInputEvent(m_buttonUp);
            ObjectPool.Return(m_buttonUp);

            EventHandler.UnregisterEvent(m_GameObject, m_uniqueEventName + "ButtonDown", OnButtonDownInternal);
            EventHandler.UnregisterEvent(m_GameObject, m_uniqueEventName + "ButtonUp", OnButtonUpInternal);
        }

    }
}
 
Last edited:
Top