Krotox Engine › Docs › Scripting API › Physics & Input
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 inOnUpdate.OnFixedUpdatecan 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 inOnFixedUpdateif needed.
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.
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.
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.
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.
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; }
}
| Handler | Called 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. |
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.
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.)
| Setting | Default |
|---|---|
Gestures.MinSwipeInches | 0.30 |
Gestures.MaxSwipeSeconds | 0.60 |
Gestures.TapSlopInches (how far a finger may wander and still count as still) | 0.12 |
Gestures.DoubleTapSeconds | 0.35 |
Gestures.LongPressSeconds | 0.50 |
Gestures.EdgeInches | 0.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.
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);
}
}
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.
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();
| Enum | Values |
|---|---|
GamepadButton | A, 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. |
GamepadAxis | LeftX, LeftY, RightX, RightY (-1 to 1, down is positive), LeftTrigger, RightTrigger. GetLeftStick / GetRightStick flip Y so up is positive; GetTrigger maps to 0–1. |
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.
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.