Input Polling / Input Controller

One of the basic features a game needs is the control of inputs. And it is not done with just checking which key is pressed or where the mouse is. Most games need much more than just reacting on clicks.

Imagine a combat game, where you allow users to have key-combos for special attacks and animations.

Therefor we provide the easy to use TGorillaInputController component to access keyboard, mouse and gamepad feedbacks. Also in combination!

Input Devices

The input controller sets up handlers for keyboard, mouse and gamepad, and enables all by default.

FInput.Supported := [TGorillaInputDeviceType.Keyboard, TGorillaInputDeviceType.Mouse,
  TGorillaInputDeviceType.GamePad];

If you do not need any of the device, you can disable them, by reducing the “Supported” enumeration set. For example, if you only want to check for gamepad events:

FInput.Supported := [TGorillaInputDeviceType.GamePad];

HotKey

A HotKey is a combination of simultanously active inputs. Each HotKey can manage inputs from keyboard, mouse and/or gamepad. An input is a hardware message:

  • For keyboards, inputs are the pressed keys
  • For mouse, inputs are pressed mouse buttons
  • For gamepad, inputs are pressed gamepad buttons

You can define up to 4 inputs per device. Which means you can handle up to 12 simultanously active input messages.

In case you define an input in two different HotKeys, the system will detect the more important Hotkey. The importance is defined by the number of set inputs.

If a HotKey was detected, the controller will call the OnTriggered event and/or an attached TAction instance.

You are allowed to setup your HotKeys at design- and runtime.

LockTime

Set the LockTime property (in milliseconds) to suppress a hotkey for a specific time. Because input commands come very often, f.e. if a button on the gamepad was pressed. As long as you hold the button the system will recognize the hotkey for this button. For sequence detection this may be a problem. Therefor we want to lock the gamepad hotkey for a certain time.

DesignTime

Because HotKeys were implemented by the TCollection component of Delphi, you can setup your combinations at design time.

  1. Drag a TGorillaInputController component onto your form and doubleclick the “HotKeys” property.
  2. In the Delphi Collection-Editor you can now add a new TGorillaHotKeyItem.
  3. Type in a unique name to identify your hotkey, create a OnTriggered event or attach a TAction component to it.
  4. In the next step we should add the input combination by double clicking the “Combinations” property.
  5. Here we can now add a new TGorillaHotKeyInputItem.
  6. In the item select the “Kind” of device.
  7. and add the message input code (tables below)
  8. Repeat it for any further inputs you wish to combine.

Runtime

uses
  Gorilla.Controller.Input,
  Gorilla.Controller.Input.Consts;
 
procedure TForm1.DoOnHotKey(const AItem : TGorillaHotKeyItem; const ACurrentInput : TGorillaHotKeyRaw);
begin
  DebugOutput(Format('>>> HotKey: %s <<< === %s', [AItem.DisplayName, ACurrentInput.ToString()]));
end;
 
procedure TForm1.FormCreate(Sender: TObject);
var 
  FInput : TGorillaInputController;
  LHotKey : TGorillaHotKeyItem;
 
  [...]
 
  FInput := TGorillaInputController.Create(Self);
 
  LHotKey := FInput.AddHotKey('TEST1');
  LHotKey.OnTriggered := DoOnHotKey;
  LHotKey.AddInput(TGorillaInputDeviceType.Keyboard, Ord(GORILLA_INPUT_KEY_ALT));
  LHotKey.AddInput(TGorillaInputDeviceType.Keyboard, Ord(GORILLA_INPUT_KEY_C));
  LHotKey.AddInput(TGorillaInputDeviceType.Keyboard, Ord(GORILLA_INPUT_KEY_RETURN));
end;
 
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  FInput.DisposeOf();
  FInput := nil;
end;

Persistent Messages

Besides the HotKey settings the component supports persistent/continuous messages. A persistent message is a continuously notified message from the system and not a temporary message like a click or key-press.

Persistent messages can not be notified by HotKeys. You need to request those separately.

Supported persistent messages are:

Message-Code Description DataType
GORILLA_INPUT_MOUSE_POSITION $100001latest mouse position TPoint
GORILLA_INPUT_GAMEPAD_TRIGGER_LEFT $200020gamepad left trigger value TPoint
GORILLA_INPUT_GAMEPAD_TRIGGER_RIGHT $200021 gamepad right trigger value TPoint
GORILLA_INPUT_GAMEPAD_THUMBSTICK_POS_LEFT $200030 gamepad left thumbstick position TPointF
GORILLA_INPUT_GAMEPAD_THUMBSTICK_POS_RIGHT $200031 gamepad right thumbstick position TPointF

You can request those messages at runtime, like in the following code:

var LMPos  : TPoint;
    LMsg   : TGorillaInputMessage;
begin
  // request latest mouse position
  if FInput.GetPersistentMessage(TGorillaInputDeviceType.Mouse, GORILLA_INPUT_MOUSE_POSITION, LMsg) then
  begin
    LMPos := LMsg.Data.AsType<TPoint>();
  end
  else LMPos := TPoint.Zero;
 
  // show mouse position in form caption
  Self.Caption := Format('Gorilla 3D - Mouse: [%d;%d]', [LMPos.X, LMPos.Y]);

Input-Codes for HotKeys

All available InputCodes (TGorillaInputCode) for HotKeys are defined in the Gorilla.Controller.Input.Consts unit.

Keyboard

Constant Char Decimal
GORILLA_INPUT_KEY_A'A'65
GORILLA_INPUT_KEY_B'B'66
GORILLA_INPUT_KEY_C 'C'67
GORILLA_INPUT_KEY_D'D'68
GORILLA_INPUT_KEY_E'E'69
GORILLA_INPUT_KEY_F'F'70
GORILLA_INPUT_KEY_G'G'71
GORILLA_INPUT_KEY_H'H'72
GORILLA_INPUT_KEY_I'I'73
GORILLA_INPUT_KEY_J'J'74
GORILLA_INPUT_KEY_K'K'75
GORILLA_INPUT_KEY_L'L'76
GORILLA_INPUT_KEY_M'M'77
GORILLA_INPUT_KEY_N'N'78
GORILLA_INPUT_KEY_O'O'79
GORILLA_INPUT_KEY_P'P'80
GORILLA_INPUT_KEY_Q'Q'81
GORILLA_INPUT_KEY_R'R'82
GORILLA_INPUT_KEY_S'S'83
GORILLA_INPUT_KEY_T'T'84
GORILLA_INPUT_KEY_U'U'85
GORILLA_INPUT_KEY_V'V'86
GORILLA_INPUT_KEY_W'W'87
GORILLA_INPUT_KEY_X'X'88
GORILLA_INPUT_KEY_Y'Y'89
GORILLA_INPUT_KEY_Z'Z'90
GORILLA_INPUT_KEY_0'0'48
GORILLA_INPUT_KEY_1'1'49
GORILLA_INPUT_KEY_2'2'50
GORILLA_INPUT_KEY_3'3'51
GORILLA_INPUT_KEY_4'4'52
GORILLA_INPUT_KEY_5'5'53
GORILLA_INPUT_KEY_6'6'54
GORILLA_INPUT_KEY_7'7'55
GORILLA_INPUT_KEY_8'8'56
GORILLA_INPUT_KEY_9'9'57
GORILLA_INPUT_KEY_ARROW_LEFT#3775
GORILLA_INPUT_KEY_ARROW_UP#3872
GORILLA_INPUT_KEY_ARROW_RIGHT#3977
GORILLA_INPUT_KEY_ARROW_DOWN#4080
GORILLA_INPUT_KEY_SPACE' '32
GORILLA_INPUT_KEY_EXCL'!'33
GORILLA_INPUT_KEY_QUOTE'“'34
GORILLA_INPUT_KEY_HASH'#'35
GORILLA_INPUT_KEY_PERCENT'%'37
GORILLA_INPUT_KEY_STAR'*'42
GORILLA_INPUT_KEY_PLUS'+'43
GORILLA_INPUT_KEY_COMMA','44
GORILLA_INPUT_KEY_MINUS'-'45
GORILLA_INPUT_KEY_DOT'.'46
GORILLA_INPUT_KEY_DIV'/'47
GORILLA_INPUT_KEY_PARA'§'167
GORILLA_INPUT_KEY_DOLLAR'$'36
GORILLA_INPUT_KEY_EURO'€'128
GORILLA_INPUT_KEY_AT'@'64
GORILLA_INPUT_KEY_AND'&'38
GORILLA_INPUT_KEY_BACKSLASH'\'92
GORILLA_INPUT_KEY_BRACKETOPEN'('40
GORILLA_INPUT_KEY_BRACKETCLOSE')'41
GORILLA_INPUT_KEY_EQUAL'='61
GORILLA_INPUT_KEY_QUESTION'?'63
GORILLA_INPUT_KEY_TILDE'~'126
GORILLA_INPUT_KEY_UNDERSCORE'_'95
GORILLA_INPUT_KEY_COLON':'58
GORILLA_INPUT_KEY_SEMICOLON';'59
GORILLA_INPUT_KEY_BAR&#124;124
GORILLA_INPUT_KEY_TAGOPEN'<'60
GORILLA_INPUT_KEY_TAGCLOSE'>'62
GORILLA_INPUT_KEY_SHIFT_BACKSPACE#88
GORILLA_INPUT_KEY_SHIFT_TAB#99
GORILLA_INPUT_KEY_LINEFEED#$0A10
GORILLA_INPUT_KEY_RETURN#$0D13
GORILLA_INPUT_KEY_ESCAPE#$1B27
GORILLA_INPUT_KEY_DELETE#$7F127
GORILLA_INPUT_KEY_SHIFT_CAPS#2020
GORILLA_INPUT_KEY_SHIFT_LEFT#160160
GORILLA_INPUT_KEY_SHIFT_RIGHT#161161
GORILLA_INPUT_KEY_CTRL_LEFT#162162
GORILLA_INPUT_KEY_CTRL_RIGHT#163163
GORILLA_INPUT_KEY_ALT#164164
GORILLA_INPUT_KEY_ALTGR#165165

Mouse

Constant IndexValue
GORILLA_INPUT_MOUSE_POSITION0$100001
GORILLA_INPUT_MOUSE_LBUTTON1$100002
GORILLA_INPUT_MOUSE_LBUTTON_DBLCLK2$100003
GORILLA_INPUT_MOUSE_RBUTTON3$100004
GORILLA_INPUT_MOUSE_RBUTTON_DBLCLK4$100005
GORILLA_INPUT_MOUSE_MBUTTON5$100006
GORILLA_INPUT_MOUSE_MBUTTON_DBLCLK6$100007
GORILLA_INPUT_MOUSE_X1BUTTON7$100008
GORILLA_INPUT_MOUSE_X1BUTTON_DBLCLK8$100009
GORILLA_INPUT_MOUSE_X2BUTTON9$10000A
GORILLA_INPUT_MOUSE_X2BUTTON_DBLCLK10$10000B
GORILLA_INPUT_MOUSE_WHEEL11$10000C

GamePad

Constant IndexValue
GORILLA_INPUT_GAMEPAD_CONNECTED$1000$200001
GORILLA_INPUT_GAMEPAD_DISCONNECTED$1001$200002
GORILLA_INPUT_GAMEPAD_DPAD_UP0$200003
GORILLA_INPUT_GAMEPAD_DPAD_DOWN1$200004
GORILLA_INPUT_GAMEPAD_DPAD_LEFT2$200005
GORILLA_INPUT_GAMEPAD_DPAD_RIGHT3$200006
GORILLA_INPUT_GAMEPAD_START_BUTTON4$200007
GORILLA_INPUT_GAMEPAD_MODEBUTTON5$200008
GORILLA_INPUT_GAMEPAD_BACKBUTTON6$200009
GORILLA_INPUT_GAMEPAD_THUMBSTICK_LEFT7$20000A
GORILLA_INPUT_GAMEPAD_THUMBSTICK_RIGHT8$20000B
GORILLA_INPUT_GAMEPAD_SHOULDER_LEFT9$20000C
GORILLA_INPUT_GAMEPAD_SHOULDER_RIGHT10$20000D
GORILLA_INPUT_GAMEPAD_BUTTON_A11$20000E
GORILLA_INPUT_GAMEPAD_BUTTON_B12$20000F
GORILLA_INPUT_GAMEPAD_BUTTON_X13$200011
GORILLA_INPUT_GAMEPAD_BUTTON_Y14$200012
GORILLA_INPUT_GAMEPAD_TRIGGER_LEFT$100$200020
GORILLA_INPUT_GAMEPAD_TRIGGER_RIGHT$101$200021
GORILLA_INPUT_GAMEPAD_THUMBSTICK_POS_LEFT$200$200030
GORILLA_INPUT_GAMEPAD_THUMBSTICK_POS_RIGHT$201$200031

Sequences

HotKeys are a nice feature, but only work for simultaneous inputs.

Imagine you build a combat game, where attacks or defences are represented by input-combos. For example a user needs to press Button A, then Button B and then Button X to do roundhouse kick. Therefor sequences come in place. Sequences are a descriptive plan of hotkeys, where the order is important.

So Sequence #1

Button A + Button B + Button X

is not the same like

Button B + Button A + Button X

Notes: You are allowed declare an input multiple times in a sequence.

Each sequence can hold up to 16 HotKeys.

DesignTime

Runtime

procedure TForm1.DoOnSequence(const ASequence : TGorillaInputSequenceItem);
begin
  DebugOutput(Format('>>> Sequence: %s <<<', [ASequence.DisplayName]));
end;
 
[...]
 
var LSequence: TGorillaInputSequenceItem;
    LSeq1HK1, LSeq1HK2, LSeq1HK3 : TGorillaHotKeyItem;
 
  [...]
 
  // create hotkey #1
  LSeq1HK1 := FInput.AddHotKey('STRIDE LEFT');
  LSeq1HK1.AddInput(TGorillaInputDeviceType.Keyboard, Ord(GORILLA_INPUT_KEY_A));
 
  // create hotkey #2
  LSeq1HK2 := FInput.AddHotKey('FORWARD');
  LSeq1HK2.AddInput(TGorillaInputDeviceType.Keyboard, Ord(GORILLA_INPUT_KEY_W));
 
  // create hotkey #3
  LSeq1HK3 := FInput.AddHotKey('STRIDE RIGHT');
  LSeq1HK3.AddInput(TGorillaInputDeviceType.Keyboard, Ord(GORILLA_INPUT_KEY_D));
 
  // create the sequence and add the relevant hotkeys
  LSequence := FInput.AddSequence('TEST SEQUENCE', [LSeq1HK1, LSeq1HK2, LSeq1HK3]);
  LSequence.OnTriggered := DoOnSequence;

Feedback Events

Now that we've covered hotkeys and sequences, another way of detecting inputs are the raw feedback events of the input controller itself.

The input controller provides a few feedback events that work parallel to the mentioned hotkey or sequence detection.

EventTypeDescription
OnMouseDownTGorillaInputMouseEvent Raw mouse down event callback thrown by the mouse handler. This event will be synchronized with main thread context.
OnMouseUp TGorillaInputMouseEvent Raw mouse up event callback thrown by the mouse handler. This event will be synchronized with main thread context.
OnMouseDblClick TGorillaInputMouseEvent Raw mouse double click event callback thrown by the mouse handler. This event will be synchronized with main thread context.
OnMouseMove TGorillaInputMouseMoveEvent Raw mouse move event callback thrown by the mouse handler. This event will be synchronized with main thread context.
OnMouseWheel TGorillaInputMouseWheelEvent Raw mouse wheel event callback thrown by the mouse handler. This event will be synchronized with main thread context.
OnKeyDown TGorillaInputKeyEvent Raw key down event callback thrown by the keyboard handler. This event will be synchronized with main thread context.
OnKeyUpTGorillaInputKeyEvent Raw key up event callback thrown by the keyboard handler. This event will be synchronized with main thread context.
OnGamePadConnect TGorillaInputGamePadEventRaw callback when a new gamepad controller was connected. This event will be synchronized with main thread context.
OnGamePadDisconnect TGorillaInputGamePadEventRaw callback when a gamepad controller was disconnected. This event will be synchronized with main thread context.
OnGamePadButtonDown TGorillaInputGamePadButtonEventRaw gamepad button down callback thrown by gamepad handler. This event will be synchronized with main thread context.
OnGamePadButtonUp TGorillaInputGamePadButtonEventRaw gamepad button up callback thrown by gamepad handler. This event will be synchronized with main thread context.
OnGamePadTrigger TGorillaInputGamePadTriggerEventRaw gamepad trigger callback thrown by gamepad handler. This event will be synchronized with main thread context. AState = (GORILLA_INPUT_GAMEPAD_TRIGGER_LEFT, GORILLA_INPUT_GAMEPAD_TRIGGER_RIGHT)
OnGamePadFeedback TGorillaInputGamePadFeedbackEventRaw gamepad feeback callback for receiving thumbstick position, thrown by gamepad handler. This event will be synchronized with main thread context. AState = (GORILLA_INPUT_GAMEPAD_THUMBSTICK_POS_LEFT, GORILLA_INPUT_GAMEPAD_THUMBSTICK_POS_RIGHT)

Feedback Event Methods

Declaring a feedback event method needs further unit integration in most cases for the used types and constants:

  • Gorilla.Controller
  • Gorilla.Controller.Input.Consts
  • Gorilla.Controller.Input.Types
TypeDeclarationNotice
TGorillaInputMouseEvent procedure(ASender : TObject; AStates : TGorillaMouseStates; APos : TPointF) of object;For available mouse states, read more here
GorillaInputMouseMoveEventprocedure(ASender : TObject; AStates : TGorillaMouseStates; APos : TPointF) of object;For available mouse states, read more here
TGorillaInputMouseWheelEventprocedure(ASender : TObject; APos : TPointF; const AWheelDelta : Integer) of object;
TGorillaInputKeyEvent procedure(ASender : TObject; AKeyCode : Integer) of object;
TGorillaInputGamePadEventprocedure(ASender : TObject; AState : TGorillaInputCode; AUser : Integer) of object;
TGorillaInputGamePadButtonEventprocedure(ASender : TObject; AState : TGorillaInputCode; AButtons : TGorillaGamePadButtons) of object;For available gamepad buttons, read more here
TGorillaInputGamePadTriggerEventprocedure(ASender : TObject; AState : TGorillaInputCode; const AButtons : TGorillaGamePadButtons; AValue : Byte; AFlags : UInt64) of object;AState = (GORILLA_INPUT_GAMEPAD_TRIGGER_LEFT, GORILLA_INPUT_GAMEPAD_TRIGGER_RIGHT)
TGorillaInputGamePadFeedbackEventprocedure(ASender : TObject; AState : TGorillaInputCode; const AButtons : TGorillaGamePadButtons; APos : TPointF; AFlags : UInt64) of object;AState = (GORILLA_INPUT_GAMEPAD_THUMBSTICK_POS_LEFT, GORILLA_INPUT_GAMEPAD_THUMBSTICK_POS_RIGHT)

Types

TGorillaInputMode = (Activated, Deactivated);
 
  TGorillaMouseState = (LButtonDown, LButtonUp, RButtonDown, RButtonUp,
    MButtonDown, MButtonUp, X1ButtonDown, X1ButtonUp, X2ButtonDown, X2ButtonUp,
    LButtonDblClick, RButtonDblClick, MButtonDblClick, X1ButtonDblClick,
    X2ButtonDblClick, MouseMove, MouseWheel);
 
TGorillaMouseStates = set of TGorillaMouseState;
 
TGorillaGamePadButton = (
    None,
    // directional pad up
    DPadUp,
    // directional pad down
    DPadDown,
    // directional pad left
    DPadLeft,
    // directional pad right
    DPadRight,
    // start button
    StartButton,
    // mode button
    ModeButton,
    // back button
    BackButton,
    // left thumbstick pressed
    LeftThumbStick,
    // right thumbstick pressed
    RightThumbStick,
    // left shoulder button
    LeftShoulder,
    // right shoulder button
    RightShoulder,
    // button A
    ButtonA,
    // button B
    ButtonB,
    // button X
    ButtonX,
    // button Y
    ButtonY
    );
 
TGorillaGamePadButtons = set of TGorillaGamePadButton;

Debugging-Issue

On Windows platform we're using low level hooks for keyboard and mouse detection, to support high-performance and accuracy.

But this comes with a major issue: In many cases debugging is getting very slow, because hooks have a 1000ms timeout by default and hooks not getting paused during debugging.

This leads to lagging mouse and keyboard input.

Currently this can not be solved out of the box!

But you can add a registry entry to your system, like this:

  1. Open RegEdit.exe (Windows)
  2. Move to the path: “HKEY_CURRENT_USER\Control Panel\Desktop\
  3. Add a new value of type DWORD with the name “LowLevelHooksTimeout
  4. Enter a value which stands for the timeout in milliseconds (f.e. 50ms)
  5. A system restart is required!

Next: Pathfinding