I’m working on an action game in which there is one attack input that has different results (in different contexts during regular gameplay) depending on whether it is pressed, tapped, held, or held and released.
Originally I tried checking if context.interaction was a Press, Tap, Hold or SlowTap interaction in response to the action’s callbacks, but I realized that doesn’t work, because different interactions override each other depending on how they’re ordered on the action.
I guess I can understand why this is the case, if I really think about what must be going on behind the scenes and try to visualize the data and timing, but it’s very confusing.
There should be, at least, some kind of warning/indication in the action map editor UI that a given interaction can’t happen at all or that it will only happen on X phase(s) of the action, due to the order of interactions.
I think I can achieve the behavior I want using a Tap interaction and a Hold interaction, but then I need to check if the Tap interaction has started to get Press behavior, and then check if a Hold interaction has canceled to get SlowTap behavior. And when doing this, Hold is started after Tap is canceled, which is a bit confusing because Hold normally is started when the input is first pressed, but for some reason it’s deferred in this case. And then I wonder, is the timer for Hold only started after Tap has been canceled? It appears not, which is good…
But I really feel like it shouldn’t be this complicated and shouldn’t require you to detect interactions in an inconsistent way in the first place. Ideally, I think there would be a callback for each interaction being started, performed, and canceled.
The order-dependence makes the feature of being able to use multiple interactions on a single action both too limited and redundant.
While at first it seems that it’s intended to facilitate my use case, in practice I’ve spent way more time trying to figure this out than I would have if I used only the Press interaction and just coded the tap and hold behaviors myself.
I’m sorry, but I think you’re overthinking this, unless your design is overcomplicated at the first place.
You’re looking for three different interaction to be performed. This is the baseline.
You add your three interactions, tap, slowtap, hold.
You subscribe to the performed event and do whatever your game needs to do in those cases.
And you’re done. You do not need anything else to do.
You don’t need the order of the started, cancelled or even the performed. You do not need to check. All you need to know which interaction was actually performed.
But, for just the clarification’s sake: you see the hold start after the slow tap cancelled and the slow tap started after the tap cancelled, because they need to be. If you were finishing the action before the deadline, you would actually perform the faster action and your wouldn’t see the second or third interactions at all. They aren’t overlapping.
Of course there is a possibility that I misunderstood the problem or your setup, in this case sorry and please disregard my comment.
Thanks for your reply.
What you suggest is how I thought it should work, where you simply check the interaction type for the performed callback, but in my tests, I’ve found that by ordering interactions different ways, some interactions are never performed.
To see what interactions are happening, I’m subscribing to my attack action’s started, performed and canceled callbacks, then logging both the phase and interaction type every time one of those events fire.
Here’s what happens when put Tap, SlowTap, and Hold interactions on my attack action (in that order), then press, hold, and release my attack input:
-started Tap
-canceled Tap
-started SlowTap
-performed SlowTap
The Hold interaction just never happens.
Here’s what happens when I order Hold above SlowTap:
-started Tap
-canceled Tap
-started Hold
-performed Hold
-canceled Hold
This time SlowTap doesn’t happen. Depending on which interactions are there and how they’re ordered, other actions don’t seem to happen.
Perhaps I’m still confused but it seems to me that the input system can’t accommodate my use-case (which I don’t think is very uncommon really) just through using the different interactions, and I have to resort to workarounds like I described.
So, I have built a quick test rig just to test out the scenarios. Here is what I found:
You need consistently keep the slower interaction above the faster ones, because of the settings you have. Also you need to decide what those settings should be, forget the default values, they’re useless. If you follow these two criteria, you can get consistent result.
Here is what I did:
using UnityEngine;
using UnityEngine.InputSystem;
public class TestInput : MonoBehaviour
{
[SerializeField]
private InputActionAsset inputActionAsset;
private void Awake()
{
inputActionAsset.Enable();
var action = inputActionAsset.FindAction("TestAction");
action.performed += context => Debug.Log(context.interaction);
}
}
Usage:
if you hit the spacebar for less than .2 second, then it’s a “tap”.
if you hit the spacebar longer than .4 but shorter than 1 second, then it’s a “slow tap”
if you hit and hold the spacebar for a second at least, it’s a “hold”.
(I have tested the interactions on the Action itself as well, same results)
Obviously you can play around with these values, you can even change the defaults in the InputSystem settings in the central settings so it is consistent among actions.
BTW, the documentation talks about this exact question, the order of the interactions matter:
Thanks very much for spending the time to do this, I appreciate it.
However, this still doesn’t accomplish what I need it to. I’m sorry, up until now I haven’t specified exactly what I need to happen, including timing. And I also need a Press interaction.
What specifically I need is to detect 4 different interactions for my attack action:
-Press interaction immediately upon pressing the input
-Tap interaction if it’s released in under 0.2 seconds
-Hold interaction if held longer than 0.2 seconds
-SlowTap interaction if released after longer than 0.2 seconds.
I was hoping to do this entirely through Interactions because it makes everything very simple and consistent, everything is neatly set up through the action map editor and input settings, timing is handled automatically, etc.
It seems very odd to me that you can setup multiple Interactions on an action, and yet, if you want to do something basic like get events for performing a Press and a Hold, you can’t.
Or if you want to detect a Hold, and then a release after that same hold time (SlowTap) you can’t do that either.
IDK if it is “basic” what you want. I just demonstrated the “basic” usage. Three interactions on one action, based on the player’s input. Usual gameplay is about that. If you hit the key, make a punch, if you hold the key short, make a stronger punch, if you hold the key (power up), do the power punch. Or something like that.
Press and hold is .started and .performed (interaction==hold) together. The problem with this is that you physically cannot know what the player has started when they pressed the input device. If they will hold for .2 it will be a tap, if they hold it for .6 it will be a slow tap, and if they don’t release the device until a second, it will be a hold.
I don’t see how the input system would know this in advance.
If you want to manage the timing, you can always drop all interactions and just hang onto the .started and .performed events and measure the time between them. Based on that you can decide when you want to do what. (.started is your pressed and the .performed can be any of these three)
Or if you want to keep the interactions, you can hang onto the .started methods, set booleans which interactions started, and then hang onto the .cancelled events too, set the booleans again and then in the .performed event you will get the result. Whatever boolean out of the three you still get true, you performed the interaction.
I don’t understand what isn’t basic about what I’m trying to do or even how it’s any more complex than your example.
If I were to drop Interactions and manually code your example, and manually code mine, the result would be exactly the same, the only difference being timing values (and handling when the input is first pressed).
In your example there are gaps of time between the Tap, the Hold, and the SlowTap, in which releasing the key will result no action being performed*.*
I don’t see how that’s more usual, and I don’t see why, from a design perspective, that should be implemented in a different way than if you were to not have staggered timing and instead have completely back-to-back Interactions.
I’ll give a real-world example that’s comparable to my game. In the modern Ninja Gaiden games, say the ones on Xbox, here’s how the strong attack input works:
-From a non-attack state, you tap Y to perform a heavy attack
-From a non-attack state, you hold Y to begin charging a charge attack
-From a charging state, you release Y to perform a charge attack
-From (most) attack states, you press Y to perform another attack in the combo
There are no gaps of timing there either. This is basic, and such behavior is extremely common, especially in action games.
The design affordances of the Interactions in the action map editor suggest that I could do something like have a Press event and a Hold event, but I can’t. The one ordered second will just not activate at all.
The design does not suggest that I need to have gaps of timing between certain interactions, and that some interactions will completely override others.
The input system appears extremely close to accommodating my use case, but I actually require workarounds and need to code small parts of my input in inconsistent ways, and forgo all the benefits of using Interactions, for what largely amounts to slight differences in timing.
These workarounds are easy, but that makes it feel even more arbitrary and messy.
If a single Interaction exists on an action, I can merely respond to its event, no matter its type.
But if multiple Interactions exist on an action, then the type, order, and timing of each interaction decides whether I can actually use those Interactions at all or if I have to code the detection of slightly different interactions myself. And it may take a lot of time and be very difficult to determine if what I want to do is possible with Interactions or not.
It honestly seems absurd and arbitrary to me.
As I said, you can adjust the values. Just put the Max Tap Duration of the tap something short, like .2, put the Min Tap Duration of Slow Tap .21 and put the hold time whenever you like. There you go, there aren’t gaps. I haven’t tried to overlap them, but I guess the earlier in the list wins when the physical holding time is exactly .2.
As for the rest, well. It’s not basic in my book, but obviously we probably have different understanding of basic.
BTW, someone mentioned here (I guess @Rene-Damm sorry to summon, just maybe useful feedback) that eventually they are planning to develop some sort of combo-system on the top of the existing one.
Good luck with your input endeavors, I can’t add more useful information.
I was actually just in the process of trying out what you’re suggesting here, to make the gaps between different Interactions very small, though not much luck yet… and I better get some sleep before I continue messing with it.
Anyway, thanks for spending your time trying to help me figure this out.
Indeed, upgrading the Input System seems to fix a lot of my issues which is awesome.
I neglected to enable and check the preview packages and had been using the verified release.
Now I can line up Interactions back-to-back with no timing gaps between, for example a Tap and a Hold, which solves quite a bit for me.
I also better understand how the Interactions work overall now, but there is still a confusing and frustrating issue here.
Before, I thought I had observed that the timings of Interactions were sequential, so that, for example, the timing of one Tap would only begin after the duration of a previous Tap had been exceeded.
For this reason, if a Press was put before a Hold, the Hold could never start until the Press was canceled, and therefore the Hold could never happen at all.
But now I’m observing that timings for Interactions are being incremented in parallel, but the callbacks still only happen sequentially, which makes for confusing and redundant behavior (and prevents my usage)
For example:
I add a Press Interaction and a Hold Interaction to an action, and set the hold time to 1.
When I press the input, I see that the Press was started and performed, as expected.
If I hold my input for longer than one second, I don’t see that the Hold was performed.
Once I finally release the input, I suddenly get 4 callbacks that say Press was canceled, and that the Hold was started, performed, and canceled.
In this example, why can’t I just receive a performed callback upon having held the input for the specified hold time? What is the use of deferring that callback until after the previous Interaction has been canceled?
When I first saw I could put multiple Interactions on an action, my initial assumption was that I could throw on any of the unique Interactions I wanted to be able to respond to, and then I would receive callbacks for all of them, and just choose which one I wanted to respond to given my game’s moment-to-moment context. (e.g. in one moment I need to do something if the input is merely pressed, and in another moment I need to do something different based on whether the input is tapped or held)
I would think that makes more sense, is more useful, and would be easier to use overall, but then again maybe I just don’t know what unique use-cases this current implementation facilitates.
I’m well aware of workarounds, and many are very easy to implement.
But that just makes it feel even more like an arbitrary limitation.
While I can easily detect a Press and Taps and SlowTaps and whatnot on a single action, if I want something as simple as a Press callback and a Hold callback, I have to roll my own solution and forgo most of the benefits of the input system’s Interactions, such as the consistent handling, editing, and organization of all my input behaviors. That doesn’t seem like a reasonable sacrifice to accomplish something so simple.
I can’t help but see the current behavior as a limitation, a hole in the design.