Hi everybody!
I’m working on a local multiplayer air hockey game where you connect two mice to your computer, and each player controls their own pusher with their mouse. Godot doesn’t natively support raw input from mice since it’s a very OS specific problem, but I was finally able to make an extension that brings support for Raw Input on Windows!!!
Here’s my GDExtension C++ code if you’re interested in using Raw Input in your own games:
// raw_mouse.hpp
#pragma once
#include <godot_cpp/classes/object.hpp>
#include <godot_cpp/variant/vector2.hpp>
#include <Windows.h>
#pragma comment(lib, "user32.lib")
namespace godot
{
class RawMouse: public Object
{
GDCLASS(RawMouse, Object)
private:
Vector2 p1_position;
Vector2 p2_position;
HANDLE p1_mouse;
HANDLE p2_mouse;
float p1_mouse_sensitivity;
float p2_mouse_sensitivity;
HWND hwnd;
protected:
static void _bind_methods();
public:
WNDPROC original_wndproc;
RawMouse();
~RawMouse();
void handle_mouse(LPARAM lParam);
int is_p1_connected();
int is_p2_connected();
Vector2 get_p1_position();
Vector2 get_p2_position();
void set_p1_position(Vector2 new_position);
void set_p2_position(Vector2 new_position);
void set_p1_sensitivity(float value);
void set_p2_sensitivity(float value);
};
}
// raw_mouse.cpp
#include "raw_mouse.hpp"
#include <godot_cpp/core/class_db.hpp>
#include <godot_cpp/classes/display_server.hpp>
using namespace godot;
RawMouse* get_raw_mouse(HWND hwnd)
{
if (!hwnd) {
return nullptr;
}
LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
return reinterpret_cast<RawMouse*>(ptr);
}
LRESULT raw_wndproc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
RawMouse *rm = get_raw_mouse(hwnd);
if (rm && uMsg == WM_INPUT) {
rm->handle_mouse(lParam);
}
if (rm && rm->original_wndproc) {
return CallWindowProc(rm->original_wndproc, hwnd, uMsg, wParam, lParam);
}
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
void RawMouse::handle_mouse(LPARAM lParam)
{
UINT dwSize = sizeof(RAWINPUT);
RAWINPUT raw;
if (GetRawInputData(reinterpret_cast<HRAWINPUT>(lParam), RID_INPUT, reinterpret_cast<LPVOID>(&raw), &dwSize, sizeof(RAWINPUTHEADER)) == -1)
{
return;
}
if (raw.header.dwType != RIM_TYPEMOUSE)
{
return;
}
if (raw.header.hDevice == p1_mouse)
{
p1_position.x += p1_mouse_sensitivity * static_cast<float>(raw.data.mouse.lLastX);
p1_position.y += p1_mouse_sensitivity * static_cast<float>(raw.data.mouse.lLastY);
return;
}
if (raw.header.hDevice == p2_mouse)
{
p2_position.x += p2_mouse_sensitivity * static_cast<float>(raw.data.mouse.lLastX);
p2_position.y += p2_mouse_sensitivity * static_cast<float>(raw.data.mouse.lLastY);
return;
}
if (p1_mouse == INVALID_HANDLE_VALUE)
{
p1_mouse = raw.header.hDevice;
return;
}
if (p2_mouse == INVALID_HANDLE_VALUE)
{
p2_mouse = raw.header.hDevice;
return;
}
}
void RawMouse::_bind_methods()
{
ClassDB::bind_method(D_METHOD("is_p1_connected"), &RawMouse::is_p1_connected);
ClassDB::bind_method(D_METHOD("is_p2_connected"), &RawMouse::is_p2_connected);
ClassDB::bind_method(D_METHOD("get_p1_position"), &RawMouse::get_p1_position);
ClassDB::bind_method(D_METHOD("get_p2_position"), &RawMouse::get_p2_position);
ClassDB::bind_method(D_METHOD("set_p1_position", "new_position"), &RawMouse::set_p1_position);
ClassDB::bind_method(D_METHOD("set_p2_position", "new_position"), &RawMouse::set_p2_position);
ClassDB::bind_method(D_METHOD("set_p1_sensitivity", "value"), &RawMouse::set_p1_sensitivity);
ClassDB::bind_method(D_METHOD("set_p2_sensitivity", "value"), &RawMouse::set_p2_sensitivity);
}
RawMouse::RawMouse()
{
p1_position = Vector2(0.0, 0.0);
p2_position = Vector2(0.0, 0.0);
p1_mouse = INVALID_HANDLE_VALUE;
p2_mouse = INVALID_HANDLE_VALUE;
p1_mouse_sensitivity = 1.0f;
p2_mouse_sensitivity = 1.0f;
hwnd = nullptr;
original_wndproc = nullptr;
RAWINPUTDEVICE Rid[1];
Rid[0].usUsagePage = 0x01;
Rid[0].usUsage = 0x02;
Rid[0].dwFlags = RIDEV_NOLEGACY;
Rid[0].hwndTarget = 0;
if (RegisterRawInputDevices(Rid, 1, sizeof(Rid[0])) == FALSE) {
print_line("Raw input registration failed");
}
hwnd = reinterpret_cast<HWND>(DisplayServer::get_singleton()->window_get_native_handle(DisplayServer::HandleType::WINDOW_HANDLE));
if (!hwnd)
{
print_line("RawMouse constructor failed to retrieve HWND");
}
original_wndproc = reinterpret_cast<WNDPROC>(SetWindowLongPtr(hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(raw_wndproc)));
SetWindowLongPtr(hwnd, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));
}
RawMouse::~RawMouse()
{
if (hwnd) {
SetWindowLongPtr(hwnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(original_wndproc));
}
}
int RawMouse::is_p1_connected()
{
if (p1_mouse == INVALID_HANDLE_VALUE)
{
return 0;
}
else
{
return 1;
}
}
int RawMouse::is_p2_connected()
{
if (p2_mouse == INVALID_HANDLE_VALUE)
{
return 0;
}
else
{
return 1;
}
}
Vector2 RawMouse::get_p1_position()
{
return p1_position;
}
Vector2 RawMouse::get_p2_position()
{
return p2_position;
}
void RawMouse::set_p1_position(Vector2 new_position)
{
p1_position = new_position;
}
void RawMouse::set_p2_position(Vector2 new_position)
{
p2_position = new_position;
}
void RawMouse::set_p1_sensitivity(float value)
{
p1_mouse_sensitivity = value;
}
void RawMouse::set_p2_sensitivity(float value)
{
p2_mouse_sensitivity = value;
}
Let me know if you have any questions or any ideas you have for other games concepts that could use Raw Input!