diff --git a/.gitignore b/.gitignore index 818cae1..dd30b33 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .vs x64/ screenshot.png +*.vcxproj.user diff --git a/Examples.sln b/Examples.sln index 1b4bbd3..36a540b 100644 --- a/Examples.sln +++ b/Examples.sln @@ -9,6 +9,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Mandelbrot", "Examples\Mand EndProject Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Minesweeper", "Examples\Minesweeper\Minesweeper.vcxproj", "{7A5D47D3-25F8-4569-A2DC-910A6C132A93}" EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ControllerTest", "Examples\ControllerTest\ControllerTest.vcxproj", "{D11903AB-AD22-48A0-BAE2-75ADE9C8C024}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x64 = Debug|x64 @@ -41,6 +43,14 @@ Global {7A5D47D3-25F8-4569-A2DC-910A6C132A93}.Release|x64.Build.0 = Release|x64 {7A5D47D3-25F8-4569-A2DC-910A6C132A93}.Release|x86.ActiveCfg = Release|Win32 {7A5D47D3-25F8-4569-A2DC-910A6C132A93}.Release|x86.Build.0 = Release|Win32 + {D11903AB-AD22-48A0-BAE2-75ADE9C8C024}.Debug|x64.ActiveCfg = Debug|x64 + {D11903AB-AD22-48A0-BAE2-75ADE9C8C024}.Debug|x64.Build.0 = Debug|x64 + {D11903AB-AD22-48A0-BAE2-75ADE9C8C024}.Debug|x86.ActiveCfg = Debug|Win32 + {D11903AB-AD22-48A0-BAE2-75ADE9C8C024}.Debug|x86.Build.0 = Debug|Win32 + {D11903AB-AD22-48A0-BAE2-75ADE9C8C024}.Release|x64.ActiveCfg = Release|x64 + {D11903AB-AD22-48A0-BAE2-75ADE9C8C024}.Release|x64.Build.0 = Release|x64 + {D11903AB-AD22-48A0-BAE2-75ADE9C8C024}.Release|x86.ActiveCfg = Release|Win32 + {D11903AB-AD22-48A0-BAE2-75ADE9C8C024}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Examples/ControllerTest/ControllerTest.vcxproj b/Examples/ControllerTest/ControllerTest.vcxproj new file mode 100644 index 0000000..2b575b8 --- /dev/null +++ b/Examples/ControllerTest/ControllerTest.vcxproj @@ -0,0 +1,139 @@ + + + + + Debug + Win32 + + + Release + Win32 + + + Debug + x64 + + + Release + x64 + + + + 17.0 + Win32Proj + {d11903ab-ad22-48a0-bae2-75ade9c8c024} + ControllerTest + 10.0 + + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + Application + true + v143 + Unicode + + + Application + false + v143 + true + Unicode + + + + + + + + + + + + + + + + + + + + + + Level3 + true + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + + + Level3 + true + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + + + Level3 + true + _DEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + + + Level3 + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + + + + + + + + + + + \ No newline at end of file diff --git a/Examples/ControllerTest/ControllerTest.vcxproj.filters b/Examples/ControllerTest/ControllerTest.vcxproj.filters new file mode 100644 index 0000000..4327830 --- /dev/null +++ b/Examples/ControllerTest/ControllerTest.vcxproj.filters @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Examples/ControllerTest/main.cpp b/Examples/ControllerTest/main.cpp new file mode 100644 index 0000000..7493ceb --- /dev/null +++ b/Examples/ControllerTest/main.cpp @@ -0,0 +1,99 @@ +#define THIRTEEN_IMPLEMENTATION +#include "../../thirteen.h" + +#include +#include + +static const unsigned int c_width = 64; +static const unsigned int c_height = 64; +static const bool c_fullscreen = false; + +static const float c_thumbStickEpsilon = 0.01f; + +int main(int argc, char** argv) +{ + Thirteen::SetApplicationName("Thirteen Demo - ControllerTest"); + + unsigned char* pixels = Thirteen::Init(c_width, c_height, c_fullscreen); + if (!pixels) + { + printf("Could not initialize Thirteen\n"); + return 1; + } + + printf("Interact with the controller to see controller values\n\n"); + + do + { + struct ButtonInfo + { + Thirteen::ControllerButton button; + const char* name; + }; + + static const ButtonInfo buttons[] = + { + { Thirteen::ControllerButton::DPadUp, "DPadUp" }, + { Thirteen::ControllerButton::DPadDown, "DPadDown" }, + { Thirteen::ControllerButton::DPadLeft, "DPadLeft" }, + { Thirteen::ControllerButton::DPadRight, "DPadRight" }, + { Thirteen::ControllerButton::Start, "Start" }, + { Thirteen::ControllerButton::Back, "Back" }, + { Thirteen::ControllerButton::LeftThumb, "LeftThumb" }, + { Thirteen::ControllerButton::RightThumb, "RightThumb" }, + { Thirteen::ControllerButton::LeftShoulder, "LeftShoulder" }, + { Thirteen::ControllerButton::RightShoulder, "RightShoulder" }, + { Thirteen::ControllerButton::A, "A" }, + { Thirteen::ControllerButton::B, "B" }, + { Thirteen::ControllerButton::X, "X" }, + { Thirteen::ControllerButton::Y, "Y" } + }; + + for (const ButtonInfo& buttonInfo : buttons) + { + const bool currentState = Thirteen::GetControllerButton(0, buttonInfo.button); + const bool previousState = Thirteen::GetControllerButtonLastFrame(0, buttonInfo.button); + + if (currentState != previousState) + { + if (currentState) + printf("%s pressed\n", buttonInfo.name); + else + printf("%s released\n", buttonInfo.name); + } + } + + const float leftTrigger = Thirteen::GetControllerTrigger(0, true); + const float leftTriggerLastFrame = Thirteen::GetControllerTriggerLastFrame(0, true); + if (fabsf(leftTrigger - leftTriggerLastFrame) > c_thumbStickEpsilon) + printf("LeftTrigger: %.3f\n", leftTrigger); + + const float rightTrigger = Thirteen::GetControllerTrigger(0, false); + const float rightTriggerLastFrame = Thirteen::GetControllerTriggerLastFrame(0, false); + if (fabsf(rightTrigger - rightTriggerLastFrame) > c_thumbStickEpsilon) + printf("RightTrigger: %.3f\n", rightTrigger); + + float leftThumbX = 0.0f; + float leftThumbY = 0.0f; + float leftThumbXLastFrame = 0.0f; + float leftThumbYLastFrame = 0.0f; + Thirteen::GetControllerThumbstick(0, true, leftThumbX, leftThumbY); + Thirteen::GetControllerThumbstickLastFrame(0, true, leftThumbXLastFrame, leftThumbYLastFrame); + if (fabsf(leftThumbX - leftThumbXLastFrame) > c_thumbStickEpsilon || fabsf(leftThumbY - leftThumbYLastFrame) > c_thumbStickEpsilon) + printf("LeftThumb: x=%.3f, y=%.3f\n", leftThumbX, leftThumbY); + + float rightThumbX = 0.0f; + float rightThumbY = 0.0f; + float rightThumbXLastFrame = 0.0f; + float rightThumbYLastFrame = 0.0f; + Thirteen::GetControllerThumbstick(0, false, rightThumbX, rightThumbY); + Thirteen::GetControllerThumbstickLastFrame(0, false, rightThumbXLastFrame, rightThumbYLastFrame); + if (fabsf(rightThumbX - rightThumbXLastFrame) > c_thumbStickEpsilon || fabsf(rightThumbY - rightThumbYLastFrame) > c_thumbStickEpsilon) + printf("RightThumb: x=%.3f, y=%.3f\n", rightThumbX, rightThumbY); + } + while (Thirteen::Render() && !Thirteen::GetKey(VK_ESCAPE)); + + Thirteen::Shutdown(); + + return 0; +} diff --git a/Examples/Mandelbrot/Mandelbrot.vcxproj.user b/Examples/Mandelbrot/Mandelbrot.vcxproj.user deleted file mode 100644 index 88a5509..0000000 --- a/Examples/Mandelbrot/Mandelbrot.vcxproj.user +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/Examples/Minesweeper/Minesweeper.vcxproj.user b/Examples/Minesweeper/Minesweeper.vcxproj.user deleted file mode 100644 index 88a5509..0000000 --- a/Examples/Minesweeper/Minesweeper.vcxproj.user +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/Examples/Simple/Simple.vcxproj.user b/Examples/Simple/Simple.vcxproj.user deleted file mode 100644 index 88a5509..0000000 --- a/Examples/Simple/Simple.vcxproj.user +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/readme.md b/readme.md index 2041dcd..3e1bbfc 100644 --- a/readme.md +++ b/readme.md @@ -226,6 +226,106 @@ Checks if a keyboard key was pressed in the previous frame. --- +### Controller Input (Windows only) + +Controller input currently uses XInput and is available on Windows builds only. On all other platforms, you can call the functions +but it will always report that no buttons are pressed, the triggers are at 0, and the thumbsticks are at (0,0). + +Supported controller indices are `0` to `3`. + +Controller button bit flags used with `GetControllerButton()` and `GetControllerButtonLastFrame()`: + +- `ControllerButton::DPadUp` +- `ControllerButton::DPadDown` +- `ControllerButton::DPadLeft` +- `ControllerButton::DPadRight` +- `ControllerButton::Start` +- `ControllerButton::Back` +- `ControllerButton::LeftThumb` +- `ControllerButton::RightThumb` +- `ControllerButton::LeftShoulder` +- `ControllerButton::RightShoulder` +- `ControllerButton::A` +- `ControllerButton::B` +- `ControllerButton::X` +- `ControllerButton::Y` + +--- + +#### `bool GetControllerButton(int controllerIndex, ControllerButton button)` +Checks if a controller button is currently pressed. + +**Parameters:** +- `controllerIndex` - Controller index (`0` to `3`) +- `button` - Button flag from `ControllerButton` + +**Returns:** `true` if pressed, `false` otherwise. + +--- + +#### `bool GetControllerButtonLastFrame(int controllerIndex, ControllerButton button)` +Checks if a controller button was pressed in the previous frame. + +**Parameters:** +- `controllerIndex` - Controller index (`0` to `3`) +- `button` - Button flag from `ControllerButton` + +**Returns:** `true` if pressed in the previous frame, `false` otherwise. + +--- + +#### `float GetControllerTrigger(int controllerIndex, bool left)` +Gets the current normalized trigger value. + +**Parameters:** +- `controllerIndex` - Controller index (`0` to `3`) +- `left` - `true` for left trigger, `false` for right trigger + +**Returns:** Trigger value in the range `[0, 1]`. + +--- + +#### `float GetControllerTriggerLastFrame(int controllerIndex, bool left)` +Gets the previous-frame normalized trigger value. + +**Parameters:** +- `controllerIndex` - Controller index (`0` to `3`) +- `left` - `true` for left trigger, `false` for right trigger + +**Returns:** Trigger value in the range `[0, 1]` from the previous frame. + +--- + +#### `void GetControllerThumbstick(int controllerIndex, bool left, float& x, float& y)` +Gets the current normalized thumbstick vector. + +**Parameters:** +- `controllerIndex` - Controller index (`0` to `3`) +- `left` - `true` for left stick, `false` for right stick +- `x` - Receives the X axis value +- `y` - Receives the Y axis value + +**Notes:** +- Values are normalized to approximately `[-1, 1]`. +- For invalid controller indices, both outputs are set to `0`. + +--- + +#### `void GetControllerThumbstickLastFrame(int controllerIndex, bool left, float& x, float& y)` +Gets the previous-frame normalized thumbstick vector. + +**Parameters:** +- `controllerIndex` - Controller index (`0` to `3`) +- `left` - `true` for left stick, `false` for right stick +- `x` - Receives the X axis value +- `y` - Receives the Y axis value + +**Notes:** +- Values are normalized to approximately `[-1, 1]`. +- For invalid controller indices, both outputs are set to `0`. + +--- + ### Input Helpers To detect a single key press or button click (not held down), compare current and previous frame states: diff --git a/thirteen.h b/thirteen.h index 03a60fd..749253e 100644 --- a/thirteen.h +++ b/thirteen.h @@ -38,6 +38,7 @@ Chris Cascioli - GetWindowHandle() and warning cleanup #include #include #include + #include #elif defined(THIRTEEN_PLATFORM_WEB) #include #include @@ -54,6 +55,7 @@ Chris Cascioli - GetWindowHandle() and warning cleanup #pragma comment(lib, "d3d12.lib") #pragma comment(lib, "dxgi.lib") #pragma comment(lib, "user32.lib") + #pragma comment(lib, "xinput.lib") #define DX12VALIDATION() (_DEBUG && false) #endif @@ -161,12 +163,58 @@ namespace Thirteen // Returns whether a keyboard key was pressed in the previous frame (use Windows virtual key codes). [[nodiscard]] bool GetKeyLastFrame(int keyCode); + enum ControllerButton : uint16_t + { + DPadUp = 0x0001, + DPadDown = 0x0002, + DPadLeft = 0x0004, + DPadRight = 0x0008, + Start = 0x0010, + Back = 0x0020, + LeftThumb = 0x0040, + RightThumb = 0x0080, + LeftShoulder = 0x0100, + RightShoulder = 0x0200, + A = 0x0400, + B = 0x0800, + X = 0x1000, + Y = 0x2000 + }; + + // Returns whether a controller button is currently pressed. + [[nodiscard]] bool GetControllerButton(int controllerIndex, ControllerButton button); + + // Returns whether a controller button was pressed in the previous frame. + [[nodiscard]] bool GetControllerButtonLastFrame(int controllerIndex, ControllerButton button); + + // Returns current trigger value in [0, 1]. + [[nodiscard]] float GetControllerTrigger(int controllerIndex, bool left); + + // Returns previous-frame trigger value in [0, 1]. + [[nodiscard]] float GetControllerTriggerLastFrame(int controllerIndex, bool left); + + // Returns current thumbstick values in approximately [-1, 1]. + void GetControllerThumbstick(int controllerIndex, bool left, float& x, float& y); + + // Returns previous-frame thumbstick values in approximately [-1, 1]. + void GetControllerThumbstickLastFrame(int controllerIndex, bool left, float& x, float& y); + } // ========== Implementation ========== #ifdef THIRTEEN_IMPLEMENTATION namespace Thirteen { + struct ControllerState + { + uint16_t buttons = 0; + float leftTrigger = 0.0f; + float rightTrigger = 0.0f; + float leftThumbX = 0.0f; + float leftThumbY = 0.0f; + float rightThumbX = 0.0f; + float rightThumbY = 0.0f; + }; // Internal state namespace Internal @@ -202,6 +250,9 @@ namespace Thirteen bool keys[256] = {}; bool prevKeys[256] = {}; + ControllerState controllers[4] = {}; + ControllerState prevControllers[4] = {}; + // The pixels to write to. uint8* Pixels = nullptr; @@ -551,6 +602,59 @@ namespace Thirteen bool Render(const uint8* pixels, uint32 width, uint32 height, bool vsyncEnabled) { + for (int controllerIndex = 0; controllerIndex < 4; ++controllerIndex) + { + controllers[controllerIndex] = ControllerState{}; + + XINPUT_STATE state; + ZeroMemory(&state, sizeof(XINPUT_STATE)); + DWORD result = XInputGetState(controllerIndex, &state); + if (result != ERROR_SUCCESS) + continue; + + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP) + controllers[controllerIndex].buttons |= ControllerButton::DPadUp; + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN) + controllers[controllerIndex].buttons |= ControllerButton::DPadDown; + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT) + controllers[controllerIndex].buttons |= ControllerButton::DPadLeft; + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT) + controllers[controllerIndex].buttons |= ControllerButton::DPadRight; + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_START) + controllers[controllerIndex].buttons |= ControllerButton::Start; + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_BACK) + controllers[controllerIndex].buttons |= ControllerButton::Back; + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB) + controllers[controllerIndex].buttons |= ControllerButton::LeftThumb; + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB) + controllers[controllerIndex].buttons |= ControllerButton::RightThumb; + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER) + controllers[controllerIndex].buttons |= ControllerButton::LeftShoulder; + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER) + controllers[controllerIndex].buttons |= ControllerButton::RightShoulder; + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_A) + controllers[controllerIndex].buttons |= ControllerButton::A; + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_B) + controllers[controllerIndex].buttons |= ControllerButton::B; + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_X) + controllers[controllerIndex].buttons |= ControllerButton::X; + if (state.Gamepad.wButtons & XINPUT_GAMEPAD_Y) + controllers[controllerIndex].buttons |= ControllerButton::Y; + + controllers[controllerIndex].leftTrigger = float(state.Gamepad.bLeftTrigger) / 255.0f; + controllers[controllerIndex].rightTrigger = float(state.Gamepad.bRightTrigger) / 255.0f; + auto NormalizeThumbAxis = [](short value) -> float + { + const float normalized = float(value) / 32767.0f; + return normalized < -1.0f ? -1.0f : (normalized > 1.0f ? 1.0f : normalized); + }; + + controllers[controllerIndex].leftThumbX = NormalizeThumbAxis(state.Gamepad.sThumbLX); + controllers[controllerIndex].leftThumbY = NormalizeThumbAxis(state.Gamepad.sThumbLY); + controllers[controllerIndex].rightThumbX = NormalizeThumbAxis(state.Gamepad.sThumbRX); + controllers[controllerIndex].rightThumbY = NormalizeThumbAxis(state.Gamepad.sThumbRY); + } + WaitForGpu(); frameIndex = swapChain->GetCurrentBackBufferIndex(); @@ -2167,6 +2271,7 @@ namespace Thirteen prevMouseY = mouseY; memcpy(prevMouseButtons, mouseButtons, sizeof(mouseButtons)); memcpy(prevKeys, keys, sizeof(keys)); + memcpy(prevControllers, controllers, sizeof(controllers[0]) * 4); // Calculate frame time double currentTime = NowSeconds(); @@ -2330,6 +2435,62 @@ namespace Thirteen return false; } + bool GetControllerButton(int controllerIndex, ControllerButton button) + { + if (controllerIndex >= 0 && controllerIndex < 4) + return (Internal::controllers[controllerIndex].buttons & button) != 0; + return false; + } + + bool GetControllerButtonLastFrame(int controllerIndex, ControllerButton button) + { + if (controllerIndex >= 0 && controllerIndex < 4) + return (Internal::prevControllers[controllerIndex].buttons & button) != 0; + return false; + } + + float GetControllerTrigger(int controllerIndex, bool left) + { + if (controllerIndex >= 0 && controllerIndex < 4) + return left ? Internal::controllers[controllerIndex].leftTrigger : Internal::controllers[controllerIndex].rightTrigger; + return 0.0f; + } + + float GetControllerTriggerLastFrame(int controllerIndex, bool left) + { + if (controllerIndex >= 0 && controllerIndex < 4) + return left ? Internal::prevControllers[controllerIndex].leftTrigger : Internal::prevControllers[controllerIndex].rightTrigger; + return 0.0f; + } + + void GetControllerThumbstick(int controllerIndex, bool left, float& x, float& y) + { + if (controllerIndex >= 0 && controllerIndex < 4) + { + x = left ? Internal::controllers[controllerIndex].leftThumbX : Internal::controllers[controllerIndex].rightThumbX; + y = left ? Internal::controllers[controllerIndex].leftThumbY : Internal::controllers[controllerIndex].rightThumbY; + } + else + { + x = 0.0f; + y = 0.0f; + } + } + + void GetControllerThumbstickLastFrame(int controllerIndex, bool left, float& x, float& y) + { + if (controllerIndex >= 0 && controllerIndex < 4) + { + x = left ? Internal::prevControllers[controllerIndex].leftThumbX : Internal::prevControllers[controllerIndex].rightThumbX; + y = left ? Internal::prevControllers[controllerIndex].leftThumbY : Internal::prevControllers[controllerIndex].rightThumbY; + } + else + { + x = 0.0f; + y = 0.0f; + } + } + void Shutdown() { using namespace Internal;