<-- Chapter 16 | Back to Index | Chapter 18 -->
Be warned! There are a number of changes we have to make to our build file to keep up to date.
Because we enabled warnings-are-errors mode in the last chapter, we now have to make sure we have no warnings.
Unfortunately, gcc has some extra warnings Visual Studio doesn't, so we'll need to pass the -Wno-sign-compare
option to make handmade.cpp compile with the latest changes. We'll also want to add -std=gnu++11 to tell the compiler we want to support C++11 features and non-standard extensions. This will let us use things like the = {} initialisation syntax. |
If you've played around with our gamepad input, you might have noticed that it falls short of perfection in a couple of ways. Firstly, we're going to want to allow the user to use the D-Pad rather than the analogue stick, should they so desire. Indeed: we're going to want to rethink our game_controller_input structure to better match the way our game is going to work.
Casey's been pretty tight-lipped about the game, but we know that we're going to need two directional inputs: one for movement and one for actions. A lot of these inputs are, in a sense, binary: we're either performing an action to the left, or not. We can then also provide an intensity or magnitude value, if we want to preserve the analogue information.
Our new game_controller_input structure looks like this:
struct game_controller_input { bool32 IsConnected; bool32 IsAnalog; real32 StickAverageX; real32 StickAverageY; union { game_button_state Buttons[12]; struct { game_button_state MoveUp; game_button_state MoveDown; game_button_state MoveLeft; game_button_state MoveRight; game_button_state ActionUp; game_button_state ActionDown; game_button_state ActionLeft; game_button_state ActionRight; game_button_state LeftShoulder; game_button_state RightShoulder; game_button_state Back; game_button_state Start; // NOTE(casey): All buttons must be added above this line game_button_state Terminator; }; }; };
We now need to update our code to fill this in correctly. The mapping we're using is:
internal void SDLProcessGameControllerButton(game_button_state *OldState, game_button_state *NewState, bool Value) { NewState->EndedDown = Value; NewState->HalfTransitionCount += ((NewState->EndedDown == OldState->EndedDown)?0:1); }We then need to alter all of our calls to it, as they need to use the new button names in the game_controller_input structure and the new SDLProcessGameControllerButton() function. For example, the A button handler would become:
SDLProcessGameControllerButton(&(OldController->ActionDown), &(NewController->ActionDown), SDL_GameControllerGetButton(ControllerHandles[ControllerIndex], SDL_CONTROLLER_BUTTON_A));
Now we could handle our analogue sticks. We'll pick a threshold (0.5 is good), and simply pass it through to our SDLProcessGameControllerButton() function:
real32 Threshold = 0.5f; SDLProcessGameControllerButton(&(OldController->MoveLeft), &(NewController->MoveLeft), NewController->StickAverageX < -Threshold);Easy! We'll also set the IsAnalog variable to true if the analogue stick is not zero.
if((NewController->StickAverageX != 0.0f) || (NewController->StickAverageY != 0.0f)) { NewController->IsAnalog = true; }
Now let's get the D-Pad working. We want to have it set up to match the analogue stick. What we'll do, therefore, is have them just set StickAverage{X,Y} to 1.0 or -1.0 if pressed. We'll also set IsAnalog to 0:
if(SDL_GameControllerGetButton(ControllerHandles[ControllerIndex], SDL_CONTROLLER_BUTTON_DPAD_UP)) { NewController->StickAverageY = 1.0f; NewController->IsAnalog = false; }
Despite sounding like a B-grade Sci-Fi flick, "the Dead Zone" is not a seething horde of zombies but rather a seething horde of workarounds for inexact hardware. Joysticks (including the analogue sticks in gamepads) will often be constructed in such a way that their axes do not sit firmly on "0" when left to their own devices. The deadzone is simply a range of values that the program should interpret as the axis being left in its neutral position.
Every device will be different, and there's no reliable way of determining what your deadzone should be. As mentioned in the stream, Microsoft provides values on MSDN for its own XInput controllers. For other controllers, one typically has to either guess or perform some calibration.
We'll handle the deadzone in exactly the same way as we did on Windows, by introducing a SDLProcessGameControllerAxisValue() function which both does the normalising we were already doing and sets any value inside the deadzone to 0:
internal real32 SDLProcessGameControllerAxisValue(int16 Value, int16 DeadZoneThreshold) { real32 Result = 0; if(Value < -DeadZoneThreshold) { Result = (real32)((Value + DeadZoneThreshold) / (32768.0f - DeadZoneThreshold)); } else if(Value > DeadZoneThreshold) { Result = (real32)((Value - DeadZoneThreshold) / (32767.0f - DeadZoneThreshold)); } return(Result); }
Stange as it may sound, not everyone has a gamepad. We're going to need some other way of controlling our game and the keyboard is a rather common implement for PC users. Fortunately, we already have code for handling keyboard events, we just need to make it update a game_controller_input structure.
To start with, we'll need to have a game_controller_input structure to pass. We currently have an array of four of them, to handle the four controllers. Let's just bump that up to five in handmade.h.
We'll make our code use index 0 for the keyboard and indices 1 to 4 for any actual controllers. We'll simply add 1 to ControllerIndex when setting up NewController and OldController
We can then set up OldKeyboardController and NewKeyboardController variables:
game_controller_input *OldKeyboardController = &OldInput->Controllers[0]; game_controller_input *NewKeyboardController = &NewInput->Controllers[0]; *NewKeyboardController = {}; for(int ButtonIndex = 0; ButtonIndex < ArrayCount(NewKeyboardController->Buttons); ++ButtonIndex) { NewKeyboardController->Buttons[ButtonIndex].EndedDown = OldKeyboardController->Buttons[ButtonIndex].EndedDown; }You'll notice that we also have to carry the EndedDown flag over. Otherwise, we'd think the button had been released every frame.
We're already checking our keyboard state in HandleEvent(), though we're not doing anything with it. Let's pass NewKeyboardController to HandleEvent(). What we do from there is practically the same as on Windows: we need to set the EndedDown variable for each button to the correct value and increment HalfTransitionCount. We'll move that out into a SDLProcessKeyPress() function:
internal void SDLProcessKeyPress(game_button_state *NewState, bool32 IsDown) { Assert(NewState->EndedDown != IsDown); NewState->EndedDown = IsDown; ++NewState->HalfTransitionCount; }We can then fill in our HandleEvent() function with calls:
if(KeyCode = SDLK_w) { SDLProcessKeyPress(&NewKeyboardController->MoveUp, IsDown); }...and so on. Compile, and we'll have keyboard control. How exciting!
At some point, we'll want to know which controllers in our game_input structure are actually connected. We'll do this by adding a IsConnected boolean to the game_controller_input structure and setting it accordingly.
The first thing we'll do is set up the Keyboard Controller: it's always going to be available, so we'll just set:
NewKeyboardController->IsConnected = true;when we initialise it.
For the other controllers, we can simply set IsConnected to true after we've checked the ControllerHandle is not 0 and that SDL_GameControllerGetAttached() returned true.
Finally, we'll want to make sure we don't accidentally try to access a controller that doesn't exist at all. We can do that with an assertion, which we'll wrap in a function in handmade.h for convenience:
inline game_controller_input *GetController(game_input *Input, int unsigned ControllerIndex) { Assert(ControllerIndex < ArrayCount(Input->Controllers)); game_controller_input *Result = &Input->Controllers[ControllerIndex]; return(Result); }We can then use GetController() in place of &Input->Controllers[i] wherever it appears.
That's our input done! We'll probably come back at the end to do some crazy keyboard layout stuff (which will be so much better and easier in SDL than in Windows), but for now we need not think of input ever again.
Sorry for the delay: with some luck the next few chapters will be coming out more quickly!
If you've bought Handmade Hero, the source for the Linux version can be downloaded here. Note that you will require the official source code for handmade.h and handmade.cpp.