Search Unity

Input from Keyboard is delayed

Discussion in 'Input System' started by Caidek, May 27, 2020.

  1. Caidek

    Caidek

    Joined:
    Jun 17, 2018
    Posts:
    5
    Hello all,

    I am new to using the new unity input 1.0 system and have been having an issue with it. I have code that polls for if the keyboard button is being held or not, which works, however it appears to only fire on 1 of every 5-10 key presses.

    Example:

    Player presses F key - attack fires, player holds F key - stomp attack fires
    Player presses F key - nothing happens, Player presses F key - nothing happens, Player holds F key - nothing happens, Player presses F key - attack fires.


    Can someone take a look and see if I am doing something wrong?

    Input Actions File


    Input c# script

    Code (CSharp):
    1. private void ProcessPlayerInput()
    2.         {
    3.             // Update horizontal inputs
    4.             horizontal += controls.Player.Move.ReadValue<float>();
    5.             horizontalRaw += horizontal;
    6.  
    7.             // Clamp horizontal to -1, 0, 1 as opposed to horizontalRaw which is not clamped
    8.             horizontal = Mathf.Clamp(horizontal, -1f, 1f);
    9.  
    10.             // Check for jump inputs
    11.             jumpPressed = jumpPressed || controls.Player.Jump.triggered;
    12.  
    13.             // Poll for attack inputs
    14.             controls.Player.Attack.performed +=
    15.             ctx =>
    16.             {
    17.                 if (ctx.interaction is HoldInteraction)
    18.                 {
    19.                     // Attack button being held = perform stomp attack
    20.                     stompAttack = true;
    21.                 }
    22.                 else
    23.                 {
    24.                     // Attack button was only pressed = basic attack
    25.                     basicAttack = true;
    26.                 }
    27.             };
    28.         }
    Originally, as per the demo scripts in (SimpleControls demo that comes with the package) it had the code block in the Awake() method, however this doesn't seem correct to me so I have moved it into the Update() method instead.
     

    Attached Files:

  2. Caidek

    Caidek

    Joined:
    Jun 17, 2018
    Posts:
    5
    Bump. Hoping that the god of the forum @Rene-Damm will see!
     
    Last edited: May 29, 2020
  3. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    1. You probably don't want both of those "interactions" in your attack action, under the properties tab you showed in your InputActionAsset. I believe Interactions are meant to modify the timing/condition of when "performed" is actually called. i.e. the Hold interaction makes it so that the callback happens after the button is held for a certain amount of time. Press on the other hand, checks for actuation, and as it says clearly is not needed for default press behavior. I'm not sure if overlapping interactions is really going to perform the way you expect it to, with both being fired. I've never tried it but there are probably better ways of going about it.
    2. This code right here:
      controls.Player.Attack.performed += ctx => //etc.
      is how you set up a callback. As in, when you += to an event (such as performed), you "add" the given function as a callback. So you do NOT want it in Update, as you will be adding this callback every frame. The other code about horizontal simply polls for the current state of action using ReadValue, which means you do want it in Update().
    3. As soon as Jump is "triggered", and this line is run:
      jumpPressed = jumpPressed || controls.Player.Jump.triggered;
      then jumpPressed will always be true, unless you are resetting it somewhere else that isn't shown.
    So, altogether, here's what I recommend:
    • Rethink how you handle jumping if that's going to be an issue. If you just need the bool to be active on one frame set it to .triggered only instead of the or statement. If you need it to be active whenever jump is held, just use ReadValue() > .5f for jump. With the button type, ReadValue returns 1 when a button is held.
    • Remove both interactions from Attack
    • Remove your attack code from Update()
    • Instead, you can do simple state checking to tell if you need to use the basic or the stomp attack. The key thing to note is that you need to know when the button is released. And you need a time to set for release before stomp is called. i.e. if the player releases within .2s, then use the basic attack. Else, use the stomp. Here's what that looks like:
    Code (CSharp):
    1. inputHeld = controls.Player.attack.ReadValue<float>() > .9f;
    2.  
    3. if(inputHeld)
    4. {
    5.     attackHeld = true;
    6.     attackProg += deltaTime;
    7.  
    8.     if(attackProg >= .2f)
    9.     {
    10.         stompAttack = true;
    11.         attackHeld = false;
    12.     }
    13. }
    14. else
    15. {
    16.     if(attackHeld && attackProg < .2f)
    17.     {
    18.         basicAttack = true;
    19.         attackHeld = false;
    20.     }
    21. }
    Something along those lines. Ideally you actually need to only trigger attack held if the button was "first pressed" and not continuously held after doing an attack already, but again that's just more state checking. The idea is that you can detect release and do a basic attack, or wait until the timer is greater than your held amount, in which case you do a stomp. I hope that made sense and puts you on the right track. If you need to implement callbacks for your attack, be sure to set them in Awake() only so that they only happen once.

    It may also be possible to do it the way you were doing already, but you won't have as much control. If it does work though you'd need to assign callbacks in Awake only like I said, which may be why your results are so inconsistent.
     
  4. Caidek

    Caidek

    Joined:
    Jun 17, 2018
    Posts:
    5
    Hey sir! Thanks so much for the help so far!

    I have tried with an implementation of your code (this is running in the UPDATE event, is that still the correct placement) as follows:

    Code (CSharp):
    1.         // Check for attack inputs
    2.         inputHeld = controls.Player.Attack.ReadValue<float>() > .9f;
    3.  
    4.         Debug.Log(controls.Player.Attack.ReadValue<float>());
    5.  
    6.         if (inputHeld)
    7.         {
    8.             attackHeld = true;
    9.             attackProg += Time.deltaTime;
    10.          
    11.             if (attackProg >= .2f)
    12.             {
    13.                 stompAttack = true;
    14.                 attackHeld = false;
    15.                 attackProg = 0;
    16.             }
    17.         }
    18.         else
    19.         {
    20.             if (attackHeld && attackProg < .2f)
    21.             {
    22.                 basicAttack = true;
    23.                 attackHeld = false;
    24.             }
    25.         }
    However, the result of
    controls.Player.Attack.ReadValue<float>()
    is always either 0 or 1. Even a quick press means the value always returns 1.

    By doing two quick key presses, this means that both attacks actually fire one after the other which is not intended. Am I missing something else here? :) Thanks again!
     
  5. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    You seem to be misunderstanding the goal of the ReadValue line. Yes, when a button is held it is 1, and when not it is 0. The purpose is simply to check if we are currently holding the attack button. The attackProg is a timer which keeps track of how long the button is held. When attackProg is < .2f and inputHeld is false, then we know we've released before .2s is up. Otherwise, it will stomp attack if > than .2 and held is true.

    But as I mentioned above, you probably need more logic in order to determine if you've pressed it for the "first time". This was just meant to be an example and you're probably getting double hits because the action being held is carried over. To do this you need to know if the action was pressed this frame, which is what triggered is for.

    I'm sorry I don't have the time right now to fully flesh out my example. However I can paste in how I accomplished this in my own game, the idea is the same so maybe you can get some meaning from it:

    Code (CSharp):
    1. var primaryAction = inputState.GetInputActionWrapper("Gameplay", "PrimaryAttack");
    2.  
    3.                 if(primaryAction.GetActionDown())
    4.                 {
    5.                     psd.heldPrimary = true;
    6.                 }
    7.  
    8.                 if(psd.heldPrimary)
    9.                 {
    10.                     if (psd.primaryButtonHold <= 10.0f)
    11.                     {
    12.                         psd.primaryButtonHold += dT;
    13.                     }
    14.  
    15.                     if (psd.primaryButtonHold >= psd.chargedAttackThreshold && !entity.combat.chargedAttacking)
    16.                     {
    17.                         entity.InputChargedPrimarySingle();
    18.                     }
    19.                 }
    20.                 else
    21.                 {
    22.                     psd.primaryButtonHold = 0.0f;
    23.                 }
    24.  
    25.                 if (primaryAction.GetActionUp())
    26.                 {
    27.                     if(psd.heldPrimary)
    28.                     {
    29.                         if(psd.primaryButtonHold >= psd.chargedAttackThreshold)
    30.                         {
    31.                             entity.ReleaseChargedAttack();
    32.                         }
    33.                         else
    34.                         {
    35.                             entity.InputPrimaryCombo();
    36.                         }
    37.                     }
    38.                     psd.heldPrimary = false;
    39.                 }
    Here I have abstracted the action behind a custom wrapper. But you can get the same information if you are careful in your state checking. psd is simply a class with info. heldPrimary is when an attack is starting. primaryButtonHold is the same as your attackProg. The chargedAttackThreshold is the time at which an attack turns into charged attack instead of primary combo.

    As you can see, I use ActionDown() (pressed this frame, i.e. triggered) to tell when to start the action. Then I use ActionUp() (canceled this frame, released) to check if primaryButtonHold is >= chargedAttackThreshold. If not, then use the primary combo. Else, release the charged attack, which initiated due to moving past the threshold already. You don't need this step, you can just do nothing. Instead you want to just initiate stomp when you know buttonHold is >= .2f, etc.
     
  6. Caidek

    Caidek

    Joined:
    Jun 17, 2018
    Posts:
    5
    Good sir you are my hero! Thank you for the explanation again as it was much easier to wrap my head around it now! I was able to modify your code to get it working with my game almost perfectly. I believe it is still possible to do this more efficiently (possibly, would love input here from anyone) using a callback (which I will play around with to see if I can make it better) but it works great for now!

    Updated code
    Code (CSharp):
    1.        var inputAction = (ButtonControl)controls.Player.Attack.controls[0];
    2.  
    3.         if (inputAction.wasPressedThisFrame)
    4.         {
    5.             attackHeld = true;
    6.         }
    7.  
    8.         if (attackHeld)
    9.         {
    10.             if (attackProg <= 1.0f)
    11.             {
    12.                 attackProg += Time.deltaTime;
    13.             }
    14.  
    15.             if (attackProg >= .5f)
    16.             {
    17.                 stompAttack = true;
    18.                 attackHeld = false;
    19.             }
    20.         }
    21.         else
    22.         {
    23.             attackProg = 0.0f;
    24.         }
    25.  
    26.         if (inputAction.wasReleasedThisFrame)
    27.         {
    28.             if (attackHeld)
    29.             {
    30.                 if (attackProg >= .5f)
    31.                 {
    32.                     stompAttack = true;
    33.                     attackHeld = false;
    34.                 }
    35.                 else
    36.                 {
    37.                     basicAttack = true;
    38.                 }
    39.             }
    40.             attackHeld = false;
    41.         }
    P.S. @SomeGuy22 I checked out your Twitter and your game dev looks amazing! Going to follow for updates and look forward to seeing what your making!
     
    Last edited: Jun 1, 2020
    SomeGuy22 likes this.
  7. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    Glad I could help! Sorry that I rushed my explanation a bit, but it's great that you were able to get it working using my method. It should be possible to do it with callbacks, though it's a bit more complicated as you'd have to measure the time between releases/etc. and you'd still have to check in update if the button was held past the threshold so I don't think it's worth it in this case. Definitely try stuff out though and see what works for you. Thanks for the follow! :)