In this part we will be covering pretty much everything you'll need to know about getting and handling input in OpenTK. The first thing I want to do is review what options you have for receiving input from various devices and the stuff you can do right off the bat from that. However after that I want to go into how I usually like to set up my input structure for games. This second section will be optional and is not needed so feel free to skip it if you wish.
Step 1: Keyboard Input
There are essentially two different ways to go about getting keyboard input in OpenTK. The first is an event based approach and the second is using KeyboardStates. We're going to look at them in order and then weigh the pros and cons.
Getting input through events is quite easy as long as you have a reference of the GameWindow class. If you've looked through the class you will notice it has three events called "KeyDown", "KeyUp", and "KeyPress". If we simply make some event handlers for these like we did for the Load, Update, and RenderFrame events we should get something as follows.
Now to get a feel for the functionality of these events let's add some console output when each one of them gets called.
If we then run this and press a few buttons you should see something like this show up in the console. Note that all three of these functions DO NOT get called when the GameWindow is not in focus. This can be seen as good or bad depending on how you want your game to work. Another thing to notice is that these events will repeatedly fire if you hold down a key for a second or so. This is usually undesirable in a video game unless you are trying to get text input.
At first glance it might seem that the KeyDown and KeyPress functions work exactly the same. However they are actually quite different. If you look at the event handlers we created you will notice that the event args are two different types: KeyboardKeyEventArgs and KeyPressEventArgs. And if we go look at the members of each we will find a drastic difference.
The KeyPressEventArgs for the KeyPress event has only one really useful member called "KeyChar". This is a simple char that represents the TEXT of whatever key we pressed. This will also take into account things like capitalization when pressing shift or caps-lock is on and alternate characters for each key. This makes the KeyPress event really useful for text input into your game, but almost worthless for regular key input.
Now if we look at KeyboardKeyEventArgs we'll see that we have a whole lot more members.
The one we looked at in this example was the "Key" member which tells us, using the "Key" enum in OpenTK, which key was actually pressed to fire the event.
There are also the "Control", "Shift" and "Alt" bools that tell us whether each of those keys was being held when this key was pressed. These relate closely to the "Modifiers" member which is a bit-wise flag holder for all three of those in one variable. This makes it easy to pass these flags around between functions using only one variable. You can then check a flag using something like if (e.Modifiers.HasFlag(KeyModifiers.Shift)). You can also use this to easily check that all three modifiers are off: if (e.Modifiers == 0).
Moving on, we also have a really important boolean called "IsRepeat". This will tell us whether the current key press was called because of an actual press by the player or if it was a auto-repeat call made by the OS when holding a key down for more than the allotted time. If we only process the input when this flag is false then we can get a true reading of when the player pressed the key.
We also have the "ScanCode" uint member which represents the scan code passed by the keyboard. This can vary from keyboard to keyboard so we won't be using this much.
And lastly we have a reference to the KeyboardState with the "Keyboard" member. This is nice if you want to do any checks to other keys.
This leads us nicely into our next form of getting Keyboard input which is using KeyboardStates. The KeyboardState class holds all of the keys that were being pressed at a specific time. You can think of it like taking a snapshot of the keyboard and saving it to a class. We can then run tests on this state to see which keys where being pressed and then act accordingly.
Now there a few different ways to get the Keyboard State in OpenTK. One of the methods we just saw was when it was passed as part of the event arguments for KeyDown and KeyUp events. However we can also get it directly, and at any time, by calling Keyboard.GetState().
Note that you can also pass an integer to GetState that will represent which keyboard to get the state of. Unless you are planning on having the player(s) use multiple keyboards then this is pointless and will default to 0 if not passed. However, this becomes important later when we are doing Joystick input since it follows the same pattern.
Let's test this out by adding the following code to the Update function.
If we then go ahead and run this you'll notice that when we press enter we get a flurry of console output.
Note that with the current way we are getting the KeyboardState you can also get input even when the GameWindow is not selected. This can be good or bad depending on what you want. Generally you should check window.Focused before doing anything if you want to only get input when the window has focus.
This happens because the keyboard state has no way of telling whether a key was just pressed or already being held. Each time we call it it simply tells us which keys are down and nothing about the past state of those keys. That means every time the update function gets called we check again and make another console output if the key is down.
This type of input is desirable in some cases but not in others. For example we could use this to check whether we should move the player forward when the up arrow is being pressed. Since this needs to happen every step that it's being pressed, regardless of whether it was just pressed or not. However let's say we want to throw a grenade when the player pressed G. If we tried to do that now you would get a grenade created every time Update is called, resulting in a chain of grenades just by holding down G. Maybe good for some games but most of the time you are going to want different functionality.
One way that we can remedy the situation is to hold a KeyboardState from the last frame to check against. That way if a key is being pressed this Update frame we can check whether it was also being pressed last frame. If not, then we can know that the player just started pressing it. Here's an example.
If you run this now you'll see that the console output only happens when you PRESS the key and not when holding it. We can also do the opposite and check whether a key was just released.
If we go back and look at the members of our GameWindow again you might notice that it has a member called "Keyboard". This is a reference to the actual KeyboardDevice. The devise doesn't really help us game much functionality however since it essentially has the same events as the GameWindow gave us minus the KeyPress event. However, it does give us a little more info about the keyboard device like how many keys, LEDs, and function keys it has as well as a few other things.
Now with those different ways of getting input outlined, let's highlight a few pros and cons.
- Easy to get Key Presses individually
- Lot's of information available about modifiers and keyboard state readily available
- Only happens when the GameWindow has focus
- KeyPress event is ideal for text input since it takes care of capitalization and alternate characters for us.
- All key pressed will register, regardless of how fast our game is running since it's not dependent on the game loop.
- No way of checking whether a key is down each frame
- All the code is outside our Update loop and so we don't know when the code will get called compared to the things that happen in our update.
- Only happens when the GameWindow has focus
Using Keyboard States
- Does not depend on GameWindow having focus
- Easy to check whether a key is being held each frame
- Allows us to check whether a key is down AND whether it was just pressed or released (with a bit of work)
- All the code is inside our Update function and can be handled in a specific order.
- Does not depend on GameWindow having focus
- A little more difficult to check whether a key was just pressed or just released
- Requires manual checking of modifiers
- Not ideal for text input
- If game runs slow then some input may be lost if not held down when our Update function is called.
As you can see it's not really an easy decision and it really depends on which type of code structure you want. Later we will make a class that will combine the best of both worlds, in a sense, and handle a lot of the nitty-gritty for us. For now though, we are going to leave it at that and move on.
Step 2: Mouse Input
Mouse input is actually largely similar to keyboard input. There are the the "MouseDown" and "MouseMove" events that are available in GameWindow and GameWindow.Mouse. There is also a MouseState class that can be obtained by calling Mouse.GetState.
However there is also a lot of other functionality when it comes to the mouse. There are the "MouseMove", "MouseEnter", "MouseLeave", and "MouseWheel" events in the GameWindow. There's also the "X", "Y", "ScrollWheelValue", "Wheel", and "WheelPrecise" members in the MouseState class. And there's also the "GetCursurState" function that can be called to get a MouseState instance.
All of these extra members are tied together in largely the same way that the keyboard functionality is tied. However there are few key differences that I would like to point out about the cursor position and mouse scrolling value. If you need more help with any of the other functions and members that I don't explain, I recommend either reading the documentation on OpenTK or simply experiment with your own test application and see how each value behaves.
Now the first thing you're probably going to want to do is get the mouse position. This is really easy but also really hard. The problem stems from the fact the the mouse' position is actually relative to the top left of the physical monitor displaying windows. Generally, in our game, we are going to want the mouse position relative to the top of our render area. This can be a little hard to obtain on your own since it's dependent on both the position of your window on the screen AND the size of the borders of your window.
Now there is a way to have OpenTK handle this for you but it's dependent on where you get your cursor position value from. For example if you take the values attached to the event arguments of MouseMove in GameWindow, you will get coordinates relative to the top left of the screen like we want. However if you take coordinates attached to the state passed back by Mouse.GetState you will get coordinates that aren't really relative to any point of reference but will have true change in value, even when the user is moving the mouse into the side of the screen. And again if you take the values passed back by Mouse.GetCursorState it will be relative to the top left of your WINDOW instead of the render portion of that window meaning it will be offset by the size of the border on your window.
As a beginner in OpenTK these differences frustrated me to no end and I had a hard time finding documentation on them. Hopefully this tutorial will serve as a guide to help you skip many hours of testing that I've had to go through to figure out these differences.
Now, since we're working in 2 dimensions we are generally going to want coordinates that are relative to the top left of our render area. One method of obtaining this value is to save it to a variable every time MouseMove is called so that you can use it later.
Another method is to take the value from the Mouse member in our GameWindow.
And lastly we can also use the values passed to us by Mouse.GetCursorState along with window.PointToClient to produce the value we want.
Note that the first two methods will both "leave" the cursor behind when the actual mouse cursor leaves the window. This can cause false readings if you are trying to determine if the mouse is inside the game window. However, the last method will correctly give you values that are negative and values that are past your width or height.
In this tutorial we are going to be using the last method here to get the cursor position since it can give us a more "True" sense of where the cursor really is.
Let's move on to talk about the scroll wheel. Most mice now days have a scroll wheel on them that allows you to scroll through web pages and zoom in and out in various applications. If you are planning on using this scroll wheel in your game let me give you a bit of a run-down on how this actually is represented in OpenTK. For demonstration purposes let's set up something like the following.
Let's try running this and scrolling the wheel a bit. Pay close attention the the values as you do. I got something like this.
There are couple of things I want to point out with this demonstration. Note that the numbers on the left are Delta and Value and the numbers on the right are DeltaPrecise and ValuePrecise. It's important to understand the relation.
Notice the DeltaPrecise value on this mouse is around 0.93333... but the Delta value is simply one. You might think that Delta could be used to count how many "steps" the wheel has turned in any direction however that's not actually the case. Notice that when I turned the wheel for the 8th time the Value stayed at 7 even though I had turned the mouse. This is caused by the fact that Delta and Value are not separate values to be used when you want clean numbers but are actually just rounded integers of DeltaPrecise and ValuePrecise.
This is important to notice because if you are making some type of functionality that operates when the scroll wheel has incremented one value higher or lower you might miss some inputs every now and then when this round occurs and doesn't actually step forward a full integer amount.
This can be a bit frustrating at times but there's a good reason that it works this way. Up until this point I have assumed you have a mouse who's wheel rotates in increments and if that's the case then you might be a little confused. However there are many mice that actually support delta values that can change dependent on how fast you scroll the wheel. And there are also mice that don't "lock" the wheel into incremental positions. Because of these differences in hardware it can be hard to standardize what "one increment" of the mouse wheel really means.
If you using the scroll wheel as input then keep in mind that for the best experience you are going to want to handle floating point deltas and values that won't necessarily be one step up or on step down.
Step 3: Joystick Input
Once again, Joystick input is going to work largely the same way that keyboard and mouse input works. However, and again, I would like to point out some differences here as well.
First thing you might notice is there is no JoystickDown or any similar events in our GameWindow class. Along with that the Joysticks member of our GameWindow is actually an array of JoystickDevices and those devices don't have any events that we can attach onto the same way we did before. This means that the event driven side of Joystick input is not going to be an option.
Another difference is that the Joystick.GetState function can't be called without passing an integer value as an index of which controller's state you want. This makes sense since there can and will more likely be multiple joysticks plugged into one computer. These indexes can range from 0-3 resulting in capability to support up to 4 controllers at once. It's also important to check whether the controller you are polling actually exists (is plugged in) or not. You can do this by checking the "IsConnected" member of the JoystickState that is passed back.
There's also another static function in the Joystick class called "GetCapabilities". Since each type of joystick has a different number of buttons, axises, and what are called "hats" we need to know how many of each are supported. This is where the GetCapabilities function comes in handy as it tells us which buttons, axises, and hats we can actually use.
The hardest part about supporting Joystick input is dealing with all of this variability. We cannot assume from the start that the joystick will have 4 axises for movement and looking. We also have no way of knowing how each of these buttons are represented on the joystick itself. An axis could be a rotation value from a accelerometer or one of two axises on an analog stick or a slider or a knob or ect...
As you can see, this starts to become a problem. Unless you know exactly what type of joystick devices your players are going to be using it's going to be difficult to make hard coded joystick support. The best, and practically the only, real way to solve this problem is to allow the user to set up the control mappings themselves. This is certainly possible, and most games have this functionality in the form of a "Controls" screen but what you might not realize is this is actually a bit hard to implement easily and effectively. It will require a layer of abstraction and classes that are a bit out of the scope of this tutorial series. I might cover the method that I use to overcome this problem later, but for now we are going to leave it at that.
Step 4: GamePad Input
Now before we get started with GamePads I must admit that I still am a little unsure about how GamePads and Joystick relate and differ. They are a bit ambiguous and I can only assume this is due to some type of backwards compatibility that was implemented earlier on in the development of OpenTK. However, that being said I am going to try and explain the way that I have come to understand the two's relation.
One thing to note is that a single input device can usually act as both a Joystick and a GamePad. So often times if you have 1 Joystick connected it will also register 1 GamePad connected as well. I am not sure if this is always the case but I haven't been able to find an input device that acts as only one or the other. Because of this duality you can actually usually choose to just use one or the other input class in your game.
For the most part GamePads work the same way Joysticks do. They have certain capabilities and can be accessed by polling the state of a certain indexed one. However, GamePads seem to be a sort of a step above Joysticks since they seem to try to define what each buttons purpose is so that you can re-introduce a bit of expected behavior again. For example, the GamePadCapabilities class contains booleans that represent whether the GamePad has buttons called "A", "B", "X", "Y", "LeftShoulder", "LeftTrigger", and many other common buttons you might expect to be on a standard controller. This can be rather helpful when deciding when button inputs to map to which controls in game and is often preferable to using Joysticks.
Other than that difference though the two classes work in relatively the same way as far as I understand. If someone has more information on this, feel free to send me an email or comment below.
That pretty much wraps up this part of the tutorial. We didn't do anything to our project that we need to keep so can revert everything to what we had in the previous tutorial if you want to follow along exactly.