Search Unity

New Input System : How to use the "Hold" interaction.

Discussion in 'Input System' started by Xynhay, Dec 31, 2018.

  1. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    That's where we get into unfortunate semantics of it. We can't call it "held" or "down" because it's an "action" and we don't hold actions we perform or trigger, or cancel them. I'm actually sure (maybe I'm wrong) that if there could be clear obvious names the API would already exist.

    Maybe this:
    properties: triggeredThisFrame, triggered, cancelled,
    callbacks: onTriggeredThisFrame, onTriggered, onCancelled

    "triggered" makes a lot of sense. The action is triggered and it's logically true since the first frame it was triggered until it's cancelled. And for example "performed" sounds like its already completed.

    The confusing part is that someone might take triggered for triggeredThisFrame

    "triggeredThisFrame" and "cancelled" are both as obvious as it gets.

    searching_meaningful_variable_name2.png

    Edit: onTriggeredThisFrame wouldn't work because it's a callback, and those are not called inside frames apparently. I think they should be, and the "raw" callback should be a separate callback instead.

    Not having callbacks called inside frames once per frame (before Update) creates issues by making input callbacks impossible to be used with Time.deltaTime and that's a big chunk of use cases. It does unnecessary calls every time the thing that is called only matters within the update, and its almost always the case, although there wont be many superfluous calls.The point is that calls that are not synchronized with Update loop are almost never preferable, in my opinion.

    I'm sure only the small minority of cases needs callbacks to be called as often as raw input events occur. And one example is sending input immediately in multiplayer, which is much smaller portion of use cases.
     
    Last edited: Oct 19, 2019
    MassimoFrancesco and Synpse like this.
  2. Kmsxkuse

    Kmsxkuse

    Joined:
    Feb 15, 2019
    Posts:
    306
    Is there any way to combine the two interactions "Hold" and "Pressed"?

    I would like the key to detect middle mouse down but wait until it's held for 0.5 seconds before calling performed. Then, when key is released, the action will call the function again returning Canceled as true.

    However, reading through the mile of text thus far, that seems like a pipe dream.
     
  3. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    I've tried migrating to the new Input System today and have stumbled across this very issue. I had a ton of logic in my Update() loop from the old "GetKey" or "GetButton" functions, and was trying to find a simple solution to get the "key pressed" info at any given time, without having to make a million callbacks; not all of my buttons are one-time actions and of course I could store bools to check against later but that seems like a huge hassle when it would be so much easier to just be able to somehow poll the "state" of any given action.

    Looking through the docs (API) I thought I had found what I was looking for: InputAction.phase. It seems like when a button is currently being pressed it should be in the "Performed" state. However when I tried observing the value it always came back as "Disabled", no matter how I configured my action. When I checked the docs again I realized the phase variable entry was only half-filled, some of the descriptions were missing, and on the main InputAction doc it says "TODO". Is this phase feature incomplete, or have I just not set up my InputAction correctly?



    I have also tried the other methods listed here like:

    Code (CSharp):
    1.     public void Update()
    2.     {
    3.         if (inputAction.ReadValue<float>() > 0)
    4.             /* ... */
    5.  
    But again that always came back as 0 when I debugged it. I realized maybe it was simply because I was using the "Button" type instead of the "Value" type for that action, but when I switched it to Value it was still reading 0. Maybe I just didn't set it up right, but I also thought that value was supposed to be used for input devices that have an actual value, like the current mouse position or an accelerometer. If I'm just binding it to the left mouse button will it not pass the value of 1 to the action when pressed? I realize part of the confusion is from my own misunderstanding of how interactions are supposed to behave with bindings/actions, but I also haven't seen any docs which specifically explain what each "interaction" is supposed to do, or if I even need them in the first place. Do they just modify when the action is performed or do they completely define how the binding affects the performed callback? I have found this, but it's unclear what happens when no interactions are set at all--do they default to press?

    Obviously I can always use the Keyboard.current ... isPressed, but that would eliminate the whole point of the InputSystem for me which is the fact that it generalizes actions with multiple bindings. Reading through the posts here again it seems like these features should be working, so maybe I have just set it up incorrectly. Regardless, I am really fond of the generic actions, the UI for bindings, and the amount of options for specific devices, however it appears as though ease of use in trying to mimic the old behavior per frame has declined. Delegates are nice, but for many of my use cases it would be much more useful to simply poll the state of an action--I'm aware that ReadValue is supposed to do that, but I don't know what kind of setup I need to make the value correspond to the button press.

    Furthermore, if I'm understanding correctly, even if I got ReadValue working it would only be on or off depending on the input state during the current frame, right? So if someone presses the mouse button and releases very quickly (between 2 frames) it wouldn't register...? Which makes it non-ideal for behavior similar to GetButtonDown(), because instead of accumulating the actions and clearing next frame it merely checks the current actuation. This is just a personal preference, but for organization purposes, in my uses the function that could be called by a button press could differ or have multiple results depending on the current frame--resubscribing callbacks every time is a hassle, and yes I could make a wrapper function but that would mean I would need to redo all my null-checks whereas in the Update() loop I can just leave it as one block and have all my buttons (which also require the null-checks) contained in that segment. I apologize for the long explanation, but I'm just hoping I can get some answers and potentially demonstrate why state-checking (for both performed and performed-between-frames) is much more convenient in some cases. Great work on the InputSystem so far, regardless of these cases it's shaping up to be a good replacement for the old method.
     
  4. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    Update: I've found out why my polling was showing bad results. It was because the action I was trying to check was "disabled". It turns out that every time I run the game the action I retrieve is in this "disabled" state, even though there is no indicator on the InputActionAsset UI, I guess this is just the default state...?

    When I ran
    InputAction.Enable()
    I got the correct "Waiting" response from checking InputAction.phase, and it turned to "Started" when I hit the button. So now that begs the question... why is this the default state for InputActions? Is it a bug? In my case, I did *not* generate a C# file because I'd prefer not to recompile after editing the asset, instead I just drag the asset into a variable and get my actions that way. I also have not set up any callbacks in the .performed delegate, I just straight up started polling for the phase. Could either of these facts be the reason why the action defaulted as disabled? Could it be that when no callback is assigned the InputSystem just assumes it should be disabled?

    Regardless, I believe I now have a solid workflow for mimicking the old GetButton behavior with the new Input System.

    Method for Migrating GetButton and GetButtonDown in Update() to Input System

    First, set up the InputActionAsset as normal. No interactions are needed for simple button presses, they merely add additional modifiers to when the action is considered "performed"--for example hold will simply delay the event until the button is held for the desired time. We are only reading the .phase so standard actions will be of type Button--movement (Axis) will be Value type with Vectors or floats.

    I have found it's best to have a master Input Controller object with a special tag, so that I can store the InputActionAsset in one variable and reference it from any script by accessing that special object. Otherwise, you can just make a variable of type InputActionAsset and reference that within each script. We need to enable all the actions at the start, so just do:

    Code (CSharp):
    1. void Awake()
    2. {
    3.     myInputActionAsset.Enable();
    4. }
    You can see here that it will simply enable all actions contained within the asset. Alternatively, you can do InputAction.Enable() for every action, but that will be a hassle.

    Now we can retrieve the action. If you haven't generated a C# script it is easy to find the action like this:

    Code (CSharp):
    1. InputAction primaryAction = myInputActionAsset.FindAction("PrimaryAction", true);
    I like the string search because it won't require a recompile every time you edit the asset. The bool is set to true so that it will throw an error if the action is not found. It is probably best to do this search once in Awake() and reference it later, but I'm lazy and would prefer not to clog up the namespace with variables, so for now I'm just doing it right before the next step.

    Now in Update() we can start polling the state of our action.

    "GetButton":

    Code (CSharp):
    1. if(primaryAction.phase == InputActionPhase.Started)
    2. {
    3.     //We are holding the button
    4. }
    "GetButtonDown":

    You can either use the same method above to know the first time pressed like this:

    Code (CSharp):
    1. bool didPressPrimary = false;
    2.  
    3. //.......
    4.  
    5. if(primaryAction.phase == InputActionPhase.Started && !didPressPrimary)
    6. {
    7.     //do stuff here - only happens on first frame if in Update()
    8.     didPressPrimary = true;
    9. }
    10. if(primaryAction.phase == InputActionPhase.Waiting)
    11. {
    12.     didPressPrimary = false;
    13. }
    However I'm worried this is prone to the same error I mentioned above, which is that if you press really quickly between frames it won't register, since you are merely checking the current state, not the accumulated events. Unless the .phase actually accumulates, but I don't think it does.

    To safeguard this, there's actually a really clean way posted by Rene a while ago on this thread:

    Code (CSharp):
    1. public InputAction myAction;
    2. private bool m_MyActionHasBeenPerformed;
    3. public void Awake()
    4. {
    5.     myAction.performed += ctx => m_MyActionHasBeenPerformed = true;
    6. }
    7. public void Update()
    8. {
    9.     if (m_MyActionHasBeenPerformed)
    10.     {
    11.         //...
    12.         m_MyActionHasBeenPerformed = false;
    13.     }
    14. }
    Now we are not just checking the state per frame, we are setting some variable at the exact moment of the event, and then just checking it on the next Update(). This ensures we catch it, while also not writing some extra function for the context (I didn't realize it could be just one line). The caveat is that either way we need some extra bool variable to know if the button was pressed, which is a shame.

    Conclusion

    I would like to know if the ReadValue() function accumulates the events between updates. If it does, then it would mean we can use the same as method #1 for "GetButtonDown" behavior while still safeguarding against events that are too quick. This is because Rene was right -- ReadValue() returns 1 if the button is pressed while an action is in "Button" type. Not that it would be any less code than the .performed context method, but it would at least allow the possibility of doing it without callbacks; entirely in the Update() code block, just like the old method.

    Regardless, this will definitely work for my project. I hope it helps someone in the future, and might clear up any confusions people have in this thread about getting the action's state within Update(). I would really like to see some sort of state checking function for the action itself that contains this logic, so that we don't have to use an extra bool for something which is normally so common. Perhaps a function like WasPerformedDown() which returns some internally stored bool which was set every time the Performed event happens, and then resets the bool when the user calls it? That would make it much easier to work with, but of course I'm sure there could be complications that would make it hard to implement :(
     
    NarraAtor likes this.
  5. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    i
    nputAction.phase
    Was one of the first places I looked for the functionality to get the input state. But it just didn't seem to work at all when I tried it. When I figured out the Enable() thing it was constantly returning Waiting.

    Right now I have two identical actions, one for LMB and one for RMB, one constantly returns "Waiting" but the other also returns "Started" when the button is down. The only difference between them is that one that returns "Started" has
    performed
    and
    cancelled
    callbacks assigned. However it's phase is never "Cancelled" or "Performed".

    Combined with the fact that no one told me about
    .phase
    I just assume that it's not working yet.

    Edit: The action that didn't return "Started" was press only, now it does return "Started" when no interaction is specified. So this is another (fourth?) way to get state of actions, and as all the other it seems incomplete. Does it only do GetKey()?

    Edit: When action is press and release it also only returns "Waiting". So many mysteries.
     
    Last edited: Oct 21, 2019
  6. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    Hmm it might be what I was talking about before where the action is "Disabled" by default when it has no callbacks.

    Right, because I believe the terminology of cancelled or performed is the "exact moment" when the interaction happens-- i.e. the callback event when the button is held, in which case the .phase will never actually be "performed" unless you are currently in the callback; I'm assuming at the end of the performed delegate operations it just sets to "started", same with "canceled". So if my guess is correct, in Update() you will only ever see Started or Waiting.

    Someone actually did mention .phase in this very thread on August 14. My problem was that it was showing "Disabled" by default, but after manually enabling the action it gave the expected result of Started when the button was held. See:



    I'm not sure what your specific setup is but yeah interactions only modify when the .performed callbacks happen. By default they just happen when the actuation is above the default press point, which I think is just what most people will want to know; if a button is currently pressed or not. If you set the interaction to press only, I believe it makes it so that the interaction is never "held" thus never enters the "Started" state. If you change the interaction to hold, it delays the .performed event from happening until the button is held for a certain time. It really should be more clear about what the interactions specifically do, this API is a good starting point but could really use some solid examples in the manual.

    Yes, just like the press interaction, press and release probably only trigger .performed on press and release and do not store any "held" data, thus the state will never be Started.

    In my research it seems as though there are only 2 ways to get the persistent state of your action, aside from going into each individual binding:
    1. InputAction.ReadValue() which may accumulate events per frame
    2. InputAction.phase which merely displays the current pressed state (may not represent events that happened between frames, but again that's all a guess)
    Regardless, if you're just looking to know if a button is pressed on this frame, those will work.

    Yes.

    If you want to emulate GetKeyDown() you will have to perform one of the methods I mentioned above where you manually track the state with a bool. However, I've actually realized I skipped something that Rene posted which may be an easier way:



    I'm not sure if the variable still exists and I haven't tested it, but he claims it will accumulate events for GetKeyDown()-like behavior. The question is what "frame time" is it using? Is it accumulating for Update()? Or for the internal input thread? My guess would be Update(), so you should check this out.

    Last thing, and I apologize for cluttering this thread... even if .triggered worked it would only account for GetKeyDown() and not GetKeyUp(). You'd still probably need bool tracking for that. Obviously it's a pain to do that because it's somewhat tedious if you have a lot of inputs. In addition, there's a fatal flaw with my methods from above that GetButtonDown() didn't have. Say that you have code like this:

    Code (CSharp):
    1.     public InputAction myAction;
    2.     public bool paused;
    3.     private bool m_MyActionHasBeenPerformed;
    4.     public void Awake()
    5.     {
    6.         myAction.performed += ctx => m_MyActionHasBeenPerformed = true;
    7.     }
    8.     public void Update()
    9.     {
    10.         if(!paused) {
    11.             if (m_MyActionHasBeenPerformed)
    12.             {
    13.                 //...Do stuff
    14.                 m_MyActionHasBeenPerformed = false;
    15.             }
    16.         }
    17.     }
    18.  
    If the game is currently paused your .performed event will actually accumulate and what's supposed be a "GetButtonDown() behavior" actually fires the moment you are no longer paused. The old GetKeyDown() stuff wouldn't do this because it the "pressed this frame" data was actually reset independently by the UnityEngine and never had to worry about extra conditions that could block it.

    I realized this was too complicated for me to work around so I actually just went ahead and wrote an input wrapper which automatically populates a Dictionary full of bools based on your InputActionAsset and does this logic separately. It only took around an hour to make, and it has support for GetButton(), GetButtonDown(), and GetButtonUp(). I took advantage of the fact that LateUpdate() always happens after update, and used extra bools to make sure that the event always fired for one and only one Update(); essentially allowing other scripts to poll if an action was pressed for one specific Update() loop, without having to manually track bools. If you want I can release this code for everyone to use, it's pretty simple and easy to use--only requires two lines in your code: 1. Get the action wrapper reference, 2. Check for OnButtonDown(), Up(), etc. If my logic is sound it should work 100% of the time.

    Hopefully this should clear things up. If you're interested I'll see if I can make another post with my cleaned-up code so all you'd need to do is put the wrapper on some master gameobject and reference it from another script to emulate all 3 of the old functions.
     
  7. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    I really want to disagree with this. One of the things I liked about what I saw in the new Input System was doing away with ugly polling. I hate polling. It creates tightly coupled code that doesn't need to be there.

    EDIT: I'm struggling with understand what the point of being able to define a Value->Vector2 (or anything like that) in a binding when the thing is only going to fire once. This behaviour just seems so odd to me.

    I know its been hashed out and argued in this thread which I am coming into late, but if I hold a joystick in a given direction, or I hold WASD in some configuration, I would expect my bound event to fire every frame with the Vector2 value until such time as I release.
     
    Last edited: Oct 22, 2019
  8. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    I'm kind of confused because you're asking for the exact behavior that you just condemned. One problem with a "continuous fire" method is that the system doesn't know what frame-time you are running in, i.e. should it fire every Update() or every FixedUpdate()? Or should it fire based on the internal looping speed (which I thought was supposed to be 60 times per second)? This is important because often you may need to multiply the result by the frame delta. Other complexities also made it not worthwhile:



    If you want the function to run every frame then just call it from Update() and use ReadValue() on the action you want to get your WASD from. If you only want to run the function when there is input happening then just check the value's magnitude against some number and return when it is == 0. That is one of the advantages of polling--it is much simpler to check the state of an input from your thread of choice so that you can process the data accordingly. It's much less work on the system than to try and figure out the correct interval for firing callbacks.
     
  9. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    Not sure where I contradicted myself. I said I don't want to be polling, but obviously the input system itself is still polling. Presumably it is polling the hardware at certain intervals and therefore can fire events at the same interval.

    The old polling methodology is in general a bad design because it tightly couples your code with the input. There are both slightly contrived reasons why this is bad, as well as more subtle solid reasons why it is bad.

    The more contrived reason is what if you decide to change your input handling in the future? If you have code polling through the input system scattered around your project then you have the headache of finding all those and changing them.

    The more solid reason is flexibility. Let's say for example you have a map that your player can pan around. You obviously need input for this, so you poll for the panning controls in your camera update and adjust accordingly. Now later you decide you want to add some autoscrolling based on game or camera events, or maybe something like edge scrolling.

    If your panning is instead handled in the camera simply by calling a method, or event, then other game code can call that same method.

    Polling is simpler in some cases sure, but not always the best way to handle things at the higher level of a game.

    The underlying input system is presumably polling the hardware on a set interval to read the state of the inputs. So why can't it use this same polling interval to call our hooked up methods?
     
    sand_lantern likes this.
  10. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    I would disagree because you can have all your input in separate classes. Which I do. If I have PlayerMovement class, I also have PlayerMovementInput, it is not so I can swap input or make my code reusable, (shipping the game is way more important), but it makes it more maintainable and whenever I need to work with input I know where to find it. And it keeps my classes small.

    I'm doing this (don't mind the customized input events):

    Code (CSharp):
    1. public class CameraControlInput : MonoBehaviour
    2. {
    3.     [SerializeField] private CameraControl _cameraControl;
    4.  
    5.     void Awake()
    6.     {
    7.         Input.actions.BuildMode.CameraRotationMode.AddInProgressCallback((a) => _cameraControl.Rotate(Input.actions.BuildMode.CameraRotation.ReadValue<Vector2>()), this);
    8.         Input.actions.BuildMode.CameraMovement.AddInProgressCallback((a) => _cameraControl.Move(a.ReadValue<Vector2>()), this);
    9.     }
    10. }
    But I could do the same thing using polling and sometimes it might make more sense when the input class needs to do some complex interaction between different inputs. And the point is that polling would not make the code any worse, in some cases it will make it better.

    Same. You don't even need a separate input class, just a separate method for panning. Existence of polling won't make your life more difficult unless you will be dealing with someone else's bad code and won't be in a position to tell them how to do it properly.
     
    Last edited: Oct 22, 2019
    SomeGuy22 likes this.
  11. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    You're still splitting input code all over the place. The alternative is much simpler. Inside your logic class for the MapCamera:

    MapCamera.Pan(Vector2 panAmount);

    Inside that method should be nothing to do with input. It just takes in a value saying how much to pan. How that gets called doesn't matter to it. It might be called from an input system event, or a game event with fixed values. It doesn't care.

    But because a Vector2 binding won't fire continuously, you need to write another function to do the polling yourself.

    Look I'm not saying its HARD to do, just that it is something the input system should do FOR us as it belongs there, not in our logic. The input handling should be abstracted away. That's the whole point of an input system.

    I'm simply giving feedback here on a new system recently released into preview.
     
  12. Rene-Damm

    Rene-Damm

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    There's currently no API to specifically observe releases on a per-frame basis equivalent to using InputAction.triggered for observing presses in a frame. I've made a note to have a look at that after 1.0.

    Note that it *is* possible to set up an action to observe presses and releases on a per-frame basis individually.

    Code (CSharp):
    1. InputAction m_PressOnly;
    2. InputAction m_ReleaseOnly;
    3. InputAction m_PressAndRelease;
    4.  
    5. void Awake()
    6. {
    7.     m_PressOnly = new InputAction(
    8.         binding: "<Gamepad>/buttonSouth",
    9.         interactions: "press(behavior=0)");
    10.     m_ReleaseOnly = new InputAction(
    11.         binding: "<Gamepad>/buttonSouth",
    12.         interactions: "press(behavior=1)");
    13.     m_PressAndRelease = new InputAction(
    14.         binding: "<Gamepad>/buttonSouth",
    15.         interactions: "press(behavior=2)");
    16. }
    17.  
    18. void Update()
    19. {
    20.     if (m_PressOnly.triggered)
    21.         Debug.Log("pressed this frame");
    22.     if (m_ReleaseOnly.triggered)
    23.         Debug.Log("released this frame");
    24.     if (m_PressAndRelease.triggered)
    25.         Debug.Log("pressed *or* released this frame");
    26. }
    Granted, the last one (m_PressAndRelease) has limited usefulness in combination with the InputAction.triggered API.
     
  13. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    So I just realized my feedback here was not well targeted and that's may bad.

    I was saying "The input system should do this" when what I really should have been saying was "The PlayerInput component should be doing this".

    My feedback was based on using the PlayerInput component, which I saw as the intended "hook up and go" method of working with the new Input System without a lot of extra work. In hindsight really my feedback here is that the PlayerInput component really doesn't abstract the Input System as well as I'd expect.

    I'll start a new thread for that though as it really doesn't, and didn't, belong here. Sorry!

    My original statement remains though. I really want to see things, at least optionally, move away from a polling style interface and more towards an event driven one. I don't want input specific code scattered throughout my project codebase. I want it in one file and everywhere else should just be blind events that don't know anything about input.
     
    MassimoFrancesco likes this.
  14. Rene-Damm

    Rene-Damm

    Joined:
    Sep 15, 2012
    Posts:
    1,779
    Disclaimer: The following reflects a thought process more than any fully formed plan for where to go with this.

    In short, yup, I agree @jwvanderbeck. Aside from PlayerInput itself needing more refinement (it came in pretty late in the process and didn't have as much time maturing as some other pieces of code so I think overall it still has some way to go), I think we're lacking structure on top of actions -- including for facets such as frame-bound and/or time-bound behavior.

    My thought is that actions as they are now are probably reaching the point where adding more complexity *within* them isn't going to push them in a good direction. But I'm thinking there's opportunity to build on top of them in a way that is not yet much exploited in the system as it is today.

    PlayerInput, as it is today, adds very little on top of actions so all the shortcomings that come with actions pretty much reflect 1:1 in the callbacks it offers. I'm thinking that somewhere in-between the two plenty of opportunity must lie in offering more control/structure for less work in game code.
     
  15. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    My thoughts on PlayerInput are that if it is designed to be a sort of "easy road" middleman between the InputSystem and your game, then it should be doing more to act in that role.

    Two concrete examples here would be:
    1) When I bind a UnityEvent inside PlayerInput to my objects, why do I then need to parse the CallbackContext myself when 99% of the time I'm just going to immediately do a context.ReadValue<>? It would be great to have as an option the ability to bind an event that is simply Callback(Vectoer2 value) or some such directly. That way the called object doesn't need to deal with the input system directly. It is abstracted.

    2) This thread has a lot of discussion about ways to get the desired behaviour and that's great fi you are doing your own middle man. But if you are using PlayerInput I see no reason why it can't handle that for you and then just call your bound events each frame with an input value and a deltaTime.
     
  16. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    I'd love to be able to do everything via PlayerInput as well. My only concern is refactoring potentially breaking searialization since it was a problem I had with serialized events in my own scripts and rewiring them was time-consuming. I hope it can or will be able to handle refactoring easily. Otherwise its kinda easier to to hook up events through code.
     
  17. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    Well obviously the main "choice" here is to use the out of the box PlayerInput or roll your own middleman. I'm just saying that if someone is using PlayerInput its probably because they want a "simpler" way to handle input and in this case PlayerInput should be able to better abstract "common" use cases so its easy to hook up and use. Diving into your own middleman code should only be required if you are dealing with less common use cases.
     
    transat likes this.
  18. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    Yes I agree, if people are using PlayerInput for ease of use then it should handle a repeating case like that. I do appreciate that new approaches are offered with the InputSystem, but there's still a major reason why I still think polling has it's advantages--in addition to the callbacks, not in-place of. Take this example you gave:

    What if your Pan() function relied on references, like, a lot of them? And everytime you called Pan() you needed to check if those references were null, perhaps for the target camera, transform to look at, etc. Now we have a second function called Move(), which also requires the same references and takes some input to function. Now we have split our two Inputs which require the same front-end logic, which means we have to rewrite code. Or make a function which does these checks, but of course that's not very scalable. Say you have some input functions which only required certain null-checks but not others, you'd have to make a check function for each case and call the correct ones on each input callback. The problem here is we are unable to nest logic blocks like this to allow for 2 inputs to have the same conditions as each other without duplicate code. This could be solved with polling because in Update() you would just check the state of each action in some if() branch that could be the nested result of another branch, allowing you to check at any time during your "universal" if--essentially pre-loading conditions that are shared by multiple inputs so that your result functions are shorter and more efficient (writing-wise).

    If you tried to do some other odd solution of accumulating your callbacks into one big function with those reference checks, you'd end up with another un-scalable problem of having to keep inserting new input values into your big function whenever you add a new input action. In other words, every single input callback you have must perform it's own logic checks without the help of nested groups that you would otherwise make in Update().

    That's also ignoring the fact that making the transition to InputSystem requires a re-structuring of our approach due to the having everything set up the old way (i.e. there might not be a function for every single input action), though I guess that's not as important as it gets phased out. Regardless, I'm just giving my two cents. I still like having callbacks, I just think having both would be best and most convenient for replacing the old Input. Using auto-populated dictionaries I was able to take advantage of dynamic callback assignments and create my own polling system, though I still think something like that should be added (on top of the InputActions) to make cases like this easier, and so I don't have to write my own system every time.
     
  19. jwvanderbeck

    jwvanderbeck

    Joined:
    Dec 4, 2014
    Posts:
    825
    I'm certainly not proposing that polling go away, just that there be alternatives. Being told "You can only do this with polling" isn't what I want to hear anymore than you want to hear "There is no polling anymore" :D
     
    SomeGuy22 likes this.
  20. roryo

    roryo

    Joined:
    May 21, 2009
    Posts:
    1,479
    It seems that one of the benefits of the PlayerInput component is to be able to quickly set up an input system with a minimum of coding. But if it can't implement basic WASD movement, where the expectation is that when you hold the W-key down you continuously move forward, then it seems to fail at one of the most common use cases for WASD input. Same for pushing a joystick forward and holding, where you expect to keep moving forward. In this sense, PlayerInput can't really be used without writing code, so why use it at all then and just go straight to code?

    LOL - if this sounds a tad negative, its because I started trying out the new InputSystem yesterday and I have spent hours wrestling with this before getting to this thread only to find there is no continuous option. :)
     
    Last edited: Nov 5, 2019
    logan4179 and Dmitresso like this.
  21. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    I don't think PlayerInput is really meant to be used entirely without code. I'll stress again that if you need a function to be running continuously then you can just call the function in Update() and poll the state of the action in question. In other words, you can ReadValue() in your thread of choosing without having to manage how functions get called.

    Or if you insist on using the Action's callbacks:
    1. Make it so the callback happens on both press and release (on change).
    2. When writing the callback function, make it so that it just sets some other Vector.
    3. In Update() or wherever, do your movement on the Vector that you set in the callback: adding the Rigidbody force or CharacterController.Move or whatever you have.
    As long as you don't set the Vector somewhere else, it will remain "pressed" while the button is being held down, even though the callback only happened once. This is because the Vector will be set to, say (0,1), on press. Then it will remain (0,1) until another change happens, like release. The callback sets it to (0,0).

    But honestly, aside from the benefit of not having to retrieve the action, it almost seems like more work to set up a delegate like that then to just read the value directly when you need it.
     
    Unvios22 likes this.
  22. roryo

    roryo

    Joined:
    May 21, 2009
    Posts:
    1,479
    Thanks for your reply, @SomeGuy22.

    Thanks for your suggestion of trying polling in Update. Unfortunately, I am having trouble getting it working. The callback has the correct vector value for the InputAction, but when I try to access it in Update, it is a zero vector.

    Code (CSharp):
    1.  
    2.  
    3.     PlayerControls controls;
    4.     Vector2 orbitV;
    5.  
    6.     private void Awake()
    7.     {
    8.         controls = new PlayerControls();
    9.         controls.Gameplay.Orbit.performed += ctx => orbit(ctx.ReadValue<Vector2>());
    10.     }
    11.  
    12.     private void OnEnable()
    13.     {
    14.         controls.Gameplay.Enable();
    15.     }
    16.  
    17.     private void OnDisable()
    18.     {
    19.         controls.Gameplay.Disable();
    20.     }
    21.  
    22.     private void orbit(Vector2 v)
    23.     {
    24.         Debug.Log("orbit: " + v);
    25.         orbitV = v;
    26.     }
    27.  
    28.  
    29.     void Update()
    30.     {
    31.           Debug.Log("orbitV: "+orbitV+", Orbit polled: " + controls.Gameplay.Orbit.ReadValue<Vector2>());
    32.     }
    33.  
    In this case, orbitV is set, but the polled vector is a zero vector.

    orbitV: (-1.0, 0.0), Orbit polled: (0.0, 0.0)

    Any help would be appreciated!
     
  23. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    Interesting... my first reaction is that maybe the Action "Orbit" is Disabled. I know you have controls.Gameplay.Enable() there, but maybe it's not firing correctly? Try Debug.Log on the Orbit.phase during Update. It should be one of these values. If you get "Waiting" then you know the Orbit action is ready for input. If you get "Disabled" then the action is disabled and you must call Enable() on it. What I did to make life easier was calling Enable() on my InputActionAsset, not the InputActionMap (your InputActionAsset looks to be "controls" in this case, with "Gameplay" being one of your Maps). Maybe that will make a difference? And I do it on Awake().

    Wait another thing is that you are spawning a new Instance of PlayerControls in Awake. Again I'm not sure if it makes a difference, but for the sake of experimentation I had a reference to my InputActionAsset as a public variable and dragged it from the Assets tab.

    Finally, I would double check the Interaction settings for your Action. I actually find the behavior I want is normally to just not have any Interaction adjustments and to leave it as default. Double check that Orbit is of the type "Value" and that it's Value type is indeed Vector2. Other than that, I can't think of anything else that would cause ReadValue() to fail in Update. Hope that helps!
     
  24. roryo

    roryo

    Joined:
    May 21, 2009
    Posts:
    1,479
    @SomeGuy22, you're a genius! Your last suggestion did the trick. I had a "Press and Release" trigger interaction set (which I don't need if I'm polling). Removing that led to the expected polling value. Thanks so much! Now I can continue my journey of exploring the new input system sans frustration. :)
     
    Last edited: Nov 7, 2019
    SomeGuy22 likes this.
  25. JonTerp

    JonTerp

    Joined:
    Aug 20, 2014
    Posts:
    9
    Not having native support for detecting continuous input is a large stumbling block for those coming to the new system.

    I spent a while trying to figure out the "correct" way to have the input system send an event for every frame the action is in the "performed" state. Only to find out it's purposefully not supported and I'd have to write my own hack around this limitation. It's a seemingly minor, but basic, feature but missing it makes the new system feel unfinished and not ready for prime time.
     
    X3doll, Zynigma, NotaNaN and 12 others like this.
  26. roryo

    roryo

    Joined:
    May 21, 2009
    Posts:
    1,479
    I have been having great success working with the new input system for the past few months. But this is the first time I have deleted an Action to find a bug. Though the Action is no longer in the InputActions Asset, when it creates the c# file, the deleted action is still in the JSON description. I have tried deleting the c# file and recreating it, but the deleted Action is still appears in the JSON description. This generates two errors:

    Action index out of range when processing default interaction
    UnityEngine.InputSystem.LowLevel.<>c__DisplayClass7_0:<set_onUpdate>b__0(NativeInputUpdateType, NativeInputEventBuffer*)
    UnityEngineInternal.Input.NativeInputSystem:NotifyUpdate(NativeInputUpdateType, IntPtr) (at /Users/builduser/buildslave/unity/build/Modules/Input/Private/Input.cs:120)​

    Should not get here
    UnityEngine.InputSystem.LowLevel.<>c__DisplayClass7_0:<set_onUpdate>b__0(NativeInputUpdateType, NativeInputEventBuffer*)
    UnityEngineInternal.Input.NativeInputSystem:NotifyUpdate(NativeInputUpdateType, IntPtr) (at /Users/builduser/buildslave/unity/build/Modules/Input/Private/Input.cs:120)​


    Here is what the asset view looks like:

    Screen Shot 2020-01-24 at 11.00.26 AM.png



    And here is the c# file it creates (note the OrbitRight Action is there with a 2D Vector binding using the arrow keys. This Action is not not shown in the Asset View because it was deleted in that view):

    Code (CSharp):
    1. // GENERATED AUTOMATICALLY FROM 'Assets/RoaringTide/RoTiBall/InputActions/PhysicsBallInputActions.inputactions'
    2.  
    3. using System;
    4. using System.Collections;
    5. using System.Collections.Generic;
    6. using UnityEngine.InputSystem;
    7. using UnityEngine.InputSystem.Utilities;
    8.  
    9. public class @PhysicsBallInputActions : IInputActionCollection, IDisposable
    10. {
    11.     private InputActionAsset asset;
    12.     public @PhysicsBalloonInputActions()
    13.     {
    14.         asset = InputActionAsset.FromJson(@"{
    15.    ""name"": ""PhysicsBalloonInputActions"",
    16.    ""maps"": [
    17.        {
    18.            ""name"": ""Navigation"",
    19.            ""id"": ""f39c9cb5-c052-4a83-a62c-d811e32da570"",
    20.            ""actions"": [
    21.                {
    22.                    ""name"": ""StartBurn"",
    23.                    ""type"": ""Button"",
    24.                    ""id"": ""6f92a6e2-49e3-4a07-9f98-d4bf90c555cf"",
    25.                    ""expectedControlType"": """",
    26.                    ""processors"": """",
    27.                    ""interactions"": ""Press""
    28.                },
    29.                {
    30.                    ""name"": ""StopBurn"",
    31.                    ""type"": ""Button"",
    32.                    ""id"": ""141ab0a2-d4fa-4437-8fc7-122b72086730"",
    33.                    ""expectedControlType"": """",
    34.                    ""processors"": """",
    35.                    ""interactions"": ""Press(behavior=1)""
    36.                },
    37.                {
    38.                    ""name"": ""StartVerticalVenting"",
    39.                    ""type"": ""Button"",
    40.                    ""id"": ""ff0e97a3-bf58-46a0-8090-d52bf240fa46"",
    41.                    ""expectedControlType"": """",
    42.                    ""processors"": """",
    43.                    ""interactions"": ""Press""
    44.                },
    45.                {
    46.                    ""name"": ""StopVerticalVenting"",
    47.                    ""type"": ""Button"",
    48.                    ""id"": ""801d0f4e-2f83-4766-ba30-ea914bc4f62f"",
    49.                    ""expectedControlType"": """",
    50.                    ""processors"": """",
    51.                    ""interactions"": ""Press(behavior=1)""
    52.                },
    53.                {
    54.                    ""name"": ""VentHorizontal"",
    55.                    ""type"": ""Value"",
    56.                    ""id"": ""424ee4d9-90b4-4cfd-aaa1-61807f785d45"",
    57.                    ""expectedControlType"": ""Vector2"",
    58.                    ""processors"": """",
    59.                    ""interactions"": """"
    60.                }
    61.            ],
    62.            ""bindings"": [
    63.                {
    64.                    ""name"": """",
    65.                    ""id"": ""05ae30b0-e024-45c8-9df2-614532ad431b"",
    66.                    ""path"": ""<Keyboard>/e"",
    67.                    ""interactions"": """",
    68.                    ""processors"": """",
    69.                    ""groups"": """",
    70.                    ""action"": ""StartBurn"",
    71.                    ""isComposite"": false,
    72.                    ""isPartOfComposite"": false
    73.                },
    74.                {
    75.                    ""name"": """",
    76.                    ""id"": ""6471d84b-93b0-4c26-a0c1-005685ab3e4f"",
    77.                    ""path"": ""<Keyboard>/x"",
    78.                    ""interactions"": ""Press"",
    79.                    ""processors"": """",
    80.                    ""groups"": ""Keyboard/Mouse"",
    81.                    ""action"": ""StartVerticalVenting"",
    82.                    ""isComposite"": false,
    83.                    ""isPartOfComposite"": false
    84.                },
    85.                {
    86.                    ""name"": """",
    87.                    ""id"": ""dc8faedb-078f-4580-9e22-d9456c89de40"",
    88.                    ""path"": ""<Keyboard>/c"",
    89.                    ""interactions"": """",
    90.                    ""processors"": """",
    91.                    ""groups"": """",
    92.                    ""action"": ""StartVerticalVenting"",
    93.                    ""isComposite"": false,
    94.                    ""isPartOfComposite"": false
    95.                },
    96.                {
    97.                    ""name"": ""2D Vector"",
    98.                    ""id"": ""f272cc8a-6402-45f4-af06-cfc2b6be44e4"",
    99.                    ""path"": ""2DVector"",
    100.                    ""interactions"": """",
    101.                    ""processors"": """",
    102.                    ""groups"": """",
    103.                    ""action"": ""VentHorizontal"",
    104.                    ""isComposite"": true,
    105.                    ""isPartOfComposite"": false
    106.                },
    107.                {
    108.                    ""name"": ""up"",
    109.                    ""id"": ""8c75aa73-7bea-4912-882f-04598dbea5f1"",
    110.                    ""path"": ""<Keyboard>/w"",
    111.                    ""interactions"": """",
    112.                    ""processors"": """",
    113.                    ""groups"": ""Keyboard/Mouse"",
    114.                    ""action"": ""VentHorizontal"",
    115.                    ""isComposite"": false,
    116.                    ""isPartOfComposite"": true
    117.                },
    118.                {
    119.                    ""name"": ""down"",
    120.                    ""id"": ""83145c76-55ea-428f-a187-cf3538599e66"",
    121.                    ""path"": ""<Keyboard>/s"",
    122.                    ""interactions"": """",
    123.                    ""processors"": """",
    124.                    ""groups"": ""Keyboard/Mouse"",
    125.                    ""action"": ""VentHorizontal"",
    126.                    ""isComposite"": false,
    127.                    ""isPartOfComposite"": true
    128.                },
    129.                {
    130.                    ""name"": ""left"",
    131.                    ""id"": ""d65030ae-9567-4513-8242-7316240f6963"",
    132.                    ""path"": ""<Keyboard>/a"",
    133.                    ""interactions"": """",
    134.                    ""processors"": """",
    135.                    ""groups"": ""Keyboard/Mouse"",
    136.                    ""action"": ""VentHorizontal"",
    137.                    ""isComposite"": false,
    138.                    ""isPartOfComposite"": true
    139.                },
    140.                {
    141.                    ""name"": ""right"",
    142.                    ""id"": ""ee61b2d2-0ffb-40ec-b7b4-33e23aea0954"",
    143.                    ""path"": ""<Keyboard>/d"",
    144.                    ""interactions"": """",
    145.                    ""processors"": """",
    146.                    ""groups"": ""Keyboard/Mouse"",
    147.                    ""action"": ""VentHorizontal"",
    148.                    ""isComposite"": false,
    149.                    ""isPartOfComposite"": true
    150.                },
    151.                {
    152.                    ""name"": """",
    153.                    ""id"": ""434a05f5-15b7-4031-9a07-59d9150c396e"",
    154.                    ""path"": ""<Keyboard>/e"",
    155.                    ""interactions"": """",
    156.                    ""processors"": """",
    157.                    ""groups"": ""Keyboard/Mouse"",
    158.                    ""action"": ""StopBurn"",
    159.                    ""isComposite"": false,
    160.                    ""isPartOfComposite"": false
    161.                },
    162.                {
    163.                    ""name"": """",
    164.                    ""id"": ""25f1be71-8eca-4fac-9646-30eb3aacf498"",
    165.                    ""path"": ""<Keyboard>/x"",
    166.                    ""interactions"": ""Press(behavior=1)"",
    167.                    ""processors"": """",
    168.                    ""groups"": ""Keyboard/Mouse"",
    169.                    ""action"": ""StopVerticalVenting"",
    170.                    ""isComposite"": false,
    171.                    ""isPartOfComposite"": false
    172.                },
    173.                {
    174.                    ""name"": """",
    175.                    ""id"": ""8dc88b07-a35e-44e1-8354-6e2bb98aaa92"",
    176.                    ""path"": ""<Keyboard>/c"",
    177.                    ""interactions"": """",
    178.                    ""processors"": """",
    179.                    ""groups"": """",
    180.                    ""action"": ""StopVerticalVenting"",
    181.                    ""isComposite"": false,
    182.                    ""isPartOfComposite"": false
    183.                },
    184.                {
    185.                    ""name"": ""2D Vector"",
    186.                    ""id"": ""93e0d9ce-c648-4dd0-8ea7-7f222adf3a48"",
    187.                    ""path"": ""2DVector"",
    188.                    ""interactions"": """",
    189.                    ""processors"": """",
    190.                    ""groups"": """",
    191.                    ""action"": ""OrbitRight"",
    192.                    ""isComposite"": true,
    193.                    ""isPartOfComposite"": false
    194.                },
    195.                {
    196.                    ""name"": ""up"",
    197.                    ""id"": ""029e07d1-07c9-4e08-9923-b00898ed4337"",
    198.                    ""path"": ""<Keyboard>/upArrow"",
    199.                    ""interactions"": """",
    200.                    ""processors"": """",
    201.                    ""groups"": ""KeyboardAndMouse"",
    202.                    ""action"": ""OrbitRight"",
    203.                    ""isComposite"": false,
    204.                    ""isPartOfComposite"": true
    205.                },
    206.                {
    207.                    ""name"": ""down"",
    208.                    ""id"": ""54554167-af49-45ac-9e8e-ee76f8e9ee5f"",
    209.                    ""path"": ""<Keyboard>/downArrow"",
    210.                    ""interactions"": """",
    211.                    ""processors"": """",
    212.                    ""groups"": ""KeyboardAndMouse"",
    213.                    ""action"": ""OrbitRight"",
    214.                    ""isComposite"": false,
    215.                    ""isPartOfComposite"": true
    216.                },
    217.                {
    218.                    ""name"": ""left"",
    219.                    ""id"": ""805ba56b-e4fd-45be-b3c2-c25b274352e2"",
    220.                    ""path"": ""<Keyboard>/leftArrow"",
    221.                    ""interactions"": """",
    222.                    ""processors"": """",
    223.                    ""groups"": ""KeyboardAndMouse"",
    224.                    ""action"": ""OrbitRight"",
    225.                    ""isComposite"": false,
    226.                    ""isPartOfComposite"": true
    227.                },
    228.                {
    229.                    ""name"": ""right"",
    230.                    ""id"": ""9421aefd-a893-4927-a185-0e66dd2818aa"",
    231.                    ""path"": ""<Keyboard>/rightArrow"",
    232.                    ""interactions"": """",
    233.                    ""processors"": """",
    234.                    ""groups"": ""KeyboardAndMouse"",
    235.                    ""action"": ""OrbitRight"",
    236.                    ""isComposite"": false,
    237.                    ""isPartOfComposite"": true
    238.                },
    239.                {
    240.                    ""name"": ""2D Vector"",
    241.                    ""id"": ""36cd00b0-6e3f-4799-94c3-6ffa02b8cc8e"",
    242.                    ""path"": ""2DVector"",
    243.                    ""interactions"": """",
    244.                    ""processors"": """",
    245.                    ""groups"": """",
    246.                    ""action"": ""OrbitRight"",
    247.                    ""isComposite"": true,
    248.                    ""isPartOfComposite"": false
    249.                },
    250.                {
    251.                    ""name"": ""up"",
    252.                    ""id"": ""e7d6b8b9-4177-480e-9f9c-2a822b337c05"",
    253.                    ""path"": ""<Keyboard>/upArrow"",
    254.                    ""interactions"": """",
    255.                    ""processors"": """",
    256.                    ""groups"": ""KeyboardAndMouse"",
    257.                    ""action"": ""OrbitRight"",
    258.                    ""isComposite"": false,
    259.                    ""isPartOfComposite"": true
    260.                },
    261.                {
    262.                    ""name"": ""down"",
    263.                    ""id"": ""cec75b55-7fc6-4348-b6ac-4206d854babc"",
    264.                    ""path"": ""<Keyboard>/downArrow"",
    265.                    ""interactions"": """",
    266.                    ""processors"": """",
    267.                    ""groups"": ""KeyboardAndMouse"",
    268.                    ""action"": ""OrbitRight"",
    269.                    ""isComposite"": false,
    270.                    ""isPartOfComposite"": true
    271.                },
    272.                {
    273.                    ""name"": ""left"",
    274.                    ""id"": ""116194d7-df13-497e-a93d-36afe0824044"",
    275.                    ""path"": ""<Keyboard>/leftArrow"",
    276.                    ""interactions"": """",
    277.                    ""processors"": """",
    278.                    ""groups"": ""KeyboardAndMouse"",
    279.                    ""action"": ""OrbitRight"",
    280.                    ""isComposite"": false,
    281.                    ""isPartOfComposite"": true
    282.                },
    283.                {
    284.                    ""name"": ""right"",
    285.                    ""id"": ""0e60b625-5e0b-4989-8cf3-ee5b4b253d70"",
    286.                    ""path"": ""<Keyboard>/rightArrow"",
    287.                    ""interactions"": """",
    288.                    ""processors"": """",
    289.                    ""groups"": ""KeyboardAndMouse"",
    290.                    ""action"": ""OrbitRight"",
    291.                    ""isComposite"": false,
    292.                    ""isPartOfComposite"": true
    293.                }
    294.            ]
    295.        }
    296.    ],
    297.    ""controlSchemes"": [
    298.        {
    299.            ""name"": ""Keyboard/Mouse"",
    300.            ""bindingGroup"": ""Keyboard/Mouse"",
    301.            ""devices"": [
    302.                {
    303.                    ""devicePath"": ""<Keyboard>"",
    304.                    ""isOptional"": false,
    305.                    ""isOR"": false
    306.                },
    307.                {
    308.                    ""devicePath"": ""<Mouse>"",
    309.                    ""isOptional"": false,
    310.                    ""isOR"": false
    311.                }
    312.            ]
    313.        },
    314.        {
    315.            ""name"": ""Gamepad"",
    316.            ""bindingGroup"": ""Gamepad"",
    317.            ""devices"": []
    318.        }
    319.    ]
    320. }");
    321.         // Navigation
    322.         m_Navigation = asset.FindActionMap("Navigation", throwIfNotFound: true);
    323.         m_Navigation_StartBurn = m_Navigation.FindAction("StartBurn", throwIfNotFound: true);
    324.         m_Navigation_StopBurn = m_Navigation.FindAction("StopBurn", throwIfNotFound: true);
    325.         m_Navigation_StartVerticalVenting = m_Navigation.FindAction("StartVerticalVenting", throwIfNotFound: true);
    326.         m_Navigation_StopVerticalVenting = m_Navigation.FindAction("StopVerticalVenting", throwIfNotFound: true);
    327.         m_Navigation_VentHorizontal = m_Navigation.FindAction("VentHorizontal", throwIfNotFound: true);
    328.     }
    329.  
    330.     public void Dispose()
    331.     {
    332.         UnityEngine.Object.Destroy(asset);
    333.     }
    334.  
    335.     public InputBinding? bindingMask
    336.     {
    337.         get => asset.bindingMask;
    338.         set => asset.bindingMask = value;
    339.     }
    340.  
    341.     public ReadOnlyArray<InputDevice>? devices
    342.     {
    343.         get => asset.devices;
    344.         set => asset.devices = value;
    345.     }
    346.  
    347.     public ReadOnlyArray<InputControlScheme> controlSchemes => asset.controlSchemes;
    348.  
    349.     public bool Contains(InputAction action)
    350.     {
    351.         return asset.Contains(action);
    352.     }
    353.  
    354.     public IEnumerator<InputAction> GetEnumerator()
    355.     {
    356.         return asset.GetEnumerator();
    357.     }
    358.  
    359.     IEnumerator IEnumerable.GetEnumerator()
    360.     {
    361.         return GetEnumerator();
    362.     }
    363.  
    364.     public void Enable()
    365.     {
    366.         asset.Enable();
    367.     }
    368.  
    369.     public void Disable()
    370.     {
    371.         asset.Disable();
    372.     }
    373.  
    374.     // Navigation
    375.     private readonly InputActionMap m_Navigation;
    376.     private INavigationActions m_NavigationActionsCallbackInterface;
    377.     private readonly InputAction m_Navigation_StartBurn;
    378.     private readonly InputAction m_Navigation_StopBurn;
    379.     private readonly InputAction m_Navigation_StartVerticalVenting;
    380.     private readonly InputAction m_Navigation_StopVerticalVenting;
    381.     private readonly InputAction m_Navigation_VentHorizontal;
    382.     public struct NavigationActions
    383.     {
    384.         private @PhysicsBalloonInputActions m_Wrapper;
    385.         public NavigationActions(@PhysicsBalloonInputActions wrapper) { m_Wrapper = wrapper; }
    386.         public InputAction @StartBurn => m_Wrapper.m_Navigation_StartBurn;
    387.         public InputAction @StopBurn => m_Wrapper.m_Navigation_StopBurn;
    388.         public InputAction @StartVerticalVenting => m_Wrapper.m_Navigation_StartVerticalVenting;
    389.         public InputAction @StopVerticalVenting => m_Wrapper.m_Navigation_StopVerticalVenting;
    390.         public InputAction @VentHorizontal => m_Wrapper.m_Navigation_VentHorizontal;
    391.         public InputActionMap Get() { return m_Wrapper.m_Navigation; }
    392.         public void Enable() { Get().Enable(); }
    393.         public void Disable() { Get().Disable(); }
    394.         public bool enabled => Get().enabled;
    395.         public static implicit operator InputActionMap(NavigationActions set) { return set.Get(); }
    396.         public void SetCallbacks(INavigationActions instance)
    397.         {
    398.             if (m_Wrapper.m_NavigationActionsCallbackInterface != null)
    399.             {
    400.                 @StartBurn.started -= m_Wrapper.m_NavigationActionsCallbackInterface.OnStartBurn;
    401.                 @StartBurn.performed -= m_Wrapper.m_NavigationActionsCallbackInterface.OnStartBurn;
    402.                 @StartBurn.canceled -= m_Wrapper.m_NavigationActionsCallbackInterface.OnStartBurn;
    403.                 @StopBurn.started -= m_Wrapper.m_NavigationActionsCallbackInterface.OnStopBurn;
    404.                 @StopBurn.performed -= m_Wrapper.m_NavigationActionsCallbackInterface.OnStopBurn;
    405.                 @StopBurn.canceled -= m_Wrapper.m_NavigationActionsCallbackInterface.OnStopBurn;
    406.                 @StartVerticalVenting.started -= m_Wrapper.m_NavigationActionsCallbackInterface.OnStartVerticalVenting;
    407.                 @StartVerticalVenting.performed -= m_Wrapper.m_NavigationActionsCallbackInterface.OnStartVerticalVenting;
    408.                 @StartVerticalVenting.canceled -= m_Wrapper.m_NavigationActionsCallbackInterface.OnStartVerticalVenting;
    409.                 @StopVerticalVenting.started -= m_Wrapper.m_NavigationActionsCallbackInterface.OnStopVerticalVenting;
    410.                 @StopVerticalVenting.performed -= m_Wrapper.m_NavigationActionsCallbackInterface.OnStopVerticalVenting;
    411.                 @StopVerticalVenting.canceled -= m_Wrapper.m_NavigationActionsCallbackInterface.OnStopVerticalVenting;
    412.                 @VentHorizontal.started -= m_Wrapper.m_NavigationActionsCallbackInterface.OnVentHorizontal;
    413.                 @VentHorizontal.performed -= m_Wrapper.m_NavigationActionsCallbackInterface.OnVentHorizontal;
    414.                 @VentHorizontal.canceled -= m_Wrapper.m_NavigationActionsCallbackInterface.OnVentHorizontal;
    415.             }
    416.             m_Wrapper.m_NavigationActionsCallbackInterface = instance;
    417.             if (instance != null)
    418.             {
    419.                 @StartBurn.started += instance.OnStartBurn;
    420.                 @StartBurn.performed += instance.OnStartBurn;
    421.                 @StartBurn.canceled += instance.OnStartBurn;
    422.                 @StopBurn.started += instance.OnStopBurn;
    423.                 @StopBurn.performed += instance.OnStopBurn;
    424.                 @StopBurn.canceled += instance.OnStopBurn;
    425.                 @StartVerticalVenting.started += instance.OnStartVerticalVenting;
    426.                 @StartVerticalVenting.performed += instance.OnStartVerticalVenting;
    427.                 @StartVerticalVenting.canceled += instance.OnStartVerticalVenting;
    428.                 @StopVerticalVenting.started += instance.OnStopVerticalVenting;
    429.                 @StopVerticalVenting.performed += instance.OnStopVerticalVenting;
    430.                 @StopVerticalVenting.canceled += instance.OnStopVerticalVenting;
    431.                 @VentHorizontal.started += instance.OnVentHorizontal;
    432.                 @VentHorizontal.performed += instance.OnVentHorizontal;
    433.                 @VentHorizontal.canceled += instance.OnVentHorizontal;
    434.             }
    435.         }
    436.     }
    437.     public NavigationActions @Navigation => new NavigationActions(this);
    438.     private int m_KeyboardMouseSchemeIndex = -1;
    439.     public InputControlScheme KeyboardMouseScheme
    440.     {
    441.         get
    442.         {
    443.             if (m_KeyboardMouseSchemeIndex == -1) m_KeyboardMouseSchemeIndex = asset.FindControlSchemeIndex("Keyboard/Mouse");
    444.             return asset.controlSchemes[m_KeyboardMouseSchemeIndex];
    445.         }
    446.     }
    447.     private int m_GamepadSchemeIndex = -1;
    448.     public InputControlScheme GamepadScheme
    449.     {
    450.         get
    451.         {
    452.             if (m_GamepadSchemeIndex == -1) m_GamepadSchemeIndex = asset.FindControlSchemeIndex("Gamepad");
    453.             return asset.controlSchemes[m_GamepadSchemeIndex];
    454.         }
    455.     }
    456.     public interface INavigationActions
    457.     {
    458.         void OnStartBurn(InputAction.CallbackContext context);
    459.         void OnStopBurn(InputAction.CallbackContext context);
    460.         void OnStartVerticalVenting(InputAction.CallbackContext context);
    461.         void OnStopVerticalVenting(InputAction.CallbackContext context);
    462.         void OnVentHorizontal(InputAction.CallbackContext context);
    463.     }
    464. }
    465.  


    Is there a way to flush the InputActions Asset? Right now after every edit, I am going into the c# file and manually deleting the deleted actions from the JSON description.
     
  27. Pixsaoul

    Pixsaoul

    Joined:
    Feb 10, 2014
    Posts:
    14
    Hello,
    as many people here, I ma currently trying to get inputs to work with continuous generation of values.
    (InputSystem 1.0.0 preview 5, with Unity 2019.3.3f1)

    In my current setup, I am trying to get a Vector2 to be sent to my business layer with mouse movement OR arrow keys. To do so I created 2 binding under the same action.
    It works well for the mouse movement with delta, however I have the behavior everyone experienced with arrow key: only 2 calls per input.

    I understand this behavior and why it was done like this.

    I am trying to use my action map by only implementing the generated C# interfaces to have a beautiful clear code :D
    Is there a recommended way to do so ? Am I "forced" to implement some other input listening to add the "continuous" behavior ?

    Sorry if a definitive answer was given here but I didn't see the interface approach taken by anyone.

    Thank you for your help and for this package, seems awesome !
     
  28. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    It's been a while but I believe there were several methods discussed here. Read my posts from Oct 20th. There I talked about a way to poll the current state of any input action from the Update() method:

    The logic still applies to your generated C# scripts. This is why I believe it can be confusing for people to use the C# generation instead of reading from the asset itself, because it's not clear what you're actually working with. The "actions" you get by generating a C# file all have the same functions and properties as the InputAction class. So in order to detect if a button is held down you can take advantage of everything the InputAction offers.

    The case I just quoted from above takes InputAction.phase and compares it to the enumerator listed in the docs. So if InputAction.phase == InputActionPhase.Started, then you know that the button is being held down. It is easy to check for this within Update(), and then just call whatever "continuous" function you need from there. You can even pass in the InputAction as a param if you need more info on the press event. Doing it through this method only requires 2 extra lines of code per action, and you can use whatever thread you want to perform the continuous check.

    Another method which was discussed and will get the job done is to just use ReadValue() in Update(). Turns out it returns > 0 on the "button" type when a button is pressed, allowing you to use it to check if something is held down. Again, you can poll this wherever and use it to call your own function.

    You're not forced to add your implementation, as I just mentioned here there is a pretty clean solution (2 in fact) to just poll the current state of any action. Granted it won't call your callback function continuously as there is no delegate support for that, but it's not hard to do this yourself and as mentioned it's only 2 lines per action to get this kind of behavior.

    If you are interested in a custom implementation of some more polling features, I actually released a script on the forums which allows you to do everything from Update(), just like the old method. You can find it here. I know this isn't what you're looking for since you are probably interested in delegates. But doing work in Update() is sometimes crucial and it can be helpful to know if a button is pressed down without having to set up a delegate and manage the logic somewhere else. My code lets you poll held presses with GetAction(), as well as GetActionDown() and GetActionUp(), encapsulating a ton of logic so that you can check the state from anywhere.

    As for why there isn't continuous support--I have one guess. I believe the InputSystem uses an internal thread/clock rate for checking inputs. If this matches the framerate then sure you can call a function continuously as a button is held, and the user dev can expect the function to be called once per frame. But what if you want the continuous logic to be done on the Fixed Step time instead? Well, you either have to mess with the input system check rate, or it has to subscribe to specific threads based on your parameters. By manually polling the action yourself, you are able to use whatever thread you want to call the function, thus ensuring that you aren't wasting resources by calling it every frame when you don't need that level of precision. I believe Rene mentioned something like this at some point but I forget where.
     
  29. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    I also want to add something here. In my understanding mouse delta is it's own beast and should be treated as it's own action. Why? Bunny83 summarizes it well here:



    Mouse delta accounts for position change per frame, whereas the arrow keys are a constant value and must take into account the deltaTime if you are moving something over time, which is the most common use case. In my game I have "LookDelta" as one action and "LookAxis" as another, allowing users to use both mouse and joystick for moving the camera. Then for the LookAxis action, I multiply by deltaTime because I am expecting -1 to 1 range, then * by camera speed. LookDelta is already taking into account the movement per frame, so I just multiply by my camera speed and add the results of both actions together. You will need a similar approach for your project.

    The solution: first split your mouse delta action from your arrow keys, they must be handled separately if you are moving something over time with them. Then do what I've explained in my above post for the arrow key action--poll in Update() and call whatever function you want from there. But be sure to account for Time.deltaTime in your arrow key action only. This should give you the behavior that you want. If you are having trouble syncing mouse movement to arrow keys, you need to multiply them by different speeds because their values will likely be very different. I think in my game for mouse I multiply by some small value like .2f to keep it in line with the axis.

    Worth noting I believe the InputSystem has other options for mouse detection which don't involve delta and will give you -1 to 1 scale like a joystick. But I haven't played around with them. I assume in this case you actually mean delta, and not one of those other options. Good luck!
     
  30. Pixsaoul

    Pixsaoul

    Joined:
    Feb 10, 2014
    Posts:
    14
    Thanks a lot for your insights ! Yeah I wanted to build something only based on callbacks (more precisely with Observable through Reactiv) to avoid active listening but it seemed to be not possible. In the end I did exactly what you proposed :
    Use the update method to check for the inputs. I still use the interface implementation part for my buttons but not for my axis.

    Another issue about polling was that I was not sure when to do it, as I have part of my inputs that are consumed by camera movement, and part of it consummed to apply forces to objects. To uncouple the issue, I poll only at update in my InputService, and then whoever consummes it is responsible of when they do. This way my dynamic object has forces applied on FixedUpdate. This would have happened with the old system too ;)

    As for the mouse delta, I added the same multiplier and it seems to works wonder.

    I didn't find that "joystick" like behavior for the mouse but I would be really interested !
    I have an overlay with UI joysticks (for mobile device) so this behavior would help me maintain both implementation in parallel (mouse and UI).
     
    SomeGuy22 likes this.
  31. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    Yes, that sounds like a good way to approach it, having a layer of abstraction to distribute your axis inputs is for the best. Though as long as you don't tamper with the actions I don't think it matters if you poll from multiple places or in different orders, but I don't know your specific so I'm not sure if it would actually have an affect.

    My bad, it seems like the mouse HID inherits from Pointer, which only has delta. I must've confused what I said before with the position option having not really looked through the docs. Though it is possible to create axis-like behavior if you take the difference of positions since last frame and normalize it, giving you a direction to use on a scale of 1 to -1 like a joystick. However it would be pointless, no pun intended, as delta is already supposed to measure this minus the normalize part. And delta already works fine assuming you account for the frame dependency as I said. So yeah it's a bit unfortunate that you need separate behavior for mouse vs. joystick but it's always been like that in the old system so at least here it's more generic and versatile. But you seem to have it down already and I'm happy you found a good solution :)
     
  32. Boolai

    Boolai

    Joined:
    Nov 13, 2014
    Posts:
    9
    Ok This worked for me. In the input settings set your action to button then under interactions set it to press with press and release parameter on it. Then in your code give it a bool such as canShoot. Then set your callback on the input like this.
    Code (CSharp):
    1.  
    2.  
    3. private void OnEnable(){
    4.  
    5. canShoot =  false;
    6.             controls.Player.Fire.started += context =>
    7.             {
    8.                 canShoot = true;
    9.                 Debug.LogError("Hold started " + context.interaction.ToString());
    10.             };
    11.             controls.Player.Fire.performed += context =>
    12.             {
    13.                 canShoot= true;
    14.                 Debug.LogError("Performing" + context.interaction.ToString());
    15.  
    16.             };
    17.             controls.Player.Fire.canceled += context =>
    18.             {
    19.                 canShoot = false;
    20.                 Debug.LogError("Canceled");
    21.             };
    22. }
    23.  
    24. private void Update() {
    25.          
    26.             if(canShoot)
    27.             {
    28.                 this.Shoot();
    29.             }
    30. }
    31.  
     
  33. Bunzaga

    Bunzaga

    Joined:
    Jan 9, 2009
    Posts:
    202
    Trying to determine if a button is pressed, to get the pointer position at the time of the press. Doesn't look possible, since pressed is a button type input, and position is a vector 2 type input.

    So it looks like I need to have 4 actions. Pressed, Released, Move, Tap.

    1. In Update poll, and store the x,y values (see Edit 2).
    2. If button pressed, set a bool to true.
    3. If button released, set the bool to false.
    4. If tapped do something on tap.
    5. In Update if pressed is true, use stored x,y values.

    Making this overly complicated guys.... I do like the new Control Schemes and Device options though, but not digging this new way of implementing the pointer events independent from each other.

    EDIT: Actually even this doesn't work correctly, since Move doesn't work when I pick up my pointer and move it to another place, and put it down, it ONLY works on Move. So what is the way to get the x,y on a touch, button down event?

    EDIT2: Found it: inputVector = playerInput.PlayerControls.Move.ReadValue<Vector2>(); So I have to pull that on update, check if pressed, then use it.
     
    Last edited: May 20, 2020
  34. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    Hi. I'm just checking up on this issue. Can someone please tell me if anything changed, or do I still need to use my custom wrapper to get a decent consistent API? (There was no consistent way to get all three types of basic interactions GetKey() GetKeyDown() GetKeyUp(), you had to use different methods for different interactions) Since the developers were adamant about the fact that the API doesn't have to be simple and consistent, I suspect that it remained the same.
     
  35. PixelLifetime

    PixelLifetime

    Joined:
    Mar 30, 2017
    Posts:
    90
    It's pretty consistent, instead of those 3 you just have `started` `canceled` `performed`. The only thing you have to modify to work properly is `performed` as `continuous` toggle was removed. Even though, I still don't understand what is the correct way to do this `continuous` ourselves for it to be thread safe and not work on the main thread. Hope when it comes out of preview if it still is, we will have some proper tutorials about this, and not some basic topics like - "just use PlayerInput". I have worked with this API quite a while ago, like 2-3 months, maybe, something has changed since then.
     
  36. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    I don't believe anything has changed, but I haven't been keeping up with the new releases. As mentioned in the post above there are consistent callback methods for both Action Pressed and Action Released--performed and canceled. If you want to know if something is pressed "currently" then simply check the ReadValue() and if it's greater than your actuation amount then you know it's pressed. From there you can call your callback function every frame if you so desire.

    If you prefer to poll the state to check for up and down, there are other various methods mentioned here. If you're concerned for consistency, the work is already done for you. I've posted this script which emulates KeyUp, KeyDown, and held for actions. All you have to do is acquire an instance of MInputState and do the dictionary search GetInputActionWrapper() and then you have a data type which has the 3 functions, GetActionDown(), GetActionUp(), and GetAction(), all behaving as you'd expect from the old system. It's true that making a custom wrapper is more work, but this only had to be done once and now everyone can reuse it forever, only a 2 minute setup stage is required to implement. This should be good enough for most cases until they are able to add something more direct, but there are already many options for polling so I wouldn't deem it necessary at this point.

    Just curious, why would you not want it to work on the main thread? Doesn't all the game logic have to happen there, since it is managed by UnityEngine? If you polled for inputs in a custom System thread you wouldn't be able to interact with any of Unity's objects right?

    If you're talking about running continuous in FixedUpdate() or something, you can check out the script I posted in the paragraph above. It uses callbacks to toggle a bool per action automatically, and flips them off after the frame is done. Right now it takes advantage of Update() and LateUpdate() happening afterwards to toggle the bool. But it wouldn't take much effort to rig it into FixedUpdate() and then find some way to switch the bools off once all FixedUpdate() calls have run. That way the "continuous" logic persists until the next fixed time step. And this can be done for any thread/timing of your choosing.

    Otherwise, you can always just poll ReadValue() in any thread you want to check if a button is pressed. However I'm still not sure if accumulation occurs for ReadValue(), I believe it does. But under what time scale does it occur? This is important if a button is pressed and released within a single frame, and you want to detect that a change happened. I believe Update() checks are safe, but I'm not sure if it's smart enough to accumulate from other threads as well. You'd have to ask Rene or something for more info. But regardless I think the method I described above would be more secure, and this issue wouldn't happen using a custom wrapper since you are in control of when the bool is changed.

    I agree, I think there should be more examples of building systems using the InputActionAsset directly. I've recently discovered a way to retrieve which controller pressed a certain button, which means if you're smart you can actually build a multiplayer system using a single InputActionMap. This kind of direct access knowledge is extremely useful, and would help in understanding the API across the board.
     
  37. PixelLifetime

    PixelLifetime

    Joined:
    Mar 30, 2017
    Posts:
    90
    Whole NewInputSystem is event based and works on a different thread or I am misinformed. Would make sense for it to not run in the main thread as well. Also, it allows for input to be independent of the frame-rate which is very important in some situations. Especially if you want to develop mobile apps with things like UIWidgets. Input should be recorded at any time because it improves the responsiveness of your application/game. If you press button 2 times for something to work when you have 20 frames per second, it makes it very unpleasant I assume, I don't know if this is technically viable thing to assume, but I believe so from previous problems that people had with old input system.
     
  38. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    I believe you are right, however as far as I know polling options are all thread-safe in the sense that you won't mess up anything within the action by merely checking their states. As for the desync between Input System and main thread, this is why it'd be helpful to know what polling options accumulate per Unity frame and what options are instantaneous. I have guessed before that ReadValue() accumulates to update, so that if you press a button and release before the next frame, you will still get a value of 1 until the frame has finished.

    Right, that's why callbacks exist I believe. I'm not sure how viable this is but you can maybe use the system's "real time" to measure the time since last press if you need to implement a double press event at low framerates. A polling solution like that is not possible from any thread though, unless you hooked up to the Input System's internal refresh or if you did some hack magic with OnGUI() or something that is run multiple times. So I understand what you mean here, but I don't believe there's any way to run code in Unity faster than the regular update, unless you maybe make a system thread (?) but then you wouldn't be able to connect to anything within Unity. In other words, you'd have to use callbacks.

    For "continuous" actions like an analogue stick or held button press, it doesn't make sense that you'd need precision beyond the main thread; simply because moving your character or detecting/responding to a held action can only happen at the granularity of Unity's Update anyways. i.e. Even if you were able to detect a held button at a certain allotted time before the next update, changes on screen are not going to happen until Update() regardless. The only scenario I could think of where this might matter is where you have to detect a held button press and then another quick press in rapid succession, which results in a different outcome than just pressing twice, all of that happening before the next update call. In that case, you'd need to use callbacks with the "canceled" event. But it's such an extreme scenario that it's not worth worrying about imo.

    In most cases I think checking for inputs at the frequency of main thread is more than enough, and might even be more consistent if you consider that people may react to the slower framerates and expect inputs to slow down as well. Not really sure if that applies to everything but it's just food for thought. Worth noting again that with a custom wrapper (with extendable data you can track yourself, such as number of times pressed) that uses callbacks internally you can achieve Update() polling with results that are accurate to the Input System's own thread at any moment in time.
     
  39. Pixelch3f

    Pixelch3f

    Joined:
    Sep 2, 2017
    Posts:
    12
    Code (CSharp):
    1. bool bWallGrabPressed;
    2. bool bWallGrabHeldDown;
    3. bool bWallGrabReleased;
    4.  
    5. void Start()
    6. {
    7. controls.Player.WallGrab.canceled += context => bWallGrabReleased = true;
    8. }
    9.  
    10. void Update()
    11. {
    12. bWallGrabPressed = controls.Player.WallGrab.triggered;
    13.         if (bWallGrabPressed) bWallGrabHeldDown = true;
    14.         if (bWallGrabReleased) bWallGrabHeldDown = false;
    15. }
    I'm pretty new and had a similar issue.. took me a while to get my head around the new system. But the above works for me, not very elegant but it works.

    If there's a better way to do it I'd like to know! :)
     
  40. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    bWallGrabReleased is never set back to false after the canceled event sets it to true. If triggered works as expected your code will force bWallGrabHeldDown to be false forever after you've canceled the action for the first time.

    Instead, you can just do
     bWallGrabHeldDown = (WallGrab.ReadValue<float>() > .5f)
    . ReadValue returns 1 for button types when you are holding the button down, so there's no need to toggle it on or off in this case. Also, if you want released to be active for 1 frame only, then you should simply set it back to false after you've done all your polling checks in Update(). Hope that helps!
     
    Pixelch3f likes this.
  41. Pixelch3f

    Pixelch3f

    Joined:
    Sep 2, 2017
    Posts:
    12
    I forgot to mention that in my code the bWallGrabReleased bool was set back to false after the action took place. But your method is better, I'm using that now, thanks!

    Also, I am using a similar method for my jump button (without the "HeldDown" bool), I tried implementing the ReadValue way that you mentioned with jump too, but that doesn't work exactly how I want it to because you can just hold down the jump button and jump continuously. I only want the button to be triggered once when pressed and once when released. I guess in that case I should stick to using .triggered and .cancelled, respectively?

    Code (CSharp):
    1. bool bJumpPressed;
    2. bool bJumpReleased;
    3. void Start()
    4. {
    5. controls.Player.Jump.canceled += context => bJumpReleased = true;
    6. }
    7. void Update()
    8. {
    9. bJumpPressed = controls.Player.Jump.triggered;
    10.  
    11. if (bJumpPressed)
    12. {
    13. // Jump function
    14. bJumpPressed = false;
    15. }
    16. if (bJumpReleased)
    17. {
    18. // Do something
    19. bJumpReleased = false;
    20. }
    21. }
    Like that, pretty much.
     
    Last edited: May 31, 2020
  42. SomeGuy22

    SomeGuy22

    Joined:
    Jun 3, 2011
    Posts:
    722
    Yes and no. That all comes down to how your other code handles the input. If you've ever seen the old CharacterMotor script that used to be a part of Unity example assets, it used a single "inputJump" bool which you'd feed in the held action directly. Then the script itself would interpret if you've pressed it for the first time or kept it held, and perform jump accordingly. This was done so that longer holds of the jump button could result in more height. Just an example to show there are many different ways of inputting and acting on jumps.

    This'll work, I'm just not sure if you need "held"-based mechanics in the first place so it's hard to say what the code is supposed to do on release. But yeah you could use the release action to trigger something like cutting off max height from the current jump or stop floating or something like that. Or if you only care about jumping when the button is pressed once you don't even need the released bool, what you have above is fine. The possibilities for interpreting input are endless!
     
  43. Caidek

    Caidek

    Joined:
    Jun 17, 2018
    Posts:
    5
  44. Pixelch3f

    Pixelch3f

    Joined:
    Sep 2, 2017
    Posts:
    12
    I haven't seen the CharacterMotor script, but it sounds like it does something very similar to what I have. It does what you said, if the jump key is released early, the height is reduced, that's why I have the jumpbutton released bool.
     
  45. JoaoSantos

    JoaoSantos

    Joined:
    Mar 31, 2014
    Posts:
    20
    Using part of information that you folks put here, I made something that helping me in my personal project

    Code (CSharp):
    1. using UnityEngine;
    2.  
    3. public class CameraTargetMovement : MonoBehaviour
    4. {
    5.     [SerializeField]
    6.     [Range(0.1f, 1f)]
    7.     private float velocity;
    8.  
    9.     private InputCamera controls;
    10.     private bool moving;
    11.     private Vector3 lastPosition;
    12.  
    13.     private const float velocityDivisionFactor = 10f;
    14.  
    15.     private void Awake()
    16.     {
    17.         this.controls = new InputCamera();
    18.  
    19.         this.controls.Camera.StartMovement.performed += ctx => SetMoving(true);
    20.         this.controls.Camera.StartMovement.canceled += ctx => SetMoving(false);
    21.  
    22.         this.controls.Camera.Movement.performed += ctx => TryMove(ctx.ReadValue<Vector2>());
    23.     }
    24.  
    25.     private void OnDestroy()
    26.     {
    27.         this.controls.Camera.StartMovement.performed -= ctx => SetMoving(true);
    28.         this.controls.Camera.StartMovement.canceled -= ctx => SetMoving(false);
    29.  
    30.         this.controls.Camera.Movement.performed -= ctx => TryMove(ctx.ReadValue<Vector2>());
    31.     }
    32.  
    33.     private void OnEnable()
    34.     {
    35.         this.controls.Enable();
    36.     }
    37.  
    38.     private void OnDisable()
    39.     {
    40.         this.controls.Disable();
    41.     }
    42.  
    43.     private void PrepareMovement()
    44.     {
    45.         var screenPoint = this.controls.Camera.Movement.ReadValue<Vector2>();
    46.         Vector3 worldPoint = screenPoint.ScreenToWorldPoint();
    47.  
    48.         this.lastPosition = worldPoint;
    49.     }
    50.  
    51.     private void SetMoving(bool isMoving)
    52.     {
    53.         if (isMoving) PrepareMovement();
    54.         this.moving = isMoving;
    55.     }
    56.  
    57.     private void TryMove(Vector2 position)
    58.     {
    59.         if (!this.moving) return;
    60.  
    61.         Vector3 currentPoint = position.ScreenToWorldPoint();
    62.  
    63.         var offset = currentPoint - this.lastPosition;
    64.  
    65.         transform.position -= (offset * this.velocity/velocityDivisionFactor);
    66.     }
    67. }
    And this is the Input system actions created:

    upload_2020-6-5_1-36-57.png

    - The Action StartMovement:
    -- Action type: Button
    -- Binding: Using Mouse button (I reduce the press point time to make more easy the implementation)

    - The Action Movement
    -- Action Type: Value
    -- Control Type: Vector2
    -- Binding: Mouse Position

    As you can see, the code identify the performed of the StartMovement and save the last point clicked. And while action don't call the canceled state, the method "TryMove" performed in Movement action will apply the action that I want.

    But I really want a OnBeginDrag, OnDrag and OnEndDrag events hahahhahahahaha
     
  46. Paulrld

    Paulrld

    Joined:
    Feb 16, 2018
    Posts:
    3
    @JoaoSantos, This is the implementation I was looking for! A way to move the mouse position while the mouse click is held down. Awesome
     
    JoaoSantos likes this.
  47. JoaoSantos

    JoaoSantos

    Joined:
    Mar 31, 2014
    Posts:
    20
    Thanks ;) The alternative call the function "TryMove" in all mouse movements and this is not good. I'm studying a way to put an extra condition to execute this just when necessary. I didn't test yet, but, when I make all base tests, I will put here ;)
     
  48. JoaoSantos

    JoaoSantos

    Joined:
    Mar 31, 2014
    Posts:
    20
    @Paulrld I tested this and work good for me.

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.InputSystem;
    3. public class CameraTargetMovement : MonoBehaviour
    4. {
    5.     [SerializeField]
    6.     [Range(0.1f, 1f)]
    7.     private float velocity;
    8.  
    9.     private InputCamera controls;
    10.     private Vector3 lastPosition;
    11.  
    12.     private const float velocityDivisionFactor = 10f;
    13.  
    14.     private void Awake()
    15.     {
    16.         this.controls = new InputCamera();
    17.  
    18.         this.controls.Camera.StartMovement.performed += ctx => SetMoving(true);
    19.         this.controls.Camera.StartMovement.canceled += ctx => SetMoving(false);    
    20.     }
    21.  
    22.     private void OnDestroy()
    23.     {
    24.         this.controls.Camera.StartMovement.performed -= ctx => SetMoving(true);
    25.         this.controls.Camera.StartMovement.canceled -= ctx => SetMoving(false);    
    26.     }
    27.  
    28.     private void OnEnable()
    29.     {
    30.         this.controls.Enable();
    31.     }
    32.  
    33.     private void OnDisable()
    34.     {
    35.         this.controls.Disable();
    36.     }
    37.  
    38.     private void SubscribeMovementEvent(){    
    39.         this.controls.Camera.Movement.performed += MoveContext;
    40.     }
    41.  
    42.     private void DisubscriveMovementEvent(){    
    43.         this.controls.Camera.Movement.performed -= MoveContext;
    44.     }
    45.  
    46.     private void MoveContext(InputAction.CallbackContext ctx)
    47.     {
    48.         Move(ctx.ReadValue<Vector2>());
    49.     }
    50.  
    51.     private void PrepareMovement()
    52.     {
    53.         var screenPoint = this.controls.Camera.Movement.ReadValue<Vector2>();
    54.         Vector3 worldPoint = screenPoint.ScreenToWorldPoint();
    55.  
    56.         this.lastPosition = worldPoint;
    57.     }
    58.  
    59.     private void SetMoving(bool isMoving)
    60.     {
    61.         if(isMoving){
    62.             PrepareMovement();
    63.             SubscribeMovementEvent();
    64.         }else{
    65.             DisubscriveMovementEvent();
    66.         }            
    67.     }
    68.  
    69.     private void Move(Vector2 position)
    70.     {            
    71.         Vector3 currentPoint = position.ScreenToWorldPoint();
    72.  
    73.         var offset = currentPoint - this.lastPosition;
    74.  
    75.         transform.position -= (offset * this.velocity/velocityDivisionFactor);
    76.     }
    77. }
    78.  
    I don't know the correct english to explaing each new modification, but, let's go:

    Now, we subscribe the MoveContext function when we start the Startmovement input and we remove it when we cancel the StartMovement.

    With that, we don't have mouse movementation in all states, just when its necessary, making the state flow better.

    I Think that we lost a start frame update input with this, but for my situation, work great. Now, I thinking to extract some methods to create a Interface class, to use in others situations
     
    Last edited: Jun 15, 2020
  49. rozsazsombor70228

    rozsazsombor70228

    Joined:
    Mar 18, 2020
    Posts:
    2
    If anyone is interested this is my solution:

    using UnityEngine;
    using UnityEngine.InputSystem;
    public class PlayerController : MonoBehaviour
    {
    public float speed = 10.0f;
    public InputMaster controls;
    private float translation;
    private float straffe;
    private Keyboard kb;
    private Gamepad gp;
    // Use this for initialization
    void Start()
    {

    //set input methods
    kb = InputSystem.GetDevice<Keyboard>();
    gp = InputSystem.GetDevice<Gamepad>();
    // turn off the cursor
    Cursor.lockState = CursorLockMode.Locked;
    //set input master
    controls = new InputMaster();
    //enable controls
    controls.Enable();
    }
    // Update is called once per frame
    void Update()
    {
    if (controls.Player.Move.ReadValue<Vector2>() != new Vector2(0, 0))
    {
    Move(controls.Player.Move.ReadValue<Vector2>());
    }
    if (kb.escapeKey.isPressed || gp.startButton.isPressed)
    {
    // turn on the cursor
    Cursor.lockState = CursorLockMode.None;
    }
    }
    private void Move(Vector2 direction)
    {
    Debug.Log(direction);
    translation = direction.y * speed * Time.deltaTime;
    straffe = direction.x * speed * Time.deltaTime;
    transform.Translate(straffe, 0, translation);
    }
    }
     
  50. illinar

    illinar

    Joined:
    Apr 6, 2011
    Posts:
    863
    This is mine:

    Code (CSharp):
    1. using System.Collections.Generic;
    2. using UnityEngine;
    3. using UnityEngine.InputSystem;
    4.  
    5. [DefaultExecutionOrder(-11)]
    6. public class Input : MonoBehaviour
    7. {
    8.     public static InputActions actions { get; private set; }
    9.     private static Input _instance;
    10.     private static Dictionary<System.Guid, Action> _actionDictionary;
    11.  
    12.     public void Awake()
    13.     {
    14.         actions = new InputActions();
    15.         _instance = this;
    16.         Input.actions.Character.Enable(); //TODO: Move
    17.         _actionDictionary = new Dictionary<System.Guid, Action>(10000);
    18.         foreach (var a in actions)
    19.         {
    20.             var action = new Action(a);
    21.             _actionDictionary.Add(a.id, action);
    22.         }
    23.     }
    24.  
    25.     public void Update()
    26.     {
    27.         foreach (var a in _actionDictionary.Values)
    28.         {
    29.             a.Invoke();
    30.         }
    31.     }
    32.  
    33.     public static void AddStartedCallback(InputAction inputAction, System.Action<InputAction> callbackAction, MonoBehaviour target = null)
    34.     {
    35.         target = target == null ? _instance : target;
    36.         GetAction(inputAction.id).AddStartedSubscriber(target, callbackAction);
    37.     }
    38.  
    39.     public static void AddInProgressCallback(InputAction inputAction, System.Action<InputAction> callbackAction, MonoBehaviour target = null)
    40.     {
    41.         target = target == null ? _instance : target;
    42.         GetAction(inputAction.id).AddInProgressSubscriber(target, callbackAction);
    43.     }
    44.  
    45.     public static void AddCancelledCallback(InputAction inputAction, System.Action<InputAction> callbackAction, MonoBehaviour target = null)
    46.     {
    47.         target = target == null ? _instance : target;
    48.         GetAction(inputAction.id).AddCancelledSubscriber(target, callbackAction);
    49.     }
    50.  
    51.     public static bool CheckActionStarted(InputAction action)
    52.     {
    53.         return GetAction(action.id).started;
    54.     }
    55.  
    56.     public static bool CheckActionInProgress(InputAction action)
    57.     {
    58.         return GetAction(action.id).inProgress;
    59.     }
    60.  
    61.     public static bool CheckActionCancelled(InputAction action)
    62.     {
    63.         return GetAction(action.id).cancelled;
    64.     }
    65.  
    66.     private static Action GetAction(System.Guid guid)
    67.     {
    68.         Action action = null;
    69.         if (!_actionDictionary.TryGetValue(guid, out action))
    70.         {
    71.             Debug.LogError("Action not found");
    72.         }
    73.         return action;
    74.     }
    75.  
    76.     private class Action
    77.     {
    78.         public InputAction inputAction;
    79.         public bool started { get; private set; }
    80.         public bool inProgress { get; private set; }
    81.         public bool cancelled { get; private set; }
    82.         // public float value;
    83.  
    84.         private Dictionary<MonoBehaviour, Subscriber> _subscriberDictionary;
    85.  
    86.         public Action(InputAction inputAction)
    87.         {
    88.             _subscriberDictionary = new Dictionary<MonoBehaviour, Subscriber>(10000);
    89.             this.inputAction = inputAction;
    90.             inputAction.performed += ctx => { started = true; inProgress = true; cancelled = false; };
    91.             inputAction.canceled += ctx => { cancelled = true; started = false; inProgress = false; };
    92.         }
    93.  
    94.         public void Invoke()
    95.         {
    96.             foreach (var sub in _subscriberDictionary.Values)
    97.             {
    98.                 if (started)
    99.                 {
    100.                     sub.startedCallbacks?.Invoke(inputAction);
    101.                 }
    102.                 if (inProgress)
    103.                 {
    104.                     sub.inProgressCallbacks?.Invoke(inputAction);
    105.                 }
    106.                 if (cancelled)
    107.                 {
    108.                     sub.cancelledCallbacks?.Invoke(inputAction);
    109.                 }
    110.             }
    111.             // started = false;
    112.             // if (cancelled)
    113.             // {
    114.             //     inProgress = false;
    115.             // }
    116.             // cancelled = false;
    117.         }
    118.  
    119.         public void AddStartedSubscriber(MonoBehaviour target, System.Action<InputAction> callbackAction)
    120.         {
    121.             GetSubscriber(target).startedCallbacks += callbackAction;
    122.         }
    123.  
    124.         public void AddInProgressSubscriber(MonoBehaviour target, System.Action<InputAction> callbackAction)
    125.         {
    126.             GetSubscriber(target).inProgressCallbacks += callbackAction;
    127.         }
    128.  
    129.         public void AddCancelledSubscriber(MonoBehaviour target, System.Action<InputAction> callbackAction)
    130.         {
    131.             GetSubscriber(target).cancelledCallbacks += callbackAction;
    132.         }
    133.  
    134.         private Subscriber GetSubscriber(MonoBehaviour target)
    135.         {
    136.             Subscriber subscriber = null;
    137.             if (!_subscriberDictionary.TryGetValue(target, out subscriber))
    138.             {
    139.                 subscriber = new Subscriber();
    140.                 _subscriberDictionary.Add(target, subscriber);
    141.             }
    142.             return subscriber;
    143.         }
    144.  
    145.         private class Subscriber
    146.         {
    147.             public System.Action<InputAction> inProgressCallbacks;
    148.             public System.Action<InputAction> startedCallbacks;
    149.             public System.Action<InputAction> cancelledCallbacks;
    150.         }
    151.     }
    152. }
    153.  
    154. public static class InputActionExtension
    155. {
    156.     public static bool Started(this InputAction a) { return Input.CheckActionStarted(a); }
    157.     public static bool InProgress(this InputAction a) { return Input.CheckActionInProgress(a); }
    158.     public static bool Canceled(this InputAction a) { return Input.CheckActionCancelled(a); }
    159.  
    160.     public static void AddStartedCallback(this InputAction a, System.Action<InputAction> action, MonoBehaviour target = null)
    161.     {
    162.         Input.AddStartedCallback(a, action, target);
    163.     }
    164.  
    165.     public static void AddInProgressCallback(this InputAction a, System.Action<InputAction> action, MonoBehaviour target = null)
    166.     {
    167.         Input.AddInProgressCallback(a, action, target);
    168.     }
    169.  
    170.     public static void AddCancelledCallback(this InputAction a, System.Action<InputAction> action, MonoBehaviour target = null)
    171.     {
    172.         Input.AddCancelledCallback(a, action, target);
    173.     }
    174. }
    Usage:
    Code (CSharp):
    1. var sprinting = Input.actions.Character.Sprint.InProgress();
    2.  
    3. Input.actions.Character.Sprint.AddInProgressCallback(MyMethod);