13

Trying to repeat the function function OnAttack() continuously while a button is being held down.

Basically I'm looking for an equivalent to Update() { GetKeyDown() {//code }} But with the input system.

Edit: using a joystick, cant tell what button is being pressed.

1
  • you can tell if a key is down, you can then use a normal unity idea of set a value += delay between repeats, if >= maxwait then set value to 0 and do thing Commented Jan 21, 2020 at 9:14

8 Answers 8

17

Okay I solved it by using "press" in the interactions and giving that The trigger behavior "Press and release", then did

bool held = false;
Update()
{
    if(held)
    {
        //animation
    }
    else if(!held)
    {
        //idle animation
     }
}
OnAttack() {
    held = !held;
}

This way if I press the button held goes to true so it repeats the animation every frame, letting go makes "held" untrue and does the idle animation

Sign up to request clarification or add additional context in comments.

1 Comment

I would recommend using the CallbackContext.ReadValueAsButton which is passed to your Performed function to determine if the value is pressed. This could avoid potential bugs with held = !held from weird IO reads
6

Essentially, the function you assign to the button will be triggered twice per button press, once when it is pressed (performed), and once when it is released (canceled). You can pass in this context at the beginning of your function, just make sure you are using the library seen at the top of this script. Now you can toggle a bool on and off stating whether or not the button is pressed, then perform actions during update dependent on the state of the bool

using static UnityEngine.InputSystem.InputAction;

bool held = false;
Update()
{
    if(held)
    {
        //Do hold action like shooting or whatever
    }
    else if(!held)
    {
        //do alternatice action. Not Else if required if no alternative action
     }
}
//switch the status of held based on whether the button is being pressed or released. OnAttack is called every time the button is pressed and every time it is released, the if statements are what determine which of those two is currently happening.
OnAttack(CallbackContext ctx) {
    if (ctx.performed)
            held= true;
    if (ctx.canceled)
            held= false;
} 

2 Comments

There is no cancelled callback available for press and release interactions, according to the docs: docs.unity3d.com/Packages/[email protected]/manual/…
Thanks ! You saved my day @Oliver Philbrick ! I used this for move my player with WASD. And for keep press key for move.
4

Having an Input related event being controlled and raised inside the Update loop of a monobehaviour is not a good place for it. Interactions, like PressAndRepeat, should be incorporated into the Input system using a custom IInputInteraction.

Examples out there are:

  1. Terse implementation https://github.com/UltraStar-Deluxe/Play/blob/master/UltraStar%20Play/Assets/Common/Input/RepeatedHoldInteraction.cs
  2. With editor bells and whistles https://github.com/ThunderTecke/Press-and-repeat-interaction/blob/main/PressAndRepeatInteraction.cs

These allow the repeated event firing to be part of the Input Action Asset Editor window as shown here: enter image description here

In case those links go down, here's a slightly modified version of the first link. I used this to create the screen shot above.

#if UNITY_EDITOR
using UnityEditor;

[InitializeOnLoad]
#endif
public class PressAndRepeatInteraction : IInputInteraction
{
    /// <summary> Make PressAndRepeatInteraction available in the Input Action Asset Editor window
    /// The method is run by [InitializeOnLoad] </summary>
    static PressAndRepeatInteraction()
    {
        InputSystem.RegisterInteraction<PressAndRepeatInteraction>();
    }

    private static readonly float defaultButtonPressPoint = 0.5f;

    [Tooltip("Perform immediately when the Control is actuated.")]
    public bool fireImmediately = true;

    [Tooltip("Seconds to wait after the button is first pressed down before the perform event fires again.")]
    public float initialPause = 0.5f;

    [Tooltip("Seconds to repeatedly wait for the perform event to be fired again. This pause is repeated until the control is no longer actuated.")]
    public float repeatedPause = 0.2f;

    public float pressPoint = 0.5f;

    private float InitialPauseOrDefault => initialPause > 0.0 ? initialPause : InputSystem.settings.defaultHoldTime;
    private float RepeatedPauseOrDefault => repeatedPause > 0.0 ? repeatedPause : InputSystem.settings.defaultHoldTime;
    private float PressPointOrDefault => pressPoint > 0.0 ? pressPoint : defaultButtonPressPoint;

    private float ignoreInputUntilTime;

    /// <inheritdoc />
    public void Process(ref InputInteractionContext context)
    {
        switch (context.phase)
        {
            case InputActionPhase.Waiting:
            case InputActionPhase.Canceled:
                if (context.ControlIsActuated(PressPointOrDefault)
                    && Time.time >= ignoreInputUntilTime - 0.1f)
                {
                    context.Started();
                    if (fireImmediately)
                    {
                        context.PerformedAndStayStarted();
                    }
                    ignoreInputUntilTime = Time.time + InitialPauseOrDefault;

                    // Check input again when the time elapsed or input changed.
                    context.SetTimeout(InitialPauseOrDefault);
                }
                break;

            case InputActionPhase.Started:
                if (!context.ControlIsActuated())
                {
                    Cancel(ref context);
                }
                else if (Time.time >= ignoreInputUntilTime - 0.1f)
                {
                    // Perform action but stay in the started phase, because we want to fire again after durationOrDefault 
                    context.PerformedAndStayStarted();
                    ignoreInputUntilTime = Time.time + RepeatedPauseOrDefault;

                    // Check input again when the time elapsed or input changed.
                    context.SetTimeout(RepeatedPauseOrDefault);
                }
                break;

            case InputActionPhase.Performed:
                if (!context.ControlIsActuated(PressPointOrDefault))
                {
                    Cancel(ref context);
                }
                break;
            default:
                if (!context.ControlIsActuated(PressPointOrDefault))
                {
                    Cancel(ref context);
                }
                break;
        }
    }

    private void Cancel(ref InputInteractionContext context)
    {
        ignoreInputUntilTime = 0;
        context.Canceled();
    }

    /// <inheritdoc />
    public void Reset()
    {
        // Method needed to implement interface
    }
}

Comments

2

This is paraphrasing a solution I created for a click to move arpg mechanic.

using System.Threading.Tasks;
using UnityEngine;

[SerializeField] private InputAction pointerClickAction;

private bool pointerHeld;

void Start()
{
    
    pointerClickAction.canceled += ClickMouseMove;
    pointerClickAction.started += PointerHoldBegin;
    pointerClickAction.performed += ClickMouseMove;
    pointerClickAction.canceled += PointerHoldEnd;
    
}

private void OnEnable()
{
    pointerClickAction.Enable();
    pointerPositionAction.Enable();
}

private void OnDisable()
{
    pointerClickAction.Disable();
    pointerPositionAction.Disable();
}

public async void ClickMouseMove(InputAction.CallbackContext context)
{
    while (pointerHeld)
    {
        DoSomething();

        await Task.Delay(500);
    }

}

public void PointerHoldBegin(InputAction.CallbackContext context)
{
    pointerHeld = true;
}

public void PointerHoldEnd(InputAction.CallbackContext context)
{
    pointerHeld = false;
}

public void DoSomething()
{
    //Your Code
}

In Task.Delay() you can insert your own polling rate in milliseconds, using Task.Yield() seems to be faster than Update so I don't recommend that, you should poll at the minimum the same delay as your physics/fixed update, having a higher delay gives a performance boost if you don't need a high amount of repetitions per loop. I set mine to 500 since I don't need my character to plot its navigation that often. In regards to TC, you would set the delay to something sensible e.g the attack's animation length, or whatever the delay rate would be for how many attacks can be performed per second.

Comments

1

If you want your project to scale you might want to avoid as much as possible assertions(i.e if functions) in your Update/FixedUpdate/LateUpdate functions as they are executed constantly. I recommand you to read this article about coroutines https://gamedevbeginner.com/coroutines-in-unity-when-and-how-to-use-them/

You can build coroutines which act as local update() functions which are executed only when needed. This will lead you to a better organization of your code and might boost performance in some cases.

For exemple in your case you could use something like this.

bool held = false;

Update()
{
    /* Whatever you want but the least assertion possible */
}

IEnumerator RenderHeldAnimation()
{
    while (held)
    {
        // held animation 
        yield return new WaitForFixedUpdate(); /* Will block until next fixed frame right after FixedUpdate() function */
        // yield return null /* Will block until next next frame right after Update() function */
    }
}

IEnumerator RenderIdleAnimation()
{
    while (!held)
    {
        // idle animation 
        yield return new WaitForFixedUpdate(); /* Will block until next fixed frame right after FixedUpdate() function */
        // yield return null /* Will block until next next frame right after Update() function */
    }
}

OnAttack() {
    held = !held;
    if (held) {
        StartCoroutine(RenderHeldAnimation());
    } else {
        StartCoroutine(RenderIdleAnimation());
    }
}

Comments

1

As mentioned in another answer, the context.canceled is not called when using the Press And Release interaction. As a follow up for documentation purposes as this is a top Google result, to correctly use a bool held without doing a blind toggle (held = !held) which may end up with drift, you can access context.control.IsPressed() like the following:

void OnAttack(CallbackContext context)
{
    held = context.control.IsPressed();
}

Comments

1

I encountered the same issue and this was the method that seemed to work for me

private float _moveSpeed = 3f;
private float _moveDirection;

private void Update()
{
    transform.Translate(_moveSpeed * _moveDirection * Time.deltaTime * transform.forward);
}

public void Move(InputAction.CallbackContext ctx)
{
    _moveDirection = ctx.ReadValue<float>();
}

For some odd reason,the hold interaction works properly in reading the input, but I still need the update function to implement the actual logic.

Can't really complain though, it works. Although I'd love to know why it happens this way.

Hopefully this can be of help to someone.

Comments

-1

You can use a timer for that purpose, in combination with events KeyUp and KeyDown. Please look at the following link. It is pretty much similar to your problem. Link

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.