So ever since I mentioned that for SWIFT☆STITCH, I had input that was independent of framerate, I rarely go a few days without somebody asking just how I did it, so to save myself lots of typing in the future, here’s the explanation 🙂
(UPDATE: Whilst this approach is an improvement on regular input in unity, it seems you can’t know what time inputs occurred at exactly, see here)
Why do it?
So here’s the thing, most input in unity is updated at the same rate as frames are drawn. here’s the rough process as simple as I can put it:
- Unity collects all the user’s inputs, and updates the Input class accordingly
- Unity runs your Update() functions, then draws a frame.
- Unity clears the Input class, and we go back to step 1
this means that when you ask for ‘Input.GetKeyDown(“space”)‘ you are asking if the space key has been pressed since the last frame. (which is why you shouldn’t look for such input in FixedUpdate() or OnGUI(), since it happens independent of framerate and you can miss it).
this is all fine and super cool (really!), so why would you ever need input to work any other way? well, what if you want user to be able to play the game faster than it is drawn? in the case of SWIFT☆STITCH, even at framerates over a hundred, input was hindering me. I could click the mouse for a fraction of a second (less than a frame), but the ship would still react as if that input lasted an entire frame. in other cases, what if you want to be able to record multiple presses per frame?
these are rare things, and I’ve only ever needed to separate input and framerate for one game so far. however, I think it’s useful to consider frames as nothing more than snapshots, an approximation of what the world looks like. the user sees the frames and the game world is constructed from them in their head. the user isn’t waiting for frames to come around before acting, they act when it feels right to them. they are playing the game in the world that is in their heads, frames are just our way of assisting them in visualising that world.
as such, it’s better to not have the game world update at the rate of something so arbitrary as how fast a computer and screen can make pretty colours, but at the rate of the world we are trying to represent, as unlimited by technology as possible.
The framerate independent model;
so my approach is this;
- we grab input at the very moment the user makes it, we record all we care about and the precise time they happen
- when our Update() functions roll around, we simulate the game world taking into account all the inputs we have and exactly when they happened
- we clear our record of inputs before the frame is drawn, and we start over at 1 again
using this model, the user is interacting independent of the framerate, the world has no quirks based on the framerate (since we are using real time and not delta time).
How to do it in Unity:
so as mentioned above, the Input class is tied in to the framerate pretty closely. if you want to capture input as it happens we need to look elsewhere, and the place my come as a surprise; OnGUI().
that’s right, I didn’t typo, you didn’t misread. I said OnGUI()! you see, OnGUI() is triggered a bunch of times, by many different things. one of them, is user input. if the user presses space a hundred times since the last frame, OnGUI() will have run *at least* 200 times since the last frame too (remember, key releases are also 😉 ).
what we need is Event.current. drop this inside OnGUI() and you can find out just what event it was that triggered the function this time. and if it was keyboard or mouse input, the relevant information is passed along as part of Event.current. (no gamepad input here sorry, you’ll have to look elsewhere to make that framerate independent).
so, there you have it, you have captued input precisely when it happened, framerate has nothing to do with it!
but… you still don’t know WHEN it was pressed. like the Input class, most of the Time class you are familiar with is tied to framerate (yep, it’s only really Time.deltaTime and Time.time, but what else have you used lately? :P). what you really want, is Time.realtimeSinceStartup.
given you now have both a way of capturing input independent of framerate and recording precisely when they happen, the only step left is simulating the game world based on this information.
I suggest you add this information to a list, and when the next Update() rolls around, you step through this list, input by input, and simulate the game up to the time the next input happened (and not forgetting to simulate the time between the last frame to the first input, and the time from the last input to the present). then clear the list and you’re good to go again 🙂
Some excess blah blah:
- yes, it takes time for a user’s input to get from their brain, to their hands, through the keyboard and so on before it gets to the game. we aren’t recording input the instant it happens, but we’re doing it as damn close as we can 🙂
- really, the odds you even need to take advantage of framerate independent input are tiny. it’s a bit of extra hassle you don’t need unless your game plays so fast that you need crazy fast input too.
- I do think having framerate independent input is a little bit of an ideal when it comes to interactive worlds… but a framerate of infinity is also a similar ideal. this one is more attainable sure, but if a player never notices then it doesn’t matter too much. we’re magicians after all! why do real magic when a simple trick has the same effect? 🙂
- the unity docs have the following caution about realtimeSinceStartup: “Depending on the platform and the hardware, it may report the same time even in several consecutive frames” I’ve not had any such problems myself on PC/Mac (iOS is another story though). so be aware of that I guess