More Control On Controllers
The Leadwerks API has standard Boolean functions that detect when the end user has pressed a key. While this is very simple and easy to understand, the issue comes when you wish to support binding of actions. Instead calling functions when a certain key was pressed or held, a better way to detect key events is to assign a key to an action. (e.g: Is the Jump key pressed). In Luawerks, I've written an action script in which returns the window call if the key defined in the user's settings is hit. The downsides is that you had to do this for every action in your game and that this was a system written for Luawerks so it wasn't very portable for non-lua/Leadwerks projects.
function Action:MoveForward() if self.suspend==true then return false end local key_forward = GetKeyInt(System:GetProperty("key_forward","w")) return window:KeyDown(key_forward) end
For those who don't know, SDL is an Open Source library that comes in handy for things like Window management, input, and such. So over the weekend, I've decided to sit down and create my own Input System in which would be allow for action bindings, portable for any type of project while supporting Game Pads outside of Steam.
The first step was to manually poll all inputs and treat them all the same. For the mouse and keyboard, this was just seeing if a key was hit, and registering that event to a map.
namespace RInput_KM { void SimulateButton(const Sint32& pKey, bool bDown); const float ButtonDown(const Sint32& pKey); void ShowMouse(); void HideMouse(); void SetMousePosition(const int x, const int y, bool bGlobal = false); void ModMousePosition(const int x, const int y, bool bGlobal = false); void UpdateMousePosition(); void SimulateMouse(const int x, const int y); const int GetMouseX(); const int GetMouseY(); void SetMouseWheelPosition(const int y); const bool OnMouseWheelUp(); const bool OnMouseWheelDown(); // Returns the long to a character. const char* GetButtonName(const Sint32& pButton); // Returns the character to a long const Sint32 GetButtonIndex(const char* pButton); void Enable(); void Disable(); void FlushKeyboard(); void FlushMouse(); void Flush(); }
When it comes to buttons, it doesn't matter if the mouse button was pressed or a key stroke. What matters is that a button on the keyboard and mouse combo was pressed. I treat the keyboard and mouse as one controller. You can also "Turn off" the mouse and keyboard if you want.
namespace RInput_GamePad { typedef enum { ENUM_GAMEPAD_NULL = -1, ENUM_GAMEPAD_ONE, ENUM_GAMEPAD_TWO, ENUM_GAMEPAD_THREE, ENUM_GAMEPAD_FOUR, ENUM_GAMEPAD_MAX = ENUM_GAMEPAD_FOUR } GamePadIndex; typedef struct { SDL_GameController* controller; const char* pszDeviceName; bool bEnabled; std::map<Uint8, bool> mControllerButtons; } gamepad_t; void Connect(const Sint32& pWhich); void Disconnect(const Sint32& pWhich); gamepad_t GetDeviceFromPort(const GamePadIndex& pPort); // Digital input: void SimulateButton(const Sint32& pWhich, const Uint8& pButton, bool bDown); const float ButtonDown(const Uint8& pButton, const GamePadIndex& iIndex = ENUM_GAMEPAD_ONE); const char* GetButtonName(const Uint8& pButton); const Uint8 GetButtonIndex(const char* pButton); // Analog input: void UpdateAxisMotions(const Sint32& pWhich, const Uint32& pAxis); const Sint16 GetAxisValue(const Sint32& pWhich, const Uint32& pAxis, bool bFlip = false); const float GetAxisFloat(const Sint32& pWhich, const Uint32& pAxis, bool bFlip = false); void Flush(const Sint32& pWhich); void FlushAll(); }
Next was the game pads. which were a bit more challenging as you need to consider multiple game pads and the valves of analog inputs.
If you were paying attention, You would notice that the ButtonDown functions for both the keyboard+mouse and the game pad are returning floats. While it may be limiting for certain applications, I've created "fake buttons" for events for when the trigger is pressed, or if the left stick is to the left. All input returns a float ranging from 0.0 to 1.0. For digital inputs like buttons, this will be a 1 or 0, but for analog the value depending the range from the resting point to the max will called. So if the left stick is all the way to the left, you'd get 1; half way 0.5. This is much better then dealing with direct Uint32 values.
Last was to merge both controllers into one entry point in which we use in our app to register our actions, and check to see if they are being called.
namespace RInput { typedef enum { CONTROLLER_KEYBOARDMOUSE, CONTROLLER_GAMEPAD } Controllers_t; void Init(SDL_Window* pWindow); void InitSDL(const void *data); void SetActiveDevice(const Controllers_t& pDevice); Controllers_t GetActiveDevice(); const char* GetActiveDeviceAsString(); const char* GetGamePadDeviceAsString(const int pPort); const Sint8 GetGamePadCount(); void TestEvents(const SDL_Event& pEvent); void PollEvents(); // <- Use this function if you're not using SDL event polling. void Flush(const Controllers_t& pController); void FlushAll(); //==================================================================== typedef struct { Sint32 key; Uint8 button; bool bDown; bool bHit; } action_t; const float GetActionInput(action_t& pButton); void RegisterAction(const std::string& pActionName, Sint32 iKey, Uint8 iButton, bool bConistant); void ModifyAction(const std::string& pActionName, Sint32 iKey, Uint8 iButton); action_t& GetAction(const std::string& pActionName); bool LoadActionsFromFile(const char* pszPath); void UpdateGamePadStickAsMouse(const Sint32& pWhich, const Sint8& pAxis); }
Actions can be bind to a with key and a button. bDown is used to check if the action is being held down while bHit is a flag to check if this is something that should be called once per pressed (Like a key to pick up a box or something.) One top of all this, Actions can have their key/button bindings changed via an XML file. Here's an example how to use this with Leadwerks.
#include "Leadwerks.h" using namespace Leadwerks; #include "rinput/rinput.h" int main() { Leadwerks::Window* window = Window::Create(); RInput::InitSDL(window->hwnd); Leadwerks::Context* context = Context::Create(window); World* world = World::Create(); Camera* camera = Camera::Create(); camera->SetRotation(35, 0, 0); camera->Move(0, 0, -6); Light* light = DirectionalLight::Create(); light->SetRotation(35, 35, 0); light->SetShadowMode(0); Model* model = Model::Box(); model->SetColor(1.0, 0.0, 0.0); model->SetPosition(0, 0, 0); model->SetShadowMode(0); RInput::RegisterAction("moveforward", KEYBOARD_W, GAMEPAD_BUTTON_LSTICK_UP, true); RInput::RegisterAction("movebackward", KEYBOARD_S, GAMEPAD_BUTTON_LSTICK_DOWN, true); RInput::RegisterAction("moveleft", KEYBOARD_A, GAMEPAD_BUTTON_LSTICK_LEFT, true); RInput::RegisterAction("moveright", KEYBOARD_D, GAMEPAD_BUTTON_LSTICK_RIGHT, true); RInput::RegisterAction("jump", KEYBOARD_SPACE, GAMEPAD_BUTTON_A, false); RInput::LoadActionsFromFile("controller.xml"); while (window->Closed() == false) { RInput::PollEvents(); float x = 0; float z = 0; float u = RInput::GetActionInput(RInput::GetAction("moveforward")); float d = RInput::GetActionInput(RInput::GetAction("movebackward")); float l = RInput::GetActionInput(RInput::GetAction("moveleft")); float r = RInput::GetActionInput(RInput::GetAction("moveright")); if (u != 0) { z += 0.05 + (u /10); } if (d != 0) { z -= 0.05 + (d / 10); } if (l != 0) { x -= 0.05 + (l / 10); } if (r != 0) { x += 0.05 + (r / 10); } model->Move(x, 0, z); x = 0; z = 0; Leadwerks::Time::Update(); world->Update(); world->Render(); context->DrawStats(0, 0, false); context->Sync(false); } return 0; }
The Result:
There are many things I didn't add for the sake of time or I wasn't sure how to implement like supporting action for multiple controllers on the front end. But this does what I was set out to do, and although I haven't tried, I'm confident that with little to no work, this code will work on macOS and Linux. As long as there is SDL2 on the platform, this will work.
Now no matter if the project is Leadwerks, Turbo or just an SDL2 project, I now have a portable input system with controller support which you can find here.
- 2
0 Comments
Recommended Comments
There are no comments to display.