-
Couldn't load subscription status.
- Fork 328
Description
Background and Motivation
This API is an effort to simplify using the Input System for new users, small projects, and/or experienced users/teams starting new projects. It should be possible for all users to implement basic input functionality in a zero-configuration, everything-just-works, environment. New users should have to learn a minimum number of new concepts to get started, without having to read any documentation, but existing users of the legacy input manager should feel some level of familiarity with the interface. The intent is also that it is possible to transition with minimum effort from this high level API into the lower level APIs piecemeal as a game progresses.
This slice of the high level API includes methods for users to get basic input only (read: no actions, maps, processors, interactions, bindings, assets, control schemes, action types, disambiguation, callbacks, composites, or any other low level concepts are involved).
Proposed API
namespace UnityEngine.InputSystem
{
public static partial class Input
{
public static IReadOnlyList<Gamepad> gamepads { get; }
public static IReadOnlyList<Joystick> joysticks { get; }
public static ReadOnlyArray<InputSlot> gamepadSlotEnums { get; }
public static int maxGamepadSlots { get; }
public static bool IsPressed(Inputs input, InputSlot slot = InputSlot.All);
public static bool WasPressedThisFrame(Inputs input, InputSlot slot = InputSlot.All);
public static bool WasReleasedThisFrame(Inputs input, InputSlot slot = InputSlot.All);
public static bool IsPressed(GamepadButton button, InputSlot slot = InputSlot.All);
public static bool WasPressedThisFrame(GamepadButton button, InputSlot slot = InputSlot.All);
public static bool WasReleasedThisFrame(GamepadButton button, InputSlot slot = InputSlot.All);
public static bool IsPressed(JoystickButton button, InputSlot slot = InputSlot.All);
public static bool WasPressedThisFrame(JoystickButton button, InputSlot slot = InputSlot.All);
public static bool WasReleasedThisFrame(JoystickButton button, InputSlot slot = InputSlot.All);
public static float GetAxis(Inputs input, InputSlot slot = InputSlot.All);
public static float GetAxis(Inputs negativeAxis, Inputs positiveAxis);
public static Vector2 GetAxis(Inputs left, Inputs right, Inputs up, Inputs down);
public static Vector2 GetAxisRaw(Inputs left, Inputs right, Inputs up, Inputs down);
public static Vector2 GetAxis(GamepadAxis stick, InputSlot inputSlot= InputSlot.All);
public static Vector2 GetAxis(InputSlot joystickSlot, float deadzone = kDefaultJoystickDeadzone);
public static void SetGamepadTriggerPressPoint(float pressPoint, InputSlot gamepadSlot = InputSlot.All);
public static void SetGamepadStickDeadzone(float deadzone, InputSlot gamepadSlot = InputSlot.All);
public static bool IsGamepadConnected(InputSlot gamepadSlot);
public static bool DidGamepadConnectThisFrame(InputSlot gamepadSlot);
public static bool DidGamepadDisconnectThisFrame(InputSlot gamepadSlot);
public static Vector2 pointerPosition { get; }
public static bool pointerPresent { get; }
public static Vector2 scrollDelta { get; }
public enum GamepadAxis
{
LeftStick,
RightStick
}
public enum GamepadButton
{
DpadUp,
DpadDown,
DpadLeft,
DpadRight,
North,
East,
South,
West,
LeftStickButton,
RightStickButton,
LeftShoulder,
RightShoulder,
LeftTrigger,
RightTrigger,
Start,
Select,
X = West,
Y = North,
A = South,
B = East,
Cross = South,
Square = West,
Triangle = North,
Circle = East
}
public enum JoystickButton
{
Trigger,
Button2,
Button3,
Button4,
Button5,
Button6,
Button7,
Button8
}
public enum GamepadSlot
{
Slot1 = 0,
Slot2,
Slot3,
Slot4,
Slot5,
Slot6,
Slot7,
Slot8,
Slot9,
Slot10,
Slot11,
Slot12,
All = Int32.MaxValue,
Any = Int32.MaxValue
}
public enum JoystickSlot
{
Slot1 = 0,
Slot2,
Slot3,
Slot4,
All = Int32.MaxValue,
Any = Int32.MaxValue
}
public enum Inputs
{
Key_Space,
Key_Enter,
Key_Tab,
Key_Backquote,
Key_Quote,
Key_Semicolon,
Key_Comma,
Key_Period,
Key_Slash,
Key_Backslash,
Key_LeftBracket,
Key_RightBracket,
Key_Minus,
Key_Equals,
Key_A,
Key_B,
Key_C,
Key_D,
Key_E,
Key_F,
Key_G,
Key_H,
Key_I,
Key_J,
Key_K,
Key_L,
Key_M,
Key_N,
Key_O,
Key_P,
Key_Q,
Key_R,
Key_S,
Key_T,
Key_U,
Key_V,
Key_W,
Key_X,
Key_Y,
Key_Z,
Key_Digit1,
Key_Digit2,
Key_Digit3,
Key_Digit4,
Key_Digit5,
Key_Digit6,
Key_Digit7,
Key_Digit8,
Key_Digit9,
Key_Digit0,
Key_LeftShift,
Key_RightShift,
Key_LeftAlt,
Key_RightAlt,
Key_AltGr = Key_RightAlt,
Key_LeftCtrl,
Key_RightCtrl,
Key_LeftMeta,
Key_RightMeta,
Key_LeftWindows = Key_LeftMeta,
Key_RightWindows = Key_RightMeta,
Key_LeftApple = Key_LeftMeta,
Key_RightApple = Key_RightMeta,
Key_LeftCommand = Key_LeftMeta,
Key_RightCommand = Key_RightMeta,
Key_ContextMenu,
Key_Escape,
Key_LeftArrow,
Key_RightArrow,
Key_UpArrow,
Key_DownArrow,
Key_Backspace,
Key_PageDown,
Key_PageUp,
Key_Home,
Key_End,
Key_Insert,
Key_Delete,
Key_CapsLock,
Key_NumLock,
Key_PrintScreen,
Key_ScrollLock,
Key_Pause,
Key_NumpadEnter,
Key_NumpadDivide,
Key_NumpadMultiply,
Key_NumpadPlus,
Key_NumpadMinus,
Key_NumpadPeriod,
Key_NumpadEquals,
Key_Numpad0,
Key_Numpad1,
Key_Numpad2,
Key_Numpad3,
Key_Numpad4,
Key_Numpad5,
Key_Numpad6,
Key_Numpad7,
Key_Numpad8,
Key_Numpad9,
Key_F1,
Key_F2,
Key_F3,
Key_F4,
Key_F5,
Key_F6,
Key_F7,
Key_F8,
Key_F9,
Key_F10,
Key_F11,
Key_F12,
Key_OEM1,
Key_OEM2,
Key_OEM3,
Key_OEM4,
Key_OEM5,
Mouse_Left,
Mouse_Right,
Mouse_Middle,
Mouse_Forward,
Mouse_Back,
Gamepad_DpadUp,
Gamepad_DpadDown,
Gamepad_DpadLeft,
Gamepad_DpadRight,
Gamepad_North,
Gamepad_East,
Gamepad_South,
Gamepad_West,
Gamepad_LeftStickButton,
Gamepad_RightStickButton,
Gamepad_LeftStickUp,
Gamepad_LeftStickDown,
Gamepad_LeftStickLeft,
Gamepad_LeftStickRight,
Gamepad_RightStickUp,
Gamepad_RightStickDown,
Gamepad_RightStickLeft,
Gamepad_RightStickRight,
Gamepad_LeftShoulder,
Gamepad_RightShoulder,
Gamepad_LeftTrigger,
Gamepad_RightTrigger,
Gamepad_Start,
Gamepad_Select,
Gamepad_X = Gamepad_West,
Gamepad_Y = Gamepad_North,
Gamepad_A = Gamepad_South,
Gamepad_B = Gamepad_East,
Gamepad_Cross = Gamepad_South,
Gamepad_Square = Gamepad_West,
Gamepad_Triangle = Gamepad_North,
Gamepad_Circle = Gamepad_East,
Joystick_Trigger,
Joystick_Button2,
Joystick_Button3,
Joystick_Button4,
Joystick_Button5,
Joystick_Button6,
Joystick_Button7,
Joystick_Button8,
}
}
}API Usage
Perform some gameplay action when both the left keyboard Control key and left mouse button are pressed.
if (Input.IsPressed(Inputs.Key_LeftCtrl) && Input.IsPressed(Inputs.Mouse_Left))
{
DoTheThing();
}Start a timer when a control is pressed and perform some action on release.
public class ChargedFire : MonoBehaviour
{
private float m_StartTime = 0;
public void Update()
{
if (Input.WasPressedThisFrame(Inputs.Mouse_Left))
{
m_StartTime = Time.time;
}
if (Input.WasReleasedThisFrame(Inputs.Mouse_Left))
{
Fire(Time.time - m_StartTime);
m_StartTime = 0;
}
}
private IEnumerator Fire(float chargeTime)
{
...
yield return null;
}
}Use the left and right triggers of any gamepad as a one-dimensional axis.
public class SteerWithGamepadTriggers : MonoBehaviour
{
[SerializeField] private float m_MaxDegreesPerSecond;
public void Update()
{
// GetAxis returns a value in the range [-1, 1]
float steering = Input.GetAxis(Inputs.Gamepad_LeftTrigger, Inputs.Gamepad_RightTrigger);
transform.Rotate(Vector2.up, steering * m_MaxDegreesPerSecond * Time.deltaTime);
}
}Use the WASD keys to move in a plane at the same speed in any direction.
public class Move2DWithWASD : MonoBehaviour
{
[SerializeField] private float m_MaxVelocity;
public void Update()
{
Vector2 directionVector = Input.GetAxis(Inputs.Key_A, Inputs.Key_D, Inputs.Key_W, Inputs.Key_S);
transform.Translate(directionVector * m_MaxVelocity * Time.deltaTime);
}
}Use the right analogue stick on any gamepad to move a game object in a plane.
public class Move2DWithGamepadRightStick : MonoBehaviour
{
[SerializeField] private float m_MaxVelocity;
public void Update()
{
Vector2 directionVector = Input.GetAxis(GamepadAxis.RightStick);
transform.Translate(directionVector * m_MaxVelocity * Time.deltaTime);
}
}If any joysticks are connected, get the axis value of the first one and use it to move a game object.
public class Move2DWithJoystick : MonoBehaviour
{
[SerializeField] private float m_MaxVelocity;
public void Update()
{
if (Input.joysticks.Count > 0)
{
Vector2 directionVector = Input.GetAxis(0);
transform.Translate(directionVector * m_MaxVelocity * Time.deltaTime);
}
}
}Spawn prefabs continuously while the south button on any gamepad is pressed.
public class FireProjectiles : MonoBehaviour
{
[SerializeField] private float m_FireInterval;
[SerializeField] private GameObject m_Projectile;
private float m_LastFireTime;
public void Update()
{
if (Input.IsGamepadButtonPressed(GamepadButton.South) && Time.time - m_LastFireTime > m_FireInterval)
{
Instantiate(m_Projectile);
m_LastFireTime = Time.time;
}
}
}Set some state to true in the frame the gamepad Square button is pressed on any PlayStation gamepad, and false when it is released.
public class Jump : MonoBehaviour
{
private bool m_IsJumping;
public void Update()
{
if (Input.IsGamepadButtonDown(GamepadButton.Square))
{
m_IsJumping = true;
}
if (Input.IsGamepadButtonUp(GamepadButton.Square))
{
m_IsJumping = false;
}
}
}Set the gamepad trigger press point to 0.9 for the gamepad connected to slot 1.
Input.SetGamepadTriggerPressPoint(0.9f, GamepadSlot.Slot1);Set the gamepad stick deadzone to 0.3 for all gamepads.
Input.SetGamepadStickDeadzone(0.3f);Simple local multiplayer using gamepad slots
public class SimpleLocalMultiplayer : MonoBehaviour
{
public struct Player
{
public GameObject gameObject;
public GamepadSlot slot;
}
[SerializeField] private GameObject m_PlayerPrefab;
private List<Player> m_Players;
public void Start()
{
m_Players = new List<Player>();
for (var i = 0; i < 4; i++)
{
if (Input.IsGamepadConnected((GamepadSlot)i))
m_Players.Add(new Player
{
gameObject = Instantiate(m_PlayerPrefab),
slot = (GamepadSlot)i
});
}
}
public void Update()
{
foreach (Player player in m_Players)
{
var movement = Input.GetAxis(GamepadAxis.LeftStick, player.slot);
player.gameObject.GetComponent<PlayerController>().Move(movement);
if (Input.IsGamepadButtonDown(GamepadButton.West, player.slot))
player.gameObject.GetComponent<PlayerController>().Shoot();
}
}
}Notes
- The IsPressed/WasPressed/WasReleased and GetAxis methods look at all connected devices of the type represented by the argument. If the corresponding controls of any are actuated, the actuation values of those controls will be returned.
- The keyboard constants in the Inputs enum should represent physical key locations based on one consistent (US?) keyboard layout, so things like W, A, S, and D keys will still work on any OS layout.
- Some important features that might be considered part of basic input will come in the next few API proposals, such as the lastInput property, which is a way to easily query whether any input occurred in the current frame and what device type was last used by the player. This will come later because it is considered part of the high level event system.
- The device arrays exposed by the Input class (keyboards, mice, gamepads) are stable, that is, if a device disconnects, the index that it was occupying remains empty until either the same device or another device of the same type connects. That device will fill the hole left by the previous one. This is to abstract gameplay logic from having to track devices across disconnects and reconnects. A game can simply target a slot index for each player and not have to be concerned when players change devices.
Risks
Namespace issues
The most obvious name for the static class wrapping all of this functionality is Input, but that name is already taken by legacy input. We can work around it by using
using Input = UnityEngine.InputSystem.HighLevelAPI.Input;
in our using declarations, but that's not great. I think we could include a code analyzer to detect when users are using the wrong type and display it as a compilation error or warning to help mitigate this, but maybe a different name is better. InputMgr?
The infernal 'Was pressed this frame' issue
WasPressedThisFrame/WasReleasedThisFame i.e. was pressed this frame functionality, isn't particularly robust at the device layer right now because controls don't have any concept of a frame. So a press and release within a frame won't cause WasPressedThisFrame calls to return true because they only look at the current state, not the state over the whole frame. Since this API funnels users towards the polling approach, this flaw might become more obvious and should be fixed.