From e312d0e54ad4036e9b604921eaeaaadce61346d2 Mon Sep 17 00:00:00 2001 From: Chris Cascioli Date: Thu, 19 Aug 2021 14:14:35 -0400 Subject: [PATCH] Added a custom input manager Updated a few comments --- DX11Starter.vcxproj | 2 + DX11Starter.vcxproj.filters | 6 + DXCore.cpp | 22 ++- Game.cpp | 5 +- Input.cpp | 282 ++++++++++++++++++++++++++++++++++++ Input.h | 86 +++++++++++ 6 files changed, 399 insertions(+), 4 deletions(-) create mode 100644 Input.cpp create mode 100644 Input.h diff --git a/DX11Starter.vcxproj b/DX11Starter.vcxproj index 0e1ebf5..d920499 100644 --- a/DX11Starter.vcxproj +++ b/DX11Starter.vcxproj @@ -125,11 +125,13 @@ + + diff --git a/DX11Starter.vcxproj.filters b/DX11Starter.vcxproj.filters index 4f0fa0b..c7deda6 100644 --- a/DX11Starter.vcxproj.filters +++ b/DX11Starter.vcxproj.filters @@ -27,6 +27,9 @@ Source Files + + Source Files + @@ -38,6 +41,9 @@ Header Files + + Header Files + diff --git a/DXCore.cpp b/DXCore.cpp index e7553a9..daa290d 100644 --- a/DXCore.cpp +++ b/DXCore.cpp @@ -1,4 +1,5 @@ #include "DXCore.h" +#include "Input.h" #include #include @@ -72,6 +73,9 @@ DXCore::~DXCore() // we don't need to explicitly clean up those DirectX objects // - If we weren't using smart pointers, we'd need // to call Release() on each DirectX object created in DXCore + + // Delete input manager singleton + delete& Input::GetInstance(); } // -------------------------------------------------------- @@ -147,6 +151,9 @@ HRESULT DXCore::InitWindow() // We need to tell Windows to show it, and how to show it ShowWindow(hWnd, SW_SHOW); + // Initialize the input manager now that we definitely have a window + Input::GetInstance().Initialize(hWnd); + // Return an "everything is ok" HRESULT value return S_OK; } @@ -396,9 +403,15 @@ HRESULT DXCore::Run() if(titleBarStats) UpdateTitleBarStats(); + // Update the input manager + Input::GetInstance().Update(); + // The game loop Update(deltaTime, totalTime); Draw(deltaTime, totalTime); + + // Frame is over, notify the input manager + Input::GetInstance().EndOfFrame(); } } @@ -532,7 +545,7 @@ void DXCore::CreateConsoleWindow(int bufferLines, int bufferColumns, int windowL // // - As it turns out, the relative path for a program is different when // running through VS and when running the .exe directly, which makes -// it a pain to properly load files external files (like textures) +// it a pain to properly load external files (like textures) // - Running through VS: Current Dir is the *project folder* // - Running from .exe: Current Dir is the .exe's folder // - This has nothing to do with DEBUG and RELEASE modes - it's purely a @@ -540,7 +553,7 @@ void DXCore::CreateConsoleWindow(int bufferLines, int bufferColumns, int windowL // for it. In fact, it could be fixed by changing a setting in VS, but // the option is stored in a user file (.suo), which is ignored by most // version control packages by default. Meaning: the option must be -// changed every on every PC. Ugh. So instead, here's a helper. +// changed on every PC. Ugh. So instead, here's a helper. // -------------------------------------------------------------------------- std::string DXCore::GetExePath() { @@ -660,6 +673,11 @@ LRESULT DXCore::ProcessMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lPara OnResize(); return 0; + + // Has the mouse wheel been scrolled? + case WM_MOUSEWHEEL: + Input::GetInstance().SetWheelDelta(GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA); + return 0; // Is our focus state changing? case WM_SETFOCUS: hasFocus = true; return 0; diff --git a/Game.cpp b/Game.cpp index 9cbc56a..b3c4405 100644 --- a/Game.cpp +++ b/Game.cpp @@ -1,5 +1,6 @@ #include "Game.h" #include "Vertex.h" +#include "Input.h" // Needed for a helper function to read compiled shader files from the hard drive #pragma comment(lib, "d3dcompiler.lib") @@ -236,8 +237,8 @@ void Game::OnResize() // -------------------------------------------------------- void Game::Update(float deltaTime, float totalTime) { - // Quit if the escape key is pressed - if (GetAsyncKeyState(VK_ESCAPE)) + // Example input checking: Quit if the escape key is pressed + if (Input::GetInstance().KeyDown(VK_ESCAPE)) Quit(); } diff --git a/Input.cpp b/Input.cpp new file mode 100644 index 0000000..ef3d6c0 --- /dev/null +++ b/Input.cpp @@ -0,0 +1,282 @@ +#include "Input.h" + +// Singleton requirement +Input* Input::instance; + +// --------------- Basic usage ----------------- +// +// The keyboard functions all take a single character +// like 'W', ' ' or '8' (which will implicitly cast +// to an int) or a pre-defined virtual key code like +// VK_SHIFT, VK_ESCAPE or VK_TAB. These virtual key +// codes are are accessible through the Windows.h +// file (already included in Input.h). See the +// following for a complete list of virtual key codes: +// https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes +// +// Checking if various keys are down or up: +// +// if (Input::GetInstance().KeyDown('W')) { } +// if (Input::GetInstance().KeyUp('2')) { } +// if (Input::GetInstance().KeyDown(VK_SHIFT)) { } +// +// +// Checking if a key was initially pressed or released +// this frame: +// +// if (Input::GetInstance().KeyPressed('Q')) { } +// if (Input::GetInstance().KeyReleased(' ')) { } +// +// (Note that these functions will only return true on +// the FIRST frame that a key is pressed or released.) +// +// +// Checking for mouse input: +// +// if (Input::GetInstance().MouseLeftDown()) { } +// if (Input::GetInstance().MouseRightDown()) { } +// if (Input::GetInstance().MouseMiddleUp()) { } +// if (Input::GetInstance().MouseLeftPressed()) { } +// if (Input::GetInstance().MouseRightReleased()) { } +// +// --------------------------------------------- + +// -------------- Less verbose ----------------- +// +// If you'd rather not have to type Input::GetInstance() +// over and over, you can save the reference in a variable: +// +// Input& input = Input::GetInstance(); +// if (input.KeyDown('W')) { } +// if (input.KeyDown('A')) { } +// if (input.KeyDown('S')) { } +// if (input.KeyDown('D')) { } +// +// --------------------------------------------- + + +// -------------------------- +// Cleans up the key arrays +// -------------------------- +Input::~Input() +{ + delete[] kbState; + delete[] prevKbState; +} + +// --------------------------------------------------- +// Initializes the input variables and sets up the +// initial arrays of key states +// +// windowHandle - the handle (id) of the window, +// which is necessary for mouse input +// --------------------------------------------------- +void Input::Initialize(HWND windowHandle) +{ + kbState = new unsigned char[256]; + prevKbState = new unsigned char[256]; + + memset(kbState, 0, sizeof(unsigned char) * 256); + memset(prevKbState, 0, sizeof(unsigned char) * 256); + + wheelDelta = 0.0f; + mouseX = 0; mouseY = 0; + prevMouseX = 0; prevMouseY = 0; + mouseXDelta = 0; mouseYDelta = 0; + + this->windowHandle = windowHandle; +} + +// ---------------------------------------------------------- +// Updates the input manager for this frame. This should +// be called at the beginning of every Game::Update(), +// before anything that might need input +// ---------------------------------------------------------- +void Input::Update() +{ + // Copy the old keys so we have last frame's data + memcpy(prevKbState, kbState, sizeof(unsigned char) * 256); + + // Get the latest keys (from Windows) + GetKeyboardState(kbState); + + // Get the current mouse position then make it relative to the window + POINT mousePos = {}; + GetCursorPos(&mousePos); + ScreenToClient(windowHandle, &mousePos); + + // Save the previous mouse position, then the current mouse + // position and finally calculate the change from the previous frame + prevMouseX = mouseX; + prevMouseY = mouseY; + mouseX = mousePos.x; + mouseY = mousePos.y; + mouseXDelta = mouseX - prevMouseX; + mouseYDelta = mouseY - prevMouseY; +} + +// ---------------------------------------------------------- +// Resets the mouse wheel value at the end of the frame. +// This cannot occur earlier in the frame, since the wheel +// input comes from Win32 windowing messages, which are +// handled between frames. +// ---------------------------------------------------------- +void Input::EndOfFrame() +{ + // Reset wheel value + wheelDelta = 0; +} + +// ---------------------------------------------------------- +// Get the mouse's current position in pixels relative +// to the top left corner of the window. +// ---------------------------------------------------------- +int Input::GetMouseX() { return mouseX; } +int Input::GetMouseY() { return mouseY; } + + +// --------------------------------------------------------------- +// Get the mouse's change (delta) in position since last +// frame in pixels relative to the top left corner of the window. +// --------------------------------------------------------------- +int Input::GetMouseXDelta() { return mouseXDelta; } +int Input::GetMouseYDelta() { return mouseYDelta; } + + +// --------------------------------------------------------------- +// Get the mouse wheel delta for this frame. Note that there is +// no absolute position for the mouse wheel; this is either a +// positive number, a negative number or zero. +// --------------------------------------------------------------- +float Input::GetMouseWheel() { return wheelDelta; } + + +// --------------------------------------------------------------- +// Sets the mouse wheel delta for this frame. This is called +// by DXCore whenever an OS-level mouse wheel message is sent +// to the application. You'll never need to call this yourself. +// --------------------------------------------------------------- +void Input::SetWheelDelta(float delta) +{ + wheelDelta = delta; +} + + +// ---------------------------------------------------------- +// Is the given key down this frame? +// +// key - The key to check, which could be a single character +// like 'W' or '3', or a virtual key code like VK_TAB, +// VK_ESCAPE or VK_SHIFT. +// ---------------------------------------------------------- +bool Input::KeyDown(int key) +{ + if (key < 0 || key > 255) return false; + + return (kbState[key] & 0x80) != 0; +} + +// ---------------------------------------------------------- +// Is the given key up this frame? +// +// key - The key to check, which could be a single character +// like 'W' or '3', or a virtual key code like VK_TAB, +// VK_ESCAPE or VK_SHIFT. +// ---------------------------------------------------------- +bool Input::KeyUp(int key) +{ + if (key < 0 || key > 255) return false; + + return !(kbState[key] & 0x80); +} + +// ---------------------------------------------------------- +// Was the given key initially pressed this frame? +// +// key - The key to check, which could be a single character +// like 'W' or '3', or a virtual key code like VK_TAB, +// VK_ESCAPE or VK_SHIFT. +// ---------------------------------------------------------- +bool Input::KeyPress(int key) +{ + if (key < 0 || key > 255) return false; + + return + kbState[key] & 0x80 && // Down now + !(prevKbState[key] & 0x80); // Up last frame +} + +// ---------------------------------------------------------- +// Was the given key initially released this frame? +// +// key - The key to check, which could be a single character +// like 'W' or '3', or a virtual key code like VK_TAB, +// VK_ESCAPE or VK_SHIFT. +// ---------------------------------------------------------- +bool Input::KeyRelease(int key) +{ + if (key < 0 || key > 255) return false; + + return + !(kbState[key] & 0x80) && // Up now + prevKbState[key] & 0x80; // Down last frame +} + + +// ---------------------------------------------------------- +// A utility function to fill a given array of booleans +// with the current state of the keyboard. This is most +// useful when hooking the engine's input up to another +// system, such as a user interface library. (You probably +// won't use this very much, if at all!) +// +// keyArray - pointer to a boolean array which will be +// filled with the current keyboard state +// size - the size of the boolean array (up to 256) +// +// Returns true if the size parameter was valid and false +// if it was <= 0 or > 256 +// ---------------------------------------------------------- +bool Input::GetKeyArray(bool* keyArray, int size) +{ + if (size <= 0 || size > 256) return false; + + // Loop through the given size and fill the + // boolean array. Note that the double exclamation + // point is on purpose; it's a quick way to + // convert any number to a boolean. + for (int i = 0; i < size; i++) + keyArray[i] = !!(kbState[i] & 0x80); + + return true; +} + + +// ---------------------------------------------------------- +// Is the specific mouse button down this frame? +// ---------------------------------------------------------- +bool Input::MouseLeftDown() { return (kbState[VK_LBUTTON] & 0x80) != 0; } +bool Input::MouseRightDown() { return (kbState[VK_RBUTTON] & 0x80) != 0; } +bool Input::MouseMiddleDown() { return (kbState[VK_MBUTTON] & 0x80) != 0; } + + +// ---------------------------------------------------------- +// Is the specific mouse button up this frame? +// ---------------------------------------------------------- +bool Input::MouseLeftUp() { return !(kbState[VK_LBUTTON] & 0x80); } +bool Input::MouseRightUp() { return !(kbState[VK_RBUTTON] & 0x80); } +bool Input::MouseMiddleUp() { return !(kbState[VK_MBUTTON] & 0x80); } + + +// ---------------------------------------------------------- +// Was the specific mouse button initially +// pressed or released this frame? +// ---------------------------------------------------------- +bool Input::MouseLeftPress() { return kbState[VK_LBUTTON] & 0x80 && !(prevKbState[VK_LBUTTON] & 0x80); } +bool Input::MouseLeftRelease() { return !(kbState[VK_LBUTTON] & 0x80) && prevKbState[VK_LBUTTON] & 0x80; } + +bool Input::MouseRightPress() { return kbState[VK_RBUTTON] & 0x80 && !(prevKbState[VK_RBUTTON] & 0x80); } +bool Input::MouseRightRelease() { return !(kbState[VK_RBUTTON] & 0x80) && prevKbState[VK_RBUTTON] & 0x80; } + +bool Input::MouseMiddlePress() { return kbState[VK_MBUTTON] & 0x80 && !(prevKbState[VK_MBUTTON] & 0x80); } +bool Input::MouseMiddleRelease() { return !(kbState[VK_MBUTTON] & 0x80) && prevKbState[VK_MBUTTON] & 0x80; } diff --git a/Input.h b/Input.h new file mode 100644 index 0000000..7914a11 --- /dev/null +++ b/Input.h @@ -0,0 +1,86 @@ +#pragma once + +#include + +class Input +{ +#pragma region Singleton +public: + // Gets the one and only instance of this class + static Input& GetInstance() + { + if (!instance) + { + instance = new Input(); + } + + return *instance; + } + + // Remove these functions (C++ 11 version) + Input(Input const&) = delete; + void operator=(Input const&) = delete; + +private: + static Input* instance; + Input() {}; +#pragma endregion + +public: + ~Input(); + + void Initialize(HWND windowHandle); + void Update(); + void EndOfFrame(); + + int GetMouseX(); + int GetMouseY(); + int GetMouseXDelta(); + int GetMouseYDelta(); + float GetMouseWheel(); + void SetWheelDelta(float delta); + + bool KeyDown(int key); + bool KeyUp(int key); + + bool KeyPress(int key); + bool KeyRelease(int key); + + bool GetKeyArray(bool* keyArray, int size = 256); + + bool MouseLeftDown(); + bool MouseRightDown(); + bool MouseMiddleDown(); + + bool MouseLeftUp(); + bool MouseRightUp(); + bool MouseMiddleUp(); + + bool MouseLeftPress(); + bool MouseLeftRelease(); + + bool MouseRightPress(); + bool MouseRightRelease(); + + bool MouseMiddlePress(); + bool MouseMiddleRelease(); + +private: + // Arrays for the current and previous key states + unsigned char* kbState; + unsigned char* prevKbState; + + // Mouse position and wheel data + int mouseX; + int mouseY; + int prevMouseX; + int prevMouseY; + int mouseXDelta; + int mouseYDelta; + float wheelDelta; + + // The window's handle (id) from the OS, so + // we can get the cursor's position + HWND windowHandle; +}; +