So far, our example programs have been using the mouse to control a pointer within our window, letting users point and click on UI elements. However, many programs, especially first-person games, use the mouse differently. For example:
SDL_SetRelativeMouseMode()
SDL supports this interaction mode directly. It is called relative mouse mode, and we can toggle it using the SDL_SetRelativeMouseMode()
function. We pass SDL_TRUE
to enable it:
#include <SDL.h>
#include "Window.h"
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
SDL_SetRelativeMouseMode(SDL_TRUE);
SDL_Event E;
while (true) {/*...*/}
}
We can turn relative mode off again by passing SDL_FALSE
to SDL_SetRelativeMouseMode()
:
SDL_SetRelativeMouseMode(SDL_FALSE);
When enabling relative mouse mode, the SDL_Window
that currently has keyboard focus is used. We can ensure the correct SDL_Window
is used by raising it before enabling relative mode:
void SetRelativeMode(
SDL_Window* Window, bool Enable
) {
if (Enable) {
SDL_RaiseWindow(Window);
SDL_SetRelativeMouseMode(SDL_TRUE);
} else {
SDL_SetRelativeMouseMode(SDL_FALSE);
}
}
We cover input focus and SDL_RaiseWindow()
in more detail in our lesson on input focus:
SDL_SetWindowRelativeMouseMode()
In SDL3, the SDL_SetRelativeMouseMode()
function is deprecated and replaced by SDL_SetWindowRelativeMouseMode()
, which directly associates relative mode with a specific window.
Similar to our previous example, this accepts the SDL_Window
pointer as the first argument, and whether we want to enable relative mode as the second argument:
SDL_SetWindowRelativeMouseMode(
Window, SDL_TRUE
);
Enabling relative mode isn’t always supported. The SDL_SetRelativeMouseMode()
function returns a negative error code if it fails, or 0
if it succeeds.
We can check this return value to determine if an error occurred, and use SDL_GetError()
for an explanation of the error:
if (SDL_SetRelativeMouseMode(SDL_TRUE) == 0) {
std::cout << "Relative mode enabled\n";
} else {
std::cout << "Error enabling relative mode: "
<< SDL_GetError() << '\n';
}
SDL_GetRelativeMouseMode()
We can check if relative mode is currently enabled using SDL_GetRelativeMouseMode()
:
if (SDL_GetRelativeMouseMode()) {
std::cout << "Relative mode is enabled\n";
} else {
std::cout << "Relative mode not enabled\n";
}
Many applications require switching dynamically between relative and non-relative modes based on the user's context. For instance, a first-person game might use relative mode during gameplay for seamless camera control but switch to non-relative mode for navigating menus.
To implement this, you can toggle relative mode dynamically based on user input. Below is an example where pressing the Tab key toggles between the two modes:
#include <SDL.h>
#include "Window.h"
void HandleKeyboardEvent(SDL_KeyboardEvent& E) {
if (E.keysym.sym == SDLK_TAB) {
SDL_SetRelativeMouseMode(
SDL_GetRelativeMouseMode()
? SDL_FALSE
: SDL_TRUE
);
}
}
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
SDL_Event E;
while (true) {
while (SDL_PollEvent(&E)) {
if (E.type == SDL_KEYDOWN) {
HandleKeyboardEvent(E.key);
} else if (E.type == SDL_QUIT) {
SDL_Quit();
return 0;
}
}
GameWindow.Update();
GameWindow.Render();
}
}
Since the player typically doesn’t control a cursor in relative mode, it makes sense to hide the cursor and constrain it to the center of the window. This is the default behavior in SDL, however we can change it by setting appropriate hints.
SDL_HINT_MOUSE_RELATIVE_CURSOR_VISIBLE
This hint determines whether the cursor is visible when relative mode is on. By default, it is set to "0"
, corresponding to the cursor being invisible.
We can change it to "1"
to show the cursor:
SDL_SetHint(
SDL_HINT_MOUSE_RELATIVE_CURSOR_VISIBLE, "1"
);
SDL_HINT_MOUSE_RELATIVE_MODE_CENTER
When relative mode is enabled, this hint controls whether the cursor is constrained to the center of the window, or can move anywhere within the window. By default, it is set to "1"
, locking the cursor to the center.
We can change it to "0"
to let the cursor move anywhere within the window:
SDL_SetHint(
SDL_HINT_MOUSE_RELATIVE_MODE_CENTER, "0"
);
When relative mode is enabled, we typically don’t care about where the cursor is in our window. Often, the cursor is just going to be locked in the middle of the window, and will typically be hidden anyway.
Instead, we care about the relative movement of the mouse - that is, the direction and distance the mouse has moved compared to some previous moment in time.
Within our event loop, these values are available within the xrel
and yrel
members of the SDL_MouseMotionEvent
. These values report where the mouse has moved in relation to the previous SDL_MouseMotionEvent
:
#include <SDL.h>
#include <iostream>
#include "Window.h"
void HandleMotionEvent(SDL_MouseMotionEvent& E) {
std::cout << "Relative mouse motion: "
<< E.xrel << ", " << E.yrel << '\n';
}
int main(int argc, char** argv) {/*...*/}
Relative mouse motion: -1, 0
Relative mouse motion: 0, -1
Relative mouse motion: 3, 0
Using the xrel
and yrel
values doesn’t require relative mode to be enabled. We can also use these values if we care about where the cursor moved relative to the previous mouse motion event.
Remember, we can also use SDL_GetRelativeMouseMode()
to check whether relative mode is enabled or not. This can help us when the behavior of our event handler needs to adapt accordingly:
#include <SDL.h>
#include <iostream>
#include "Window.h"
void HandleMotionEvent(SDL_MouseMotionEvent& E) {
if (SDL_GetRelativeMouseMode()) {
std::cout << "Relative mouse motion: "
<< E.xrel << ", " << E.yrel << '\n';
} else {
std::cout << "Cursor window position: "
<< E.x << ", " << E.y << '\n';
}
}
int main(int argc, char** argv) {/*...*/}
Cursor window position: 428, 81
Cursor window position: 428, 82
Cursor window position: 428, 83
Previously, we introduced how we could query the mouse state at any time using SDL_GetMouseState()
:
When using relative mode, SDL_GetRelativeMouseState()
is likely to be more useful. It reports how far the mouse has moved, relative to the previous invocation of SDL_GetRelativeMouseState()
.
It operates similarly to SDL_GetMouseState()
but provides the relative movement of the mouse since the last call, along with the button state. The function updates the provided integer pointers with the relative x and y values:
int x, y;
SDL_GetRelativeMouseState(&x, &y);
We can pass a nullptr
as either argument if we only care about relative movement in one direction:
// We only care about horizontal motion
int x;
SDL_GetRelativeMouseState(&x, nullptr);
// We only care about vertical motion
int y;
SDL_GetRelativeMouseState(nullptr, &y);
Because SDL_GetRelativeMouseState()
is sensitive to previous invocations, it is typically the case that we only want a single object monitoring the relative mouse state:
// MouseTracker.h
#pragma once
#include <SDL.h>
#include <iostream>
struct MouseTracker {
void Tick() {
int x, y;
SDL_GetRelativeMouseState(&x, &y);
std::cout << "Mouse movement this frame: "
<< x << ", " << y << '\n';
}
};
#include <SDL.h>
#include "Window.h"
#include "MouseTracker.h"
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
MouseTracker Tracker;
SDL_Event E;
while (true) {
while (SDL_PollEvent(&E)) {/*...*/}
GameWindow.Update();
Tracker.Tick();
GameWindow.Render();
}
}
Mouse movement this frame: 0, 1
Mouse movement this frame: -1, 2
Mouse movement this frame: 0, 2
If we call the function more than once in a single tick, all invocations except the first will report no further movement:
#include <SDL.h>
#include "Window.h"
#include "MouseTracker.h"
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
MouseTracker TrackerA;
MouseTracker TrackerB;
SDL_Event E;
while (true) {
while (SDL_PollEvent(&E)) {/*...*/}
GameWindow.Update();
TrackerA.Tick();
TrackerB.Tick();
std::cout << '\n';
GameWindow.Render();
}
}
Mouse movement this frame: 0, 1
Mouse movement this frame: 0, 0
Mouse movement this frame: -1, 2
Mouse movement this frame: 0, 0
Mouse movement this frame: 0, 2
Mouse movement this frame: 0, 0
SDL_GetRelativeMouseState()
doesn’t require relative mode to be enabled. Even if the player is controlling a pointer, there are some use cases where we’re interested in the relative motion of that pointer, and SDL_GetRelativeMouseState()
can help with that.
In our previous lesson on mouse state, we covered how to determine if our mouse buttons are currently pressed using the return value of SDL_GetMouseState()
.
The SDL_GetRelativeMouseState()
function returns this same value, so we can use it in the same way:
// MouseTracker.h
#pragma once
#include <SDL.h>
#include <iostream>
struct MouseTracker {
void Tick() {
int x, y;
Uint32 Buttons{
SDL_GetRelativeMouseState(&x, &y)};
if (Buttons & SDL_BUTTON_LMASK) {
std::cout << "Left button is pressed\n";
}
if (Buttons & SDL_BUTTON_MMASK) {
std::cout << "Middle button is pressed\n";
}
if (Buttons & SDL_BUTTON_RMASK) {
std::cout << "Right button is pressed\n";
}
}
};
If we don’t care about the mouse position and only care about the mouse button state, SDL_GetRelativeMouseState()
and SDL_GetMouseState()
are equivalent:
Uint32 ButtonsA{
SDL_GetRelativeMouseState(nullptr, nullptr)};
// Equivalent:
Uint32 ButtonsB{
SDL_GetMouseState(nullptr, nullptr)};
In most scenarios, we’ll want to apply some coefficient to the relative mouse movement reported by SDL. For example, if the mouse is being used to control a camera in our game, we can multiply the relative movement by some coefficients to make the camera move slower or faster:
#include <SDL.h>
#include "Window.h"
struct Camera {
void HandleMotion(SDL_MouseMotionEvent& E) {
xPosition += E.xrel * CameraSpeed;
yPosition += E.yrel * CameraSpeed;
}
// Make the camera move at double speed
float CameraSpeed{2.0f};
// Make the camera move at half speed
// float CameraSpeed{0.5f};
int xPosition;
int yPosition;
};
int main(int argc, char** argv) {/*...*/}
In more complex games, we’d let the player define this coefficient within an options menu. This lets them specify the exact level of mouse reponsiveness that they prefer.
SDL_HINT_MOUSE_RELATIVE_SPEED_SCALE
In addition to scaling the relative speed on a case-by-case basis, we can prompt SDL to scale the speed before it is reported to us.
We can do this by setting the SDL_HINT_MOUSE_RELATIVE_SPEED_SCALE
. This will be used to scale the movement reported by SDL within mouse motion events and SDL_GetMouseStateRelative()
when relative mode is enabled. By default, this has a value of 1.0
, but we can scale it up or down as desired:
// Double the relative movement values
SDL_SetHint(
SDL_HINT_MOUSE_RELATIVE_SPEED_SCALE, "2.0");
// Halve the relative movement values
SDL_SetHint(
SDL_HINT_MOUSE_RELATIVE_SPEED_SCALE, "0.5");
SDL_HINT_MOUSE_RELATIVE_SYSTEM_SCALE
When our operating system receives mouse input, the platform typically reinterprets those inputs to tweak the speed and acceleration with which our cursor moves.
These remappings are designed on the assumption our mouse is controlling a cursor. In relative mode, we’re usually not controlling a cursor, so this intervention generally degrades the user experience.
As such, by default, SDL ignores these system settings when relative mode is enabled. We can change this by setting the SDL_HINT_MOUSE_RELATIVE_SYSTEM_SCALE
to 1
, instead of its default value of 0
:
// Respect system mouse acceleration
SDL_SetHint(
SDL_HINT_MOUSE_RELATIVE_SYSTEM_SCALE, "1");
// Ignore system mouse acceleration (default)
SDL_SetHint(
SDL_HINT_MOUSE_RELATIVE_SYSTEM_SCALE, "0");
Relative mouse mode in SDL2 allows developers to capture continuous mouse motion within a window, particularly useful for first-person games and applications that require precise directional tracking.
This mode differs from traditional pointer-based interactions by focusing on relative movement instead of absolute cursor position. Key takeaways:
SDL_SetRelativeMouseMode()
: Enables or disables relative mouse modeSDL_GetRelativeMouseMode()
: Checks if relative mouse mode is currently activeSDL_GetRelativeMouseState()
: Retrieves relative mouse movement and button statesSDL_RaiseWindow()
: Ensures the correct window has input focusSDL_SetHint()
: Set hints to control cursor visibility and movement characteristicsLearn how to restrict cursor movement to a window whilst capturing mouse motion continuously.
Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games