diff --git a/Hazel/src/Hazel/Core/Application.cpp b/Hazel/src/Hazel/Core/Application.cpp index 008c58b16..6dd3d2fe6 100644 --- a/Hazel/src/Hazel/Core/Application.cpp +++ b/Hazel/src/Hazel/Core/Application.cpp @@ -27,6 +27,9 @@ namespace Hazel { m_Window = Window::Create(WindowProps(m_Specification.Name)); m_Window->SetEventCallback(HZ_BIND_EVENT_FN(Application::OnEvent)); + Input::Init(); + Input::SetEventCallback(HZ_BIND_EVENT_FN(Application::OnEvent)); + Renderer::Init(); m_ImGuiLayer = new ImGuiLayer(); @@ -108,6 +111,8 @@ namespace Hazel { m_ImGuiLayer->End(); } + Input::Update(); + m_Window->OnUpdate(); } } diff --git a/Hazel/src/Hazel/Core/GamepadCodes.h b/Hazel/src/Hazel/Core/GamepadCodes.h new file mode 100644 index 000000000..2bff131d6 --- /dev/null +++ b/Hazel/src/Hazel/Core/GamepadCodes.h @@ -0,0 +1,34 @@ +#pragma once + +namespace Hazel +{ + const uint32_t MaxGamepadCount = 4; + + using GamepadCode = uint16_t; + + namespace Gamepad + { + enum : GamepadCode + { + DPadUp, + DPadDown, + DPadLeft, + DPadRight, + Start, + Select, + Back = Select, + A, + B, + X, + Y, + Cross = B, + Square = Y, + Circle = A, + Triangle = X, + LeftThumb, + RightThumb, + LeftShoulder, + RightShoulder + }; + } +} diff --git a/Hazel/src/Hazel/Core/Input.h b/Hazel/src/Hazel/Core/Input.h index 89703a7d7..30a5197ec 100644 --- a/Hazel/src/Hazel/Core/Input.h +++ b/Hazel/src/Hazel/Core/Input.h @@ -2,6 +2,8 @@ #include "Hazel/Core/KeyCodes.h" #include "Hazel/Core/MouseCodes.h" +#include "Hazel/Core/GamepadCodes.h" +#include "Hazel/Events/Event.h" #include @@ -10,11 +12,22 @@ namespace Hazel { class Input { public: + static void Init(); + static void SetEventCallback(const EventCallbackFn& callback); + static void Update(); + static bool IsKeyPressed(KeyCode key); static bool IsMouseButtonPressed(MouseCode button); static glm::vec2 GetMousePosition(); static float GetMouseX(); static float GetMouseY(); + + static bool IsGamepadButtonPressed(uint32_t playerIndex, GamepadCode code); + static bool IsGamepadButtonReleased(uint32_t playerIndex, GamepadCode code); + static glm::vec2 GetGamepadLeftJoystick(uint32_t playerIndex); + static glm::vec2 GetGamepadRightJoystick(uint32_t playerIndex); + static glm::vec2 GetGamepadTriggers(uint32_t playerIndex); + static void SetGamepadVibration(uint32_t playerIndex, const glm::vec2& vibration); }; } diff --git a/Hazel/src/Hazel/Core/Window.h b/Hazel/src/Hazel/Core/Window.h index c31e1c52b..4fca52b88 100644 --- a/Hazel/src/Hazel/Core/Window.h +++ b/Hazel/src/Hazel/Core/Window.h @@ -25,8 +25,6 @@ namespace Hazel { class Window { public: - using EventCallbackFn = std::function; - virtual ~Window() = default; virtual void OnUpdate() = 0; diff --git a/Hazel/src/Hazel/Events/Event.h b/Hazel/src/Hazel/Events/Event.h index 1344a642b..a34a0430d 100644 --- a/Hazel/src/Hazel/Events/Event.h +++ b/Hazel/src/Hazel/Events/Event.h @@ -18,7 +18,8 @@ namespace Hazel { WindowClose, WindowResize, WindowFocus, WindowLostFocus, WindowMoved, AppTick, AppUpdate, AppRender, KeyPressed, KeyReleased, KeyTyped, - MouseButtonPressed, MouseButtonReleased, MouseMoved, MouseScrolled + MouseButtonPressed, MouseButtonReleased, MouseMoved, MouseScrolled, + GamepadConnected, GamepadDisconnected }; enum EventCategory @@ -28,7 +29,8 @@ namespace Hazel { EventCategoryInput = BIT(1), EventCategoryKeyboard = BIT(2), EventCategoryMouse = BIT(3), - EventCategoryMouseButton = BIT(4) + EventCategoryMouseButton = BIT(4), + EventCategoryGamepad = BIT(5), }; #define EVENT_CLASS_TYPE(type) static EventType GetStaticType() { return EventType::type; }\ @@ -83,5 +85,6 @@ namespace Hazel { return os << e.ToString(); } + using EventCallbackFn = std::function; } diff --git a/Hazel/src/Hazel/Events/GamepadEvent.h b/Hazel/src/Hazel/Events/GamepadEvent.h new file mode 100644 index 000000000..5e0ce9a00 --- /dev/null +++ b/Hazel/src/Hazel/Events/GamepadEvent.h @@ -0,0 +1,52 @@ +#pragma once + +#include "Hazel/Events/Event.h" + +namespace Hazel { + + class GamepadEvent : public Event + { + public: + GamepadEvent(uint32_t gamepadIndex) + : m_GamepadIndex(gamepadIndex) {} + + uint32_t GetGamepadIndex() { return m_GamepadIndex; } + + EVENT_CLASS_CATEGORY(EventCategoryGamepad) + protected: + uint32_t m_GamepadIndex; + }; + + class GamepadConnectedEvent : public GamepadEvent + { + public: + GamepadConnectedEvent(uint32_t gamepadIndex) + : GamepadEvent(gamepadIndex) {} + + std::string ToString() const override + { + std::stringstream ss; + ss << "GamepadConnectedEvent: Player Index = " << m_GamepadIndex; + return ss.str(); + } + + EVENT_CLASS_TYPE(GamepadConnected) + }; + + class GamepadDisconnectedEvent : public GamepadEvent + { + public: + GamepadDisconnectedEvent(uint32_t gamepadIndex) + : GamepadEvent(gamepadIndex) {} + + std::string ToString() const override + { + std::stringstream ss; + ss << "GamepadDisconnectedEvent: Player Index = " << m_GamepadIndex; + return ss.str(); + } + + EVENT_CLASS_TYPE(GamepadDisconnected) + }; + +} diff --git a/Hazel/src/Platform/Windows/WindowsInput.cpp b/Hazel/src/Platform/Windows/WindowsInput.cpp index 3d56e0612..fac2c5ce1 100644 --- a/Hazel/src/Platform/Windows/WindowsInput.cpp +++ b/Hazel/src/Platform/Windows/WindowsInput.cpp @@ -4,8 +4,152 @@ #include "Hazel/Core/Application.h" #include +#include "Hazel/Events/GamepadEvent.h" + +#include + +// Define the layout of the function we're going to load. Make a default one in case we're unable to load it from the dll. +#define XINPUT_GET_STATE(name) DWORD name(DWORD dwUserIndex, XINPUT_STATE* pState) +typedef XINPUT_GET_STATE(xinput_get_state); +XINPUT_GET_STATE(XInputGetStateStub) { return ERROR_DEVICE_NOT_CONNECTED; } +static xinput_get_state* XInputGetState_ = XInputGetStateStub; +#define XInputGetState XInputGetState_ + +// Same here. +#define XINPUT_SET_STATE(name) DWORD name(DWORD dwUserIndex, XINPUT_VIBRATION* pVibration) +typedef XINPUT_SET_STATE(xinput_set_state); +XINPUT_SET_STATE(XInputSetStateStub) { return ERROR_DEVICE_NOT_CONNECTED; } +static xinput_set_state* XInputSetState_ = XInputSetStateStub; +#define XInputSetState XInputSetState_ + + namespace Hazel { + const float DeadzoneX = 0.05f; + const float DeadzoneY = 0.02f; + + struct GamepadState + { + bool IsConnected; + std::unordered_map Buttons; + glm::vec2 LeftJoystick; + glm::vec2 RightJoystick; + glm::vec2 Triggers; + glm::vec2 Vibration; + }; + + static std::array s_Gamepads; + static EventCallbackFn s_EventCallback; + + void Input::Init() + { + HZ_PROFILE_FUNCTION(); + + s_Gamepads = {}; + + HMODULE Module = LoadLibraryA("xinput1_4.dll"); + if (!Module) + { + HZ_CORE_ERROR("Failed to load xinput1_4.dll!"); + return; + } + + XInputGetState = (xinput_get_state*)GetProcAddress(Module, "XInputGetState"); + XInputSetState = (xinput_set_state*)GetProcAddress(Module, "XInputSetState"); + } + + void Input::SetEventCallback(const EventCallbackFn& callback) + { + s_EventCallback = callback; + } + + void Input::Update() + { + HZ_PROFILE_FUNCTION(); + + for (uint32_t i = 0; i < MaxGamepadCount; i++) + { + XINPUT_STATE state; + ZeroMemory(&state, sizeof(XINPUT_STATE)); + + DWORD result = XInputGetState(i, &state); + bool connected = (result == ERROR_SUCCESS); + + if (s_Gamepads[i].IsConnected != connected) + { + if (connected == true) + { + GamepadConnectedEvent event(i); + s_EventCallback(event); + } + else + { + GamepadDisconnectedEvent event(i); + s_EventCallback(event); + } + } + + s_Gamepads[i].IsConnected = connected; + if (connected) + { + // Diamond Buttons + s_Gamepads[i].Buttons[Gamepad::A] = (state.Gamepad.wButtons & XINPUT_GAMEPAD_A); + s_Gamepads[i].Buttons[Gamepad::B] = (state.Gamepad.wButtons & XINPUT_GAMEPAD_B); + s_Gamepads[i].Buttons[Gamepad::X] = (state.Gamepad.wButtons & XINPUT_GAMEPAD_X); + s_Gamepads[i].Buttons[Gamepad::Y] = (state.Gamepad.wButtons & XINPUT_GAMEPAD_Y); + + // DPad Buttons + s_Gamepads[i].Buttons[Gamepad::DPadDown] = (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_DOWN); + s_Gamepads[i].Buttons[Gamepad::DPadUp] = (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_UP); + s_Gamepads[i].Buttons[Gamepad::DPadLeft] = (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_LEFT); + s_Gamepads[i].Buttons[Gamepad::DPadRight] = (state.Gamepad.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT); + + // Navigation buttons + s_Gamepads[i].Buttons[Gamepad::Start] = (state.Gamepad.wButtons & XINPUT_GAMEPAD_START); + s_Gamepads[i].Buttons[Gamepad::Back] = (state.Gamepad.wButtons & XINPUT_GAMEPAD_BACK); + + // Shoulder buttons + s_Gamepads[i].Buttons[Gamepad::LeftShoulder] = (state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_SHOULDER); + s_Gamepads[i].Buttons[Gamepad::RightShoulder] = (state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_SHOULDER); + + // Thumbstick buttons + s_Gamepads[i].Buttons[Gamepad::LeftThumb] = (state.Gamepad.wButtons & XINPUT_GAMEPAD_LEFT_THUMB); + s_Gamepads[i].Buttons[Gamepad::RightThumb] = (state.Gamepad.wButtons & XINPUT_GAMEPAD_RIGHT_THUMB); + + // Triggers + s_Gamepads[i].Triggers.x = (float)state.Gamepad.bLeftTrigger / 255; + s_Gamepads[i].Triggers.y = (float)state.Gamepad.bRightTrigger / 255; + + // Left joystick + float normLX = fmaxf(-1, (float)state.Gamepad.sThumbLX / 32767); + float normLY = fmaxf(-1, (float)state.Gamepad.sThumbLY / 32767); + + s_Gamepads[i].LeftJoystick.x = (abs(normLX) < DeadzoneX ? 0 : (abs(normLX) - DeadzoneX) * (normLX / abs(normLX))); + s_Gamepads[i].LeftJoystick.y = (abs(normLY) < DeadzoneY ? 0 : (abs(normLY) - DeadzoneY) * (normLY / abs(normLY))); + s_Gamepads[i].LeftJoystick.x *= 1 / (1 - DeadzoneX); + s_Gamepads[i].LeftJoystick.y *= 1 / (1 - DeadzoneY); + + // Right joystick + float normRX = fmaxf(-1, (float)state.Gamepad.sThumbRX / 32767); + float normRY = fmaxf(-1, (float)state.Gamepad.sThumbRY / 32767); + + s_Gamepads[i].RightJoystick.x = (abs(normRX) < DeadzoneX ? 0 : (abs(normRX) - DeadzoneX) * (normRX / abs(normRX))); + s_Gamepads[i].RightJoystick.y = (abs(normRY) < DeadzoneY ? 0 : (abs(normRY) - DeadzoneY) * (normRY / abs(normRY))); + s_Gamepads[i].RightJoystick.x *= 1 / (1 - DeadzoneX); + s_Gamepads[i].RightJoystick.y *= 1 / (1 - DeadzoneY); + + // Set vibration + XINPUT_VIBRATION vibration = {}; + vibration.wLeftMotorSpeed = (uint16_t)(s_Gamepads[i].Vibration.x * 65335.0f); + vibration.wRightMotorSpeed = (uint16_t)(s_Gamepads[i].Vibration.y * 65335.0f); + XInputSetState(i, &vibration); + + // Reset vibration + s_Gamepads[i].Vibration = glm::vec2(0.0f); + } + } + } + bool Input::IsKeyPressed(const KeyCode key) { auto* window = static_cast(Application::Get().GetWindow().GetNativeWindow()); @@ -39,4 +183,33 @@ namespace Hazel { return GetMousePosition().y; } -} \ No newline at end of file + bool Input::IsGamepadButtonPressed(uint32_t playerIndex, GamepadCode code) + { + return s_Gamepads[playerIndex].Buttons[code] == true; + } + + bool Input::IsGamepadButtonReleased(uint32_t playerIndex, GamepadCode code) + { + return s_Gamepads[playerIndex].Buttons[code] == false; + } + + glm::vec2 Input::GetGamepadLeftJoystick(uint32_t playerIndex) + { + return s_Gamepads[playerIndex].LeftJoystick; + } + + glm::vec2 Input::GetGamepadRightJoystick(uint32_t playerIndex) + { + return s_Gamepads[playerIndex].RightJoystick; + } + + glm::vec2 Input::GetGamepadTriggers(uint32_t playerIndex) + { + return s_Gamepads[playerIndex].Triggers; + } + + void Input::SetGamepadVibration(uint32_t playerIndex, const glm::vec2& vibration) + { + s_Gamepads[playerIndex].Vibration = vibration; + } +}