Chapter 7: OpenTK.Input

[Todo: describe keyboard and joystick input]

OpenTK provides two distinct methods for mouse input.

  1. OpenTK.Input.Mouse, which provides low-level access to mouse motions. It is independent of windows/displays and provides raw (i.e. non-accelerated) values, if possible. Use this for games that require mouse motions not confined to the display, for instance first-person shooters and 3d games with mouse-controlled cameras.

    OpenTK.Input requires OpenTK 1.1 or higher. Its methods are thread-safe and may be used from any thread.

  2. GameWindow.Mouse* events, which are equivalent to WinForms/WPF/GTK# mouse events. The values are accelerated by the OS and are reported in window coordinates. Use this for menu/UI navigation and games that require absolute coordinates, for instance and 2d games.

    Mouse events require OpenTK 1.0 or higher. They are not thread-safe and may only be used on the thread that created the GameWindow.

OpenTK.Input.Mouse

You can move the mouse cursor using OpenTK.Input.Mouse.SetPosition(x, y). This method will not generate GameWindow events. This method may generate GLControl events, depending on the operating system.

Use Mouse.GetState() to retrieve the aggregate state of all connected mice.

Use Mouse.GetState(int) to retrieve the state of the specified mouse.

To check whether a button is pressed:

using OpenTK.Input;
 
var mouse = Mouse.GetState();
if (mouse[MouseButton.Left])
{
    // Left mouse button is pressed
}

To check whether a mouse button is not pressed:

using OpenTK.Input;
 
var mouse = Mouse.GetState();
if (!mouse[MouseButton.Left])
{
    // Left mouse button is not pressed
}

To check whether the mouse state has changed:

using OpenTK.Input;
 
MouseState current, previous;
 
void UpdateMouse()
{
    current = Mouse.GetState();
    if (current != previous)
    {
        // Mouse state has changed
        int xdelta = current.X - previous.X;
        int ydelta = current.Y - previous.Y;
        int zdelta = current.Wheel - previous.Wheel;
    }
    previous = current;
}

To get the mouse coordinates:

using OpenTK.Input;
 
var mouse = Mouse.GetState();
int x = mouse.X;
int y = mouse.Y;
int z = mouse.Wheel;

Note that these mouse coordinates do not correspond physically to the monitor and should only be used for relative motion. Code that requires physical coordinates should use GameWindow or GLControl mouse events instead.

GameWindow Mouse Input

Use these methods if you want the exact mouse position on the screen. Some examples include:

           this.Mouse.ButtonUp += (object sender, MouseButtonEventArgs buttonEvent) => {
                Console.WriteLine("Mouse button up: " + buttonEvent.Button + " at: " + buttonEvent.Position);
            };

or

            Vector2 pos = new Vector2(this.Mouse.X, this.Mouse.Y);

Where "this" is a GameWindow.

Keyboard Input

OpenTK offers two keyboard input mechanisms:

  1. A stateless polling mechanism in OpenTK.Input.Keyboard
  2. An event-based mechanism in GameWindow

The first mechanism is independent of any GameWindow (you can use it with GLControl) and supports multiple keyboard devices.

// get the combined state of all keyboard devices:
var state = OpenTK.Input.Keyboard.GetState();
 
// get the state of the second keyboard device:
var state = OpenTK.Input.Keyboard.GetState(1);
 
if (state[Key.Up])
    ; // move up
if (state[Key.Down])
    ; // move down

The second mechanism is bound to a specific GameWindow and does not distinguish between different keyboard devices:

GameWindow win = new GameWindow();
 
// Use for game input
// Independent of keyboard layout
win.KeyDown += (sender, e) =>
{
    switch (e.Key)
    {
        case Key.Left:
            // move left
            break;
        ...
        case Key.Space:
            // start charging weapon
            break;
    }
};
win.KeyUp += (sender, e) =>
{
    switch (e.Key)
    {
        case Key.Space:
            // fire charged weapon
            break;
        ...
    }
};
 
// Use for text input
// Depends on keyboard layout
win.KeyPress += (sender, e) =>
{
    Console.WriteLine(e.KeyChar);
};

A KeyDown event is fired as soon as any keyboard key is pressed. Conversely, a KeyUp event is fired as soon as any keyboard key is released. Both are independent of the current keyboard layout!

A KeyPress event is fired whenever a completed character is formed. It is meant for text input and depends on the current keyboard layout.

The distinction between KeyDown and KeyPress is important, because not all keys generate text input. For instance, the accent key on a greek keyboard layout latches the accent character "΄" but does not generate a KeyPress event until a vowel is typed (e.g. "ά"). If a consonant is typed, then two KeyPress events are generated (e.g. "΄β").

On a typical 101-key PC keyboard, the sequence of events would be as follows:

// press ΄
KeyDown:  Key.Quote
KeyUp:    Key.Quote
// press α
KeyDown:  Key.A
KeyPress: ά
KeyUp:    Key.A
 
// press ΄
KeyDown:  Key.Quote
KeyUp:    Key.Quote
// press β
KeyDown:  Key.B
KeyPress: ΄
KeyPress: β
KeyUp:    Key.B

Control keys (i.e. keys where Char.IsControl() returns true) do not generate KeyPress events at all. Escape is one of those.

Note that you need OpenTK 1.1 beta4 or newer to use GameWindow keyboard events successfully.

In previous versions, replace with:

win.Keyboard.KeyDown += (sender, e) => ...
win.Keyboard.KeyUp += (sender, e) => ...

In this case, KeyPress events may not work correctly.

GamePad Input

OpenTK provides a simple-to-use, stateless GamePad input API under OpenTK.Input.GamePad. The API is modeled after XNA GamePad, so users of the latter will be comfortable using the OpenTK version and vice versa.

A GamePad is defined as an input device with a specific, well-defined layout:

  • One directional pad
  • Two thumb sticks with four axes of movement (left x-y, right x-y).
  • Two analogue triggers
  • Four main buttons (A, B, X, Y)
  • Up to seven auxiliary buttons (start, back, guide, left shoulder, right shoulder, left stick, right stick)

A given input device can be missing some of these capabilities. You can use GamePad.GetCapabilities() to retrieve the capabilities of a specific device:

for (int i = 0; i < 4; i++)
{
    var caps = GamePad.GetCapabilities(i);
    if (caps.HasAButton)
        ; // do something
 
    // Print all capabilities for this GamePad
    Console.WriteLine(caps.ToString());
}

You can retrieve the current state of a device using GamePad.GetState():

for (int i = 0; i < 4; i++)
{
    var state = GamePad.GetState(i);
    if (state.Button.A == ButtonState.Pressed)
        ; // do something
 
    // Print the current state for this GamePad
    Console.WriteLine(state.ToString());
}

To detect changes in the state of a GamePad, store and compare the new state with the previous state:

GamePadState old_state;
GamePadState state = GamePad.GetState(0);
 
if (state != old_state)
{
    if (state.Button.A == ButtonState.Pressed && old_state.Button.A == ButtonState.Released)
        ; // Button A was just pressed
    else if (state.Button.A == ButtonState.Pressed && old_state.Button.A == ButtonState.Pressed)
       ; // Button A is held
    else if (state.Button.A == ButtonState.Released && old_state.Button.A == ButtonState.Pressed)
       ; // Button A was just released
    else
       ; // Button A is not pressed
 
    // Update state for the next frame
    old_state = state;
}

Joystick Input

OpenTK provides a stateless, low-level Joystick input API under OpenTK.Input.Joystick.

Each connected joystick is identified using an integer index between 0 and a platform-specific maximum. When a joystick is connected or reconnected, it is assigned the first available index. When a joystick is disconnected, its index is marked as available without affecting the indices of other joysticks.

In OpenTK, joysticks are modeled as collections of axes and buttons. These axes and buttons do not have any inherent meaning attached. Indeed, the exact layout of a single joystick device may differ when connected to a different platform or using different drivers. For this reason, applications should allow users to configure joystick layouts according to their preferences.

If you prefer developing against a deterministic layout, consider using OpenTK.Input.GamePad instead.

Use Joystick.GetCapabilities to retrieve the number of axes and buttons available at a specific joystick index:

for (int i = 0; i < 4; i++)
{
    var caps = Joystick.GetCapabilities(i);
    if (caps.IsConnected)
    {
        Console.WriteLine("Joystick {0}:", i);
        Console.WriteLine(caps.AxisCount);
        Console.WriteLine(caps.ButtonCount);
    }
}

Use Joystick.GetState to poll the current state of a joystick device:

for (int i = 0; i < 4; i++)
{
    var state = Joystick.GetState(i);
    if (state.IsConnected)
    {
        float x = state.GetAxis(JoystickAxis.Axis0);
        float y = state.GetAxis(JoystickAxis.Axis1);
        if (state.GetButton(JoystickButton.Button0))
            ; // do something
 
        // Print the current state of the joystick
        Console.WriteLine(state);
    }
}