Krotox Engine › Docs › Scripting API › Physics & Input

Input: keyboard, mouse, touch, gestures & gamepads

Reading input in Krotox: keys, mouse buttons and position, multi-touch, the built-in gesture recognizer (tap, double tap, long press, swipe, edge swipe, drag, pinch), input bindings, gamepads, the Android back button, and ignoring taps the UI used.

Input is sampled once per frame by the engine, so every script sees the same answer whichever asks first. For each button there are three questions: is it held, did it go down this frame, did it come up this frame.

Read input in OnUpdate. OnFixedUpdate can run zero times in a fast frame and miss a key that went down and up within it. Store what you read and act on it in OnFixedUpdate if needed.

Keyboard

if (Input.GetKey(KeyCode.W))         { }   // held          (same as IsKeyDown)
if (Input.GetKeyDown(KeyCode.Space)) { }   // went down     (same as IsKeyPressed)
if (Input.GetKeyUp(KeyCode.Space))   { }   // came up

// A movement axis from keys (there is no Input.GetAxis)
float x = (Input.IsKeyDown(KeyCode.D) || Input.IsKeyDown(KeyCode.Right) ? 1f : 0f)
        - (Input.IsKeyDown(KeyCode.A) || Input.IsKeyDown(KeyCode.Left)  ? 1f : 0f);

KeyCode names: letters A–Z; digits D0–D9; Space, Enter, Escape, Tab, Backspace, Delete, Insert; arrows Up, Down, Left, Right; PageUp, PageDown, Home, End; F1–F25; keypad KP0–KP9, KPEnter, KPAdd and so on; modifiers LeftShift, RightShift, LeftControl, RightControl, LeftAlt, RightAlt, LeftSuper, RightSuper; punctuation such as Comma, Period, Minus, Equal, Slash, Semicolon, Apostrophe, LeftBracket, RightBracket, Backslash, GraveAccent.

Mouse

if (Input.GetMouseButton(0))     { }       // held: 0 = left, 1 = right, 2 = middle
if (Input.GetMouseButtonDown(0)) { }
if (Input.GetMouseButtonUp(1))   { }

Vector2 mouse  = Input.MousePosition;      // pixels, origin at the top-left
Vector2 moved  = Input.MouseDelta;         // since last frame
Vector2 scroll = Input.MouseScrollDelta;   // Y is the wheel

// Click in the world, but not when the click was on a button
if (Input.GetMouseButtonDown(0) && !Input.IsPointerOverUI())
{
    Vector2 world = Camera.ScreenToWorldPoint(Input.MousePosition);
    Translation = new Vector3(world.X, world.Y, 0);
}

Screen space. Mouse and touch positions, Screen.Width / Height and UI layout all use the same space: the game's own pixels. In the editor with a device preset selected, that is the device's resolution even though the Game view draws it smaller, so Camera.ScreenToWorldPoint(Input.MousePosition) needs no correction.

Touch

for (int i = 0; i < Input.TouchCount; i++)
{
    Touch t = Input.GetTouchInfo(i);       // FingerId, Position, DeltaPosition, Phase
    switch (t.Phase)
    {
        case TouchPhase.Began:      break;
        case TouchPhase.Moved:      break;
        case TouchPhase.Stationary: break;
        case TouchPhase.Ended:      break;
        case TouchPhase.Canceled:   break;
    }
}

Touch[] all = Input.Touches;

On desktop and in the editor, touch 0 follows the left mouse button, so single-finger code can be tested without a phone.

Gestures

Krotox recognizes common gestures from the raw touches. Distances are measured in inches, so a short swipe is the same thumb movement on every phone. There are two ways to react, and you can mix them.

1. Declare a handler

Write a method with the right name and parameters on any script. Every active script that declares one is called; there is nothing to subscribe or unsubscribe.

public class Runner : Entity
{
    void OnSwipe(SwipeDirection direction, Vector2 from, Vector2 to) { ChangeLane(direction); }
    void OnTap(Vector2 at)                    { Jump(); }
    void OnPinch(float scale, Vector2 centre) { m_Zoom *= scale; }
}
HandlerCalled when
void OnTap(Vector2 at)A quick touch and release in one spot.
void OnDoubleTap(Vector2 at)Two quick taps in the same place within DoubleTapSeconds. Each is also an OnTap.
void OnLongPress(Vector2 at)A finger held still for LongPressSeconds; fires once, while still down.
void OnSwipe(SwipeDirection direction, Vector2 from, Vector2 to)A quick, straight stroke at least MinSwipeInches long, under MaxSwipeSeconds.
void OnEdgeSwipe(ScreenEdge edge)A swipe that started at a screen edge and moved inward (in addition to OnSwipe). ScreenEdge.Left is a swipe in from the left edge.
void OnDragBegin(Vector2 at)A finger went down and moved off its spot.
void OnDrag(Vector2 at, Vector2 delta)The dragging finger moved: where it is and how far since the last call.
void OnDragEnd(Vector2 at)It lifted, or a second finger turned it into a pinch.
void OnPinch(float scale, Vector2 centre)Two fingers moving together or apart, every frame they do. scale is the change since the last frame (above 1 spreading), so zoom *= scale.

2. Subscribe to events

The same gestures are static events on Gestures (OnTap, OnDoubleTap, OnLongPress, OnSwipe, OnEdgeSwipe, OnDragBegin, OnDrag, OnDragEnd, OnPinch). Use them from classes that are not entity scripts, or for handlers that should only exist for part of the game. Subscribe in OnCreate, unsubscribe in OnDestroy; they are cleared when Play stops.

void OnCreate()                   { Gestures.OnSwipe += HandleSwipe; }
public override void OnDestroy()  { Gestures.OnSwipe -= HandleSwipe; }

void HandleSwipe(SwipeDirection direction, Vector2 from, Vector2 to) { }

bool dragging  = Gestures.IsDragging;     // between OnDragBegin and OnDragEnd
Vector2 finger = Gestures.DragPosition;
Do not give a subscribed method one of the handler names above (such as OnSwipe). The engine calls declared handlers by itself, so it would receive every gesture twice.

Positions and directions

Gesture positions are in screen space, like touches. Convert them for the world with Camera.ScreenToWorldPoint(position). direction.ToVector() turns a SwipeDirection into a world direction: (0, 1) for Up, (1, 0) for Right. (Screen from / to coordinates grow downward.)

Tuning

SettingDefault
Gestures.MinSwipeInches0.30
Gestures.MaxSwipeSeconds0.60
Gestures.TapSlopInches (how far a finger may wander and still count as still)0.12
Gestures.DoubleTapSeconds0.35
Gestures.LongPressSeconds0.50
Gestures.EdgeInches0.12

They return to these defaults when Play stops.

What counts as what. A diagonal or curved stroke is not a swipe. A finger that stays still and lifts quickly is a tap; one that stays longer is a long press, never also a tap. A moving finger is a drag, and a quick straight one is a swipe as well, so a script that wants only one should check Gestures.IsDragging or ignore the other. Only a single finger makes taps, swipes and drags, so a pinch never ends as one of them. With a mouse, everything except pinch can be tried in the editor.

Example: pinch-to-zoom camera

public class PinchZoom : Entity
{
    public float MinSize = 3.0f, MaxSize = 15.0f;
    CameraComponent m_Camera;

    public override void OnAwake() { m_Camera = GetComponent<CameraComponent>(); }

    void OnPinch(float scale, Vector2 centre)
    {
        // Spreading (scale > 1) zooms in, so divide the visible size
        m_Camera.OrthographicSize = Mathf.Clamp(m_Camera.OrthographicSize / scale, MinSize, MaxSize);
    }
}

Input bindings

Bind a key or gamepad button to a method; it fires when the button goes down.

void OnCreate()                  { InputManager.Bind(KeyCode.Escape, TogglePause); }
public override void OnDestroy() { InputManager.Unbind(KeyCode.Escape, TogglePause); }

void TogglePause() { if (Game.IsPaused) Game.Resume(); else Game.Pause(); }

InputManager.Bind(GamepadButton.Start, TogglePause);

Bindings can also be set up in the inspector on the Script component (event bindings), mapping keys to parameterless methods.

Gamepads

Desktop only (Windows, macOS, Linux), up to four pads with a standard Xbox-style layout. On Android every slot reports disconnected for now.

if (Input.IsGamepadConnected(0))
{
    Vector2 move = Input.GetLeftStick(0);                  // dead zone applied; up is +Y
    Vector2 look = Input.GetRightStick(0, deadZone: 0.2f);
    float gas    = Input.GetTrigger(GamepadAxis.RightTrigger);   // 0 to 1
    if (Input.GetGamepadButtonDown(GamepadButton.A)) Jump();
    float rawX   = Input.GetGamepadAxis(GamepadAxis.LeftX);      // raw -1 to 1
    string name  = Input.GetGamepadName(0);
}

Input.OnGamepadConnected    += slot => Log.Info(quot;Pad {slot} connected");
Input.OnGamepadDisconnected += slot => Game.Pause();
EnumValues
GamepadButtonA, B, X, Y, LeftBumper, RightBumper, Back, Start, Guide, LeftStick, RightStick, DPadUp, DPadRight, DPadDown, DPadLeft. On PlayStation pads A is Cross, B Circle, X Square, Y Triangle.
GamepadAxisLeftX, LeftY, RightX, RightY (-1 to 1, down is positive), LeftTrigger, RightTrigger. GetLeftStick / GetRightStick flip Y so up is positive; GetTrigger maps to 0–1.

The Android back button

Subscribing to Application.OnBackPressed takes over the system back button and the back gesture. What back means is then up to you:

void OnCreate()                  { Application.OnBackPressed += OnBack; }
public override void OnDestroy() { Application.OnBackPressed -= OnBack; }

float m_LastPress = -10f;

void OnBack()
{
    if (m_Popup.Active)
        m_Popup.Active = false;                                // back closes the top thing first
    else if (Time.ElapsedTime - m_LastPress < 2f)
        Application.Quit();                                    // second press within 2 s leaves
    else
        m_LastPress = Time.ElapsedTime;                        // show "press back again to quit"
}

With nobody subscribed, Android's normal behaviour runs (the game closes cleanly). A handler that throws is logged and the press is still taken, so a script bug never closes the game on the player. Android only.

Ignoring taps on the UI

Input.IsPointerOverUI() is true when the UI took the pointer this frame (a button, or an image with Raycast Target on). Check it before treating a click or tap as gameplay.