Skip to content

Instantly share code, notes, and snippets.

@LordNed
Created March 30, 2015 02:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LordNed/e83fa1f8c64eec592d6b to your computer and use it in GitHub Desktop.
Save LordNed/e83fa1f8c64eec592d6b to your computer and use it in GitHub Desktop.
Wrapping input for use in FixedUpdate.
using UnityEngine;
using System.Collections;
/// <summary>
/// What the problem is:
/// In Unity, the only way to get input from the user's keyboard/joysticks/etc. is to poll the Input
/// system in the Update() function. However, there are often times where you need to actually get
/// their input in the FixedUpdate() function (such as when dealing with the physics engine).
///
/// Why is this a problem?
/// If you use the Input system in FixedUpdate things like GetButton/GetButtonDown will miss key-presses.
/// This is because FixedUpdate runs at 50fps (by default) while the Input system runs at however-fast
/// vsync-is-set. Because of this, you can press and release a key between FixedUpdate calls and
/// it'll never register if used there.
///
/// How does this fix it?
/// It gathers input in the Update frames and then stores it until FixedUpdate, after which it will reset
/// the state. It preserves the state of buttons (by only updating if they have been pressed) so even
/// single frame long presses of a key will remain 'true' until FixedUpdate is run, instead of the default
/// behaviour where it turns true and then false again before FixedUpdate is ever run.
/// </summary>
public class CharacterInput
{
private class InputState
{
public float Horizontal;
public float Vertical;
public bool Jump;
public bool Fire;
}
public float Horizontal { get { return m_currentState.Horizontal; } }
public float Vertical { get { return m_currentState.Vertical; } }
public bool JumpDown { get { return !m_previousState.Jump && m_currentState.Jump; } }
public bool Jump { get { return m_currentState.Jump; } }
public bool FireDown { get { return !m_previousState.Fire && m_currentState.Fire; } }
public bool Fire { get { return m_currentState.Fire; } }
// The current state of the input that is updated on the fly via the Update loop.
private InputState m_currentState;
// Store the previous state of the input used on the last FixedUpdate loop,
// so that we can replicate the difference between GetButton and GetButtonDown.
private InputState m_previousState;
// Have we been updated since the last FixedUpdate call? If we haven't been updated,
// we don't reset. Otherwise, FixedUpdate being called twice in a row will cause
// JumpDown/FireDown to falsely report being reset.
private bool m_updatedSinceLastReset;
public CharacterInput()
{
m_currentState = new InputState();
m_previousState = new InputState();
}
public void OnUpdate(float horizontal, float vertical, bool jump, bool fire)
{
// We always take their most up to date horizontal and vertical input. This way we
// can ignore tiny bursts of accidental press, plus there's some smoothing provided
// by Unity anyways.
m_currentState.Horizontal = horizontal;
m_currentState.Vertical = vertical;
// However, for button presses we want to catch even single-frame presses between
// fixed updates. This means that we can only copy across their 'true' status, and not
// false ones. This means that a single frame press of the button will result in that
// button reporting 'true' until the end of the next FixedUpdate clearing it. This prevents
// the loss of very quick button presses which can be very important for jump and fire.
if (jump)
{
m_currentState.Jump = true;
}
if (fire)
m_currentState.Fire = true;
m_updatedSinceLastReset = true;
}
public void ResetAfterFixedUpdate()
{
// Don't reset unless we've actually recieved a new set of input from the Update() loop.
if (!m_updatedSinceLastReset)
return;
// Swap the current with the previous and then we'll reset the old
// previous.
InputState temp = m_previousState;
m_previousState = m_currentState;
m_currentState = temp;
// We reset the state of single frame events only (that aren't set continuously) as the
// continious ones will be set from scratch on the next Update() anyways.
m_currentState.Jump = false;
m_currentState.Fire = false;
m_updatedSinceLastReset = false;
}
}
[RequireComponent(typeof(CharacterMovement), typeof(CharacterWeaponInventory))]
public class Character : MonoBehaviour
{
private CharacterMovement m_characterMovement;
private CharacterWeaponInventory m_weaponInventory;
private CharacterInput m_input;
private void Awake()
{
m_input = new CharacterInput();
m_characterMovement = GetComponent<CharacterMovement>();
m_characterMovement.InputController = m_input;
m_weaponInventory = GetComponent<CharacterWeaponInventory>();
m_weaponInventory.InputController = m_input;
}
private void Update()
{
m_input.OnUpdate(Input.GetAxis("Horizontal"), Input.GetAxis("Vertical"), Input.GetButton("Jump"), Input.GetButton("Fire"));
}
private void FixedUpdate()
{
// Script Execution order has been modified to ensure that Character should do FixedUpdate last, so it doesn't get
// reset until all other components have executed their FixedUpdate calls.
m_input.ResetAfterFixedUpdate();
}
}
public class CharacterMovement : MonoBehaviour
{
private void FixedUpdate()
{
if (InputController.Jump)
{
// We want jumping to always be triggered by a complete press
// of the jump key. This prevents bunny hoppying in place/as you go
// and instead makes it a little more timing/skill based.
if (InputController.JumpDown)
{
// Do something.
}
// Do some other things.
}
}
}
@LordNed
Copy link
Author

LordNed commented Mar 30, 2015

Edit: These are just example scripts/usage, not compilable code. For one, CharacterMovement is missing a "public CharacterInput InputController", not to mention the other missing scripts.

This code is far from perfect, and I'm really not happy with how it is handled. However, I feel like the snippet is too good to go to waste (as I'm reverting out of using FixedUpdate after writing this).

The one advantage of doing it this way is that you don't have to write the gather-input-and-store-until-use for every class using FixedUpdate. Instead, you gather all of the input once here in the Character's Update() function (which can be swapped out with a Player or a bot/networked character with relative ease) and then all of the sub-components use this input in their FixedUpdate calls.

More useful as a code snippet and explanation of some issues present with Unity's Update/FixedUpdate than anything for production code I guess, but feel free to do whatever.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment