Handling User Input
From C4 Engine Wiki
In this tutorial, you will learn the basic concepts behind handling user input from the keyboard and mouse. This tutorial builds off concepts previously established in the Hello World tutorial and will attempt to stay OS neutral. If you haven't yet, look over that tutorial before continuing. The program we will create will react to the WASD keys and the mouse button. Upon each key stroke, a message will display with the count of how many times the button was pressed. Upon a mouse click, a 'zap!' message will appear.
Contents |
Preperations
Before we begin the tutorial, you should have a new blank C4 project opened and ready to code. If you're confused on how to do that, please return to the Hello World tutorial. Though you may use what ever project and class name that you'd like, for the purposes of this tutorial the project will be named ActionsDemo while the main game class will be named Game. Don't forget to update game.cfg with the new project name for $gameModuleName.
Actions
When a user hits a key or button, the program's corresponding reaction is referred to as an action. Each possible key press will be assigned to it's own action. Since our program will read four keys (WASD) and a mouse button, we will have five total actions. To define these actions, we'll create an enum list which must correlate with our configuration file. The guts of these actions will then be coded with the given C4 class Action.
Defining
First we must decide upon the name for each of our actions. We can use anything we want so long as its value is four characters long. For our program we will use the following:
enum
{
kActionForward = 'frwd',
kActionBackward = 'bkwd',
kActionLeft = 'left',
kActionRight = 'rght',
kActionZap = 'zap!'
};
This will be a globally defined enum. If you're wondering why each enumeration begins with a 'k', please refer to C4's Coding Conventions. As you can see, each four character value is almost like an abbreviation for its given action. These will be very important to remember for our configuration file. Don't worry about typing this code yet. The entire contents of each game.h and game.cpp will be given later.
Configuration File
The configuration file, input.cfg, is where our actions are binded with its associative button or key. Its location varies based on each operating system. Please refer to File Locations to learn where yours is. The file input.cfg will define a device followed by the binding for its keys. Here we can add, remove, and modify as needed for our program. Scanning through the file, you'll see that most of our action's are already binded by default:
bind "W" %frwd; bind "A" %left; bind "S" %bkwd; bind "D" %rght;
Notice how the four character coding after each '%' matches with a value in our enumerations. This is extremely important, else the key bindings will not work as expected. We still need to bind our left mouse button to the zap! action. Look for the line that starts with:
bind "Button 0"
Its probably binded to the action %fire. Go a head and change that line to read as:
bind "Button 0" %zap!
With our configuration file completed, lets move on to the coding.
The Code
Copy the following into your Game.h file:
#ifndef GAME_H #define GAME_H #include "C4Application.h" #include "C4Input.h" #include "C4Interface.h" #include "C4String.h" extern "C" { C4MODULEEXPORT C4::Application* ConstructApplication(); } using namespace C4; enum { kActionForward = 'frwd', kActionBackward = 'bkwd', kActionLeft = 'left', kActionRight = 'rght', kActionZap = 'zap!' }; namespace HW { class GameAction : public Action { private: unsigned long count; String<> defaultText; TextWidget *tWidget; public: GameAction(unsigned long type, const char *text, unsigned int xLoc, unsigned int yLoc); ~GameAction(void); void Begin(void); void End(void); }; class Game : public Singleton<Game>, public Application { private: GameAction *forwardAction; GameAction *backwardAction; GameAction *leftAction; GameAction *rightAction; GameAction *zapAction; public: Game(); ~Game(); void Display(TextWidget* tWidget); void UnDisplay(TextWidget* tWidget); }; extern Game* TheGame; } #endif // GAME_H
Copy the following into Game.cpp:
#include "Game.h" using namespace C4; using namespace HW; Game* HW::TheGame = nullptr; C4::Application* ConstructApplication() { return (new Game); } Game::Game() : Singleton<Game>(TheGame) { //Creates our custom actions forwardAction = new GameAction(kActionForward, "Forward: ", 200, 150); backwardAction = new GameAction(kActionBackward, "Backward: ", 200, 250); leftAction = new GameAction(kActionLeft, "Left: ", 100, 200); rightAction = new GameAction(kActionRight, "Right: ", 300, 200); zapAction = new GameAction(kActionZap, "ZAP!!!", 200, 200); //Register our custom actions with the Input Manager TheInputMgr->AddAction(forwardAction); TheInputMgr->AddAction(backwardAction); TheInputMgr->AddAction(leftAction); TheInputMgr->AddAction(rightAction); TheInputMgr->AddAction(zapAction); //Tells the Input Manager to accept input from the keyboard and mouse TheInputMgr->SetInputMode(kInputKeyboardActive | kInputMouseActive); //Automatically releases keyboard and mouse from game when '~' is hit TheInterfaceMgr->SetInputManagementMode(kInputManagementAutomatic); } Game::~Game() { //Nesseccary to clean up pointers, else program crashes on exit delete forwardAction; delete backwardAction; delete leftAction; delete rightAction; delete zapAction; } void Game::Display(TextWidget* tWidget) { //Adds widget to the screen TheInterfaceMgr->AddWidget(tWidget); } void Game::UnDisplay(TextWidget* tWidget) { //Removes widget from the screen TheInterfaceMgr->RemoveWidget(tWidget); } GameAction::GameAction(unsigned long type, const char *text, unsigned int xLoc, unsigned int yLoc) : Action(type) { //Creates new widget which will be used to display to the screen tWidget = new TextWidget(Vector2D(100.0F, 16.0F), text); tWidget->SetFont("font/Bold"); tWidget->SetWidgetColor(ColorRGBA(0.0f, 0.0f, 1.0f)); tWidget->SetWidgetPosition(Point3D(xLoc, yLoc, 0)); defaultText = text; //Saves for when we need to reset default display string count = 0; } GameAction::~GameAction(void) { delete tWidget; } //An action key has been released. void GameAction::End(void) { TheGame->UnDisplay(tWidget); } //An action key is pressed. Holding the key down doesn't result in multiple function calls. void GameAction::Begin(void) { String<> temp; //Final string that will get displayed switch ((unsigned long)GetActionType()) { case kActionZap: //If mouse button pressed temp = defaultText; break; default: //Else keyboard button was pressed temp = defaultText; temp += ++count; } //Update widget text, display to screen tWidget->SetText(temp); TheGame->Display(tWidget); }
Go ahead and run the program to ensure everything works correctly. Play around with hitting the buttons or click the mouse to get a feel of what's happening. When your ready, continue reading for an explanation of what the code is doing.
The Action Class
Though C4 provides an Action class for reacting to user input, we must create our own subclass in order to suit our needs. Thus our GameAction class inherits from the Action class. Since each action will result in a message displayed to the screen with a count the following variables are created:
- count - Will keep count of how many times the action is executed.
- defaultText - The standard message string (forward, backward, etc) that will display to the screen for each action.
- tWidget - Text Widget that is outputted to screen.
You'll also notice a Begin() and End() function. When a user hits a key, its corresponding action will execute the Begin() function. When the user releases said key, the End() function will then be called.
The Con/De-structor
The code here should look very familiar as it was lifted almost directly from the Hello World tutorial. Minor differences include a slightly larger widget to hold our text and initialization of the count and defaultText variables.
GameAction::Begin()
This is where most of the meat and potatoes of our program resides. Remember, for every key stroke, this function will be called. The switch statement is used for determining whether the mouse button was clicked or a key was pressed. This is to handle the slightly different display of the two. If the mouse is clicked, a string is assembled with the default text. If a keyboard button was pressed, a string is assembled with the default text and the count combined.
From there the text widget is updated with the newly assembled string and passed to our Game class to display it.
GameAction::End()
Remember, this function is only called when a key is released. Our function does one thing, pass the text widget to our Game, signalling it should be removed from the screen. This will result in each action's string message only appearing when its corresponding key is being pressed.
The Game Class
This main class inherits from both C4's Singleton class and the Application class. The former ensures only one instance of our program is created while the latter pertains to allowing the engine to link to our program. Our class also handles the displaying of text widgets and houses five GameAction objects. They are:
- forwardAction - Which corresponds to the 'w' key.
- backwardAction - Which corresponds to the 'd' key.
- leftAction - Which corresponds to the 'a' key.
- rightAction - Which corresponds to the 'd' key.
- zapAction - Which corresponds to the left mouse button.
The Con/De-structor
First we instantiate each of our five GameAction variables. We pass in the action type to which it will represent, along with the default text to display, and the x and y location of its text widget.
Next we add each action into the TheInputMgr. This allows us to register our custom actions with C4's Input Manager. Following this, we ensure the input manager knows which input devices it will use. Here we pass in both the keyboard and mouse flags.
Finally we set the input management to be handled automatically with the interface manager. This line is needed because the input manager now has control of the mouse and keyboard. In the Hello World tutorial, there was still a mouse cursor that you could use to close the application. Once we passed kInputMouseActive and kInputKeyboardActiveinto the TheInputMgr the game takes focus of those devices. Even when you hit '~' to enter console mode, control of the mouse and keyboard stays with the game. Including this line fixes that. To further understand, go ahead and comment out this line. Then recompile and run the game. Press '~' and you'll see how you can't type or use the mouse, nor exit the console by hitting '~' again.
The destructor is pretty straightforward. Cleans up all dynamically allocated memory.
Game::Display()
Fairly simple class that takes a text widget parameter and adds it to the interface manager which in turn will display it.
Game::UnDisplay()
Fairly simple class that takes a text widget parameter and removes it from the interface manager which in turn will remove it from the screen.
Summary
This tutorial talked through the entire process of creating actions which are used in response to key presses. It then talked you through the given code on what was happening. Though there are a few steps in getting actions to work, its pretty easy once you get the idea. Just remember the following when handling user input:
input.cfgbinds keys to action types.- Action types are defined by enumerations and MUST be four characters long.
- Instantiate a custom
Actionclass for each action type. - Register each custom action with
TheInputMgr. - The action class Begin() function is called when its key is pressed.
- The action class End() function is called when its key is released.
