Search Unity

  1. Unity 6 Preview is now available. To find out what's new, have a look at our Unity 6 Preview blog post.
    Dismiss Notice
  2. Unity is excited to announce that we will be collaborating with TheXPlace for a summer game jam from June 13 - June 19. Learn more.
    Dismiss Notice

Bug Input cached when not immediately consumed by a State

Discussion in 'Open Projects' started by drbier, Dec 15, 2020.

  1. drbier

    drbier

    Joined:
    Oct 4, 2020
    Posts:
    10
    https://open.codecks.io/unity-open-...ched-when-not-immediately-consumed-by-a-state

    Hey all, just wanted to start the conversation about this bug since it seems important and I'm curious about what might be causing it. Not exactly sure what I'm looking for, but I'll post anything I notice/suspect here. Help would be greatly appreciated!

    And please feel free to add to/correct any of the information below.

    ---

    Short description
    Player input is cached while performing another action. The cached action is performed once the existing action finishes.

    Expected behaviour
    Some existing actions should be cancelled when the new action is initiated (e.g. if the player attempts to talk to an NPC while walking, the player should stop walking and enter dialogue), whereas some should ignore the new input (e.g. a player that is jumping should not be able to enter dialogue)

    To Reproduce
    1. Open a testing scene, press Play
    2. While walking or jumping, press the ExtraActionButton ("L" on keyboard, "Button North" on gamepad)
    Notes
    Interacting with the pot to begin "cooking" does seem to cancel both walking and jumping, but not the ExtraAction. I assume the input is still being cached though because "cooking" begins as soon as ExtraAction completes.
     
  2. Harsh-NJ

    Harsh-NJ

    Joined:
    May 1, 2020
    Posts:
    315
    Are we storing input somewhere?
     
  3. drbier

    drbier

    Joined:
    Oct 4, 2020
    Posts:
    10
    My rough, high-level understanding is that the Protagonist class stores input from the InputReader and relays it to the State Machine which decides what to do with the data. The way it is currently set up it looks like each input is read and acted upon totally independent of the others -- which seems very good for some things, but makes it hard for me to see an obvious solution to this bug. Maybe the TransitionTable or Conditions could be modified to add more constraints on when to change states? (I see a field called "Cache Result" on each Condition but I'm unclear on what it does, and setting it to false does not seem to solve the issue... might be unrelated.)

    I'm kinda shooting in the dark here. Perhaps someone more familiar with the State Machine could shed some light on this?

    EDIT: Just though of this so I thought I'd add it. It might be helpful for designers to have a visual way to modify the transition table -- similar to ShaderGraph or the Animator -- but then again that is probably more trouble than it is worth.
     

    Attached Files:

    Last edited: Dec 16, 2020
  4. Harsh-NJ

    Harsh-NJ

    Joined:
    May 1, 2020
    Posts:
    315
    We should have a look at Protagonist.cs and that Cache result script.
    Maybe some light is there.
     
    cirocontinisio likes this.
  5. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    The problem is not the state machine cached conditions (I'll explain below), but how we store the input. We currently read the input from the InputReader ScriptableObject, saving the readings on the component called "Protagonist". If the StateMachine doesn't read that input, it will stay there. For the jump, it will automatically be set to false when you release the button, but we don't have that for other actions like Talking*. So when you press the Y button, the input is set to true and when the StateMachine first checks if it's true (i.e. when it's in Idle), it will make the character start talking.

    So that's the issue. How to solve it, there are many ways. One would be for instance to hook up an event to the input so that when you release the button for that action, the bool on Protagonist is set to false. This means that if the StateMachine comes and checks one second later, the player won't begin to talk.

    This opens up a new issue: if you hold the talk button throughout the jump, and don't release, he will start talking. Maybe we can add what is called an Interaction on the button, to make sure that some actions only get triggered as a result of a Tap: https://docs.unity3d.com/Packages/com.unity.inputsystem@1.0/manual/Interactions.html#tap

    * = In addition to the above, I want to clarify that the Talk action you see implemented now (and the one that is producing the issue) will not work like this in the final version, meaning that to talk to a character you need to be in front of them. But it doesn't matter, this is an issue that will happen with other actions (for instance, attacking) so we need to fix it anyway.

    If I remember correctly, cache results basically saves the result of a certain condition check in a frame, so that if for some reason the same condition needs to be checked again, it just uses the cached result.
    Now that I'm saying (writing) it out loud it doesn't make much sense, as I don't see how this will ever happen... the only case would be when you have a Transition that contains Conditions structured like: if `isMoving` and !`isGrounded` or `isMoving`and !`isTalking`. In this case the condition isMoving would only be checked once.
    But honestly, having that checkbox on every condition now is not the best if we'll never use it.
    This is the PR, for reference. https://github.com/UnityTechnologies/open-project-1/pull/164 (and yes, I thought it was a great idea at the time :D)
     
  6. drbier

    drbier

    Joined:
    Oct 4, 2020
    Posts:
    10
    Got it, thanks for the clarification. That's what thought was happening, but I was stuck thinking about more generic/infeasible solutions. I'll experiment with the Protagonist and InputReader scripts, and avoid messing with the StateMachine!

    Using Interactions sounds promising. I'll check it out and try some stuff!

    The only problem that I anticipate with that is that we might need to experiment a lot to get the Interaction type/timings correct for each action, since actions might be linked in quick succession, like during combat for example. And this could lead to some interdependencies between those Interaction types/timings.

    So funny! Future-proofing for a future that never came...
     
  7. rcarag

    rcarag

    Joined:
    Feb 11, 2016
    Posts:
    5
    I agree, it seems when a state checks the value of extraActionInput, what they really want to know is whether or not the extraActionInput was performed during the same frame. A quick and dirty solution is to just set
    extraActionInput = false;
    in LateUpdate(), but that will get messy as more inputs are added.

    Maybe we can introduce new classes for modeling common input types. In this situation, the input should only be true if it was performed during the same frame. For example:

    class TriggerInput
    // triggers only on the same frame when the input was performed, ex. Attack, extraActionInput (for now, until it is handled via an Interaction)
    - bool IsTriggered: returns true if the input was performed this frame, false otherwise
    - OnLateUpdate(): resets the triggered status to false at the end of the current frame

    The Protagonist class will hook it up with the InputReader, and call its respective update method every frame. These input types can be expanded later if needed (ex. ChargeInput, ToggleInput, etc.)

    We also encountered a similar challenge in handling chained attack animation combos for an action brawler project. Our approach was to have AnimationEvents in mecanim to signal the start/end frames where an input could transition into the next attack animation. The mecanim UI was a pain to work with for that purpose though. :( I wonder what Chop Chop's combat system will be like?
     
    Last edited: Dec 23, 2020
  8. jlemos

    jlemos

    Joined:
    Nov 27, 2015
    Posts:
    8
    Hi for all.

    I made two little changes on State Machine.

    1st change - Walking State:
    I added a transition to Talk State when player will talk to someone and not in proccess of jump.
    01.PNG

    2nd change - Talking State:
    I added more one action to disable player walking.
    02.PNG

    I think that is expected behaviour that was reported by drbir.

    In my tests this two changes has been put the train on the track.
    :)

    Please feel free to add or remove any change that I maded.
     
  9. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Hi @jlemos. I didn't get one thing: what happens if I press the button while the character is in the air?
    I don't think you're fixing the issue this way. As far as I understood, if I press any interaction/fight button while in the air, the input is still cached, and the player will talk/attack when they land. Is it correct?
     
  10. jlemos

    jlemos

    Joined:
    Nov 27, 2015
    Posts:
    8
    Hi.

    Yes.

    The previous action is canceled immedially in only the case action "Walking".

    I wait the finish of the action "Jump" and when thats action was finish the action "Talk" is started.

    As I understand it in the previous messages is that the action "Talk" would high priority related to action "Walk".

    Was my understanding correct?

    If my understanding is correct read the message #1 or just read the message #2 otherwise:

    #1
    This change was only priorize to cancel action "Walk" when the character start action "Talk".

    #2
    Perhaps the excitement of helping and getting involved in the project may have gone much further and I missed a few points.

    If my excitement overrode reason, I apologize.

    :)
     
  11. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    It's ok :)

    But yeah, the issue is different. It's not a question of interrupting or not an action in progress. Yes, you can interrupt the walking state, but you can never interrupt a jump. That would make the character attack or talk in the air.

    So it's more of a question of not keeping (caching) the input (i.e. the button pressed) so when you land, or stop, or whatever, the second action (talk or attack) is not performed.

    So I'll close the PR for now. Read a bit of the conversation above between me and rcarag for ideas. I'll try to find a solution too.
     
  12. jlemos

    jlemos

    Joined:
    Nov 27, 2015
    Posts:
    8
    Hi again.

    I wait that character finishes jump and after that I start to action "Talk".
    I never interrupt the jump.

    I'll looking for something here too.

    Would be interesting the use of an attribute in class StateConditionSO to indicate the behavior that this action could make?

    Something like an attribute boolean "isToStartImmediately" that could be interpreted to cancel some action.
     
    Last edited: Jan 30, 2021
  13. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    I know, but that's not the problem. The action should not start delayed, it should never start.
     
  14. jlemos

    jlemos

    Joined:
    Nov 27, 2015
    Posts:
    8
    I understand your point.
     
  15. jlemos

    jlemos

    Joined:
    Nov 27, 2015
    Posts:
    8
    Hi.

    I don't know if this solve the problem, but works here.

    I just made one change on Condition isMoving settings Cache Result to FALSE, but I think that this maybe have some other problem.

    upload_2021-1-30_19-16-3.png
     
  16. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Hmm, no, Cache Result is a property present on all Actions, its use is different (I talked about it a few messages above).
     
  17. Alex311

    Alex311

    Joined:
    Dec 20, 2012
    Posts:
    6
  18. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
  19. Alex311

    Alex311

    Joined:
    Dec 20, 2012
    Posts:
    6
    maybe we can create a scriptableObject that acts as my filter state. This object would be responsible for informing the current state or Condition about the activity of the Inputs. Or create a Condition Inputs active. Or look at the previous state in a condition.
     
    Last edited: Jan 31, 2021
  20. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Hmm, seems anyway like we would be creating many more things that are not states. Seems like something we can check better in a more proper location, which would also be less CPU consuming.

    To me it seems like this comment explains it all, and it's a pretty simple way to do what we need?
     
  21. deivsky

    deivsky

    Joined:
    Nov 3, 2019
    Posts:
    87
    I've made a very simple fix using coroutines based on the earlier discussions in the issue.
    I don't remember exactly what Condition was our concern at the time, I think IsSliding because we have to calculate the angle so we wanted to avoid doing that several times per frame... But it doesn't have to be some oddly structured set of Conditions, as several Transitions can share the same Conditions, eg: from JumpDescending to both Walking and Idle have IsGrounded and IsMoving :p. Perhaps I'd leave the feature on but remove the option from the SO, as I think it's caused more confusion than anything and it's not disabled in any Condition.
     
    cirocontinisio likes this.
  22. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Yes, I was thinking it's a good solution. If it causes an issue in any of the conditions we should treat it as a bug, and we'll think about how can we implement a hack?
     
  23. deivsky

    deivsky

    Joined:
    Nov 3, 2019
    Posts:
    87
    Yeah, and I think if there's ever any conflict it's probably because the Condition is already hacky, like changing the input of the Condition from within the Condition itself so subsequent calls in the same frame get a different result??
     
  24. deivsky

    deivsky

    Joined:
    Nov 3, 2019
    Posts:
    87
    Hmm, this might not fix the issue for Talking, I think (is there any scene to test talking interactions?). The Condition to go to the Talk state checks Protagonist.extraActionInput, but I can't seem to find where it's being set to true ? Does anyone know?
     
  25. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    In any case don't think too much about talking, as most probably it's a state that will be triggered as a result of a dialogue starting (so interaction).
    Think more of the Attack. But the behaviour should be very similar.

    I don't have the code right now, but I think I rewired ExtraAction to open the inventory, so talk might not be wired right now (as it should, see above).
     
  26. deivsky

    deivsky

    Joined:
    Nov 3, 2019
    Posts:
    87
    Oh okay, then my fix should do. Attacking works as intended, no Attack-chaining and no immediate Jump->Attack, you have to press the Attack button after (or, at most, one frame before) you're in a State that can transition to Attacking (ie: Idle, Walking).

    One question would be whether we want that for jumping as well. Currently, you can start holding jump while mid-air or mid-attack and you'll start jumping once you go back to Idle/Walking. Do we want to change that?
     
    Amel-Unity and cirocontinisio like this.
  27. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Yes, I think so. It feels very unnatural to be able to queue these commands.
     
    deivsky likes this.
  28. deivsky

    deivsky

    Joined:
    Nov 3, 2019
    Posts:
    87
    Okay, I fixed queueing jump.

    The easiest way to fix it was by using an Action to set jumpInput to false when entering Idle or Walking. Then after doing that I thought it'd be best to have all the input clearing stuff together, instead of scattered around across multiple scripts, so I did that and reverted the changes I made to Protagonist.cs and InteractionManager.cs. I think it's all working properly now.
     
    Amel-Unity and cirocontinisio like this.
  29. cirocontinisio

    cirocontinisio

    Joined:
    Jun 20, 2016
    Posts:
    884
    Yes, I agree it's good to centralise the way we handle inputs. We might make some changes to how we handle these special actions like Talking and Picking up!

    Thanks!
     
    deivsky likes this.