Previously, we introduced how SDL dispatches events when the player moves their mouse within our window, or when they click their mouse buttons when our window has input focus:
#include <SDL.h>
#include <iostream>
#include "Window.h"
void HandleButton(SDL_MouseButtonEvent& E) {
if (E.button == SDL_BUTTON_LEFT) {
if (E.state == SDL_PRESSED) {
std::cout << "Left button pressed at ("
<< E.x << ", " << E.y << ")\n";
} else if (E.state == SDL_RELEASED) {
std::cout << "Left button released at ("
<< E.x << ", " << E.y << ")\n";
}
}
}
void HandleMotion(SDL_MouseMotionEvent& E) {
std::cout << "Mouse moved to ("
<< E.x << ", " << E.y << ")\n";
}
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_MOUSEBUTTONDOWN
|| E.type == SDL_MOUSEBUTTONUP) {
HandleButton(E.button);
} else if (E.type == SDL_MOUSEMOTION) {
HandleMotion(E.motion);
}
}
GameWindow.Update();
}
SDL_Quit();
return 0;
}
Mouse moved to (226, 4)
Left button pressed at (226, 4)
Mouse moved to (227, 4)
Mouse moved to (228, 5)
Left button released at (228, 5)
This allows us to react to our user’s mouse input, but it is not the only approach that we can use. Instead of reacting to events, we are free to query the mouse’s current position and which buttons are pressed at any time.
We covered how to track mouse motion and clicking through the event loop in our introductory lesson:
This lesson covers techniques for tracking the mouse independently of the event loop, and practical examples of why we’d want to.
SDL_GetMouseState()
SDL_GetMouseState
accepts two int
pointers, which it will update with the mouse’s current x
and y
coordinates.
int x, y;
SDL_GetMouseState(&x, &y);
std::cout << "Mouse is at " << x << ", " << y;
Just like with mouse events, these values are relative to the top left of the window. If the player’s cursor is at the extreme top left corner, then x
and y
will be updated to have values 0
and 0
.
To get the state of the buttons, we use the return value of SDL_GetMouseState()
. The function returns a 32-bit unsigned integer representing a bit mask:
int x, y;
Uint32 Buttons{SDL_GetMouseState(&x, &y)};
A bit mask uses individual bits within a number to store multiple yes/no (boolean) values efficiently. Think of it like a row of switches - each bit represents one switch that can be either on (1
) or off (0
).
In our case, each bit represents whether a specific mouse button is pressed (1
) or not pressed (0
). This lets us track multiple button states using a single number. SDL provides helper constants to use with these bit masks, allowing us to isolate the bit representing the button we’re interested in:
SDL_BUTTON_LMASK
checks if the left button is pressedSDL_BUTTON_RMASK
checks if the right button is pressedWe use the &
(bitwise AND) operator to test if a specific button is pressed:
int x, y;
Uint32 Buttons { SDL_GetMouseState(&x, &y) };
bool LeftPressed = Buttons & SDL_BUTTON_LMASK;
bool RightPressed = Buttons & SDL_BUTTON_RMASK;
We cover bit masks and the &
operator in more detail in our introductory course:
nullptr
to SDL_GetMouseState()
If we don’t care about the mouse’s x
or y
position, we can pass nullptr
to either (or both) of those parameters:
int x, y;
// We only care about the x (horizontal) position
SDL_GetMouseState(&x, nullptr);
// We only care about the y (vertical) position
SDL_GetMouseState(nullptr, &y);
Uint32 Buttons{
// We only care about the buttons
SDL_GetMouseState(nullptr, nullptr)
};
Naturally, if we wanted to be notified when the player moves their mouse or clicks a mouse button, we’d monitor the event loop for those specific events.
Querying the state of the mouse directly using SDL_GetMouseState()
is for scenarios when we need to understand what’s going on with the mouse when some other event happens.
For example, we might have a gameplay behavior where the player shoots in the direction of their cursor when they press a button on their keyboard. Rather than tracking the cursor continuously through the event loop, we can instead just wait for the space bar event, and then find out where the cursor is.
Here’s an example where we get and print the mouse state when the user presses their space bar:
#include <iostream>
#include <SDL.h>
#include "Window.h"
void HandleKeyboardEvent(SDL_KeyboardEvent& E) {
if (E.type == SDL_KEYDOWN
&& E.keysym.sym == SDLK_SPACE) {
int x, y;
Uint32 Buttons{SDL_GetMouseState(&x, &y)};
std::cout << "\nMouse Position: "
<< x << ", " << y;
if (Buttons & SDL_BUTTON_LMASK) {
std::cout << " - Left Button is pressed";
}
if (Buttons & SDL_BUTTON_RMASK) {
std::cout << " - Right Button is pressed";
}
}
}
int main(int argc, char** argv) {/*...*/}
Mouse Position: 289, 101
Mouse Position: 525, 150 - Left Button is pressed
Mouse Position: 64, 210 - Left Button is pressed - Right Button is pressed
Another use case for querying the mouse state rather than using the event loop is for game objects that need to continuously track the mouse.
For example, we might be working on a drag-and-drop interaction that needs to continuously track the cursor whilst the player is holding down their mouse button.
In these scenarios, we’d typically use those objects’ Tick()
function to implement the required behaviors:
#include <iostream>
#include <SDL.h>
#include "Window.h"
class Object {
public:
void Tick() {
int x, y;
Uint32 Buttons{SDL_GetMouseState(&x, &y)};
std::cout << "\nMouse Position: "
<< x << ", " << y;
if (Buttons & SDL_BUTTON_LMASK) {
std::cout << " - Left Button is pressed";
}
if (Buttons & SDL_BUTTON_RMASK) {
std::cout << " - Right Button is pressed";
}
}
};
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
Object GameObject;
SDL_Event Event;
while (true) {
while (SDL_PollEvent(&Event)) {
// ...
}
GameObject.Tick();
GameWindow.RenderFrame();
}
SDL_Quit();
return 0;
}
Mouse Position: 308, 108
Mouse Position: 308, 107
Mouse Position: 308, 107
Mouse Position: 308, 106 - Left Button is pressed
Mouse Position: 308, 106 - Left Button is pressed
Mouse Position: 307, 106 - Left Button is pressed - Right Button is pressed
Remember, the more logic we have executing in Tick()
functions, the slower our application will take to produce each frame, reducing responsiveness. We should strive to only perform these checks when they’re required. One possible solution is a simple if
check:
// ...
class Object {
public:
void Tick() {
// Return early if tracking isn't enabled
if (!shouldTrack) return;
// Track Mouse
// ...
}
private:
// Only enable tracking when necessary
bool shouldTrack{false};
};
By default, SDL will stop tracking our mouse when our pointer leaves the window. The x
and y
values updated by SDL_GetMouseState()
will continue to report the position the cursor had before leaving the window.
In most situations, objects that are using this function to continuously track the mouse will want to pause that tracking when the cursor is outside the window.
As we covered in the previous lesson, we can monitor the event loop and examine SDL_WindowEvent
events to understand when the cursor enters and leaves our window:
#include <iostream>
#include <SDL.h>
#include "Window.h"
class Object {
public:
void HandleEvent(SDL_Event& E) {
if (E.type == SDL_WINDOWEVENT) {
HandleWindowEvent(E.window);
}
}
void Tick() {
if (shouldTrack) { HandleMouse(); }
}
private:
bool shouldTrack{true};
void HandleWindowEvent(SDL_WindowEvent& E) {
if (E.event ==
SDL_WINDOWEVENT_ENTER) {
std::cout << "\nTracking Resumed";
shouldTrack = true;
} else if (E.event ==
SDL_WINDOWEVENT_LEAVE) {
std::cout << "\nTracking Paused";
shouldTrack = false;
}
}
void HandleMouse() {/*...*/}
};
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
Object GameObject;
SDL_Event Event;
while (true) {
while (SDL_PollEvent(&Event)) {
GameObject.HandleEvent(Event);
}
GameObject.Tick();
GameWindow.RenderFrame();
}
SDL_Quit();
return 0;
}
Mouse Position: 25, 196
Mouse Position: 25, 196
Tracking Paused
Tracking Resumed
Mouse Position: 14, 238
Mouse Position: 14, 238
SDL_CaptureMouse()
If we need SDL to track our mouse even outside of our window, we can use the SDL_CaptureMouse()
function. We cover this in detail later in the chapter.
Let's create a a more complex example that combines some of the techniques we’ve learned. This practical example will also demonstrate a scenario where querying mouse state is more appropriate than using events.
We'll build a simple projectile that follows the mouse cursor, using continuous position tracking to update its trajectory each frame. First, let's create a Projectile
class that manages the projectile's position and appearance.
Our constructor will receive the SDL_Window*
as an argument, and initialize the projectile’s position at the center of that window. We’ll also create an SDL_Surface
representing the visual appearance of the projectile. We’ll just use a red square in this example:
// Projectile.h
#pragma once
#include <SDL.h>
class Projectile {
public:
Projectile(SDL_Window* window) {
// Get window dimensions
int windowWidth, windowHeight;
SDL_GetWindowSize(
window, &windowWidth, &windowHeight);
// Start in center of window
positionX = windowWidth / 2.0f;
positionY = windowHeight / 2.0f;
// Create a small red square surface
surface = SDL_CreateRGBSurface(
0, 20, 20, 32, 0, 0, 0, 0);
SDL_FillRect(surface, NULL, SDL_MapRGB(
surface->format, 255, 0, 0));
}
~Projectile() { SDL_FreeSurface(surface); }
private:
float positionX{0.0f};
float positionY{0.0f};
SDL_Surface* surface{nullptr};
};
Next, let's add the Tick()
method that will update the projectile's position based on the current mouse location
Our projectile needs to track its position using floating-point numbers to allow for smooth movement. To accomplish this, we’ll store velocity components that will help create more natural motion over time, rather than having the projectile snap directly to the mouse position.
Don’t worry if the maths in this function doesn’t make sense - we’ll cover physics in a dedicated chapter later in the course:
// Projectile.h
#pragma once
#include <SDL.h>
#include <cmath>
class Projectile {
public:
// ...
void Tick() {
// Get current mouse position
int mouseX, mouseY;
SDL_GetMouseState(&mouseX, &mouseY);
// Calculate direction to mouse
float directionX = mouseX - positionX;
float directionY = mouseY - positionY;
// Calculate distance to mouse
float distance = std::sqrt(
directionX * directionX +
directionY * directionY);
// Only move if we're not already at the target
if (distance > 1.0f) {
// Normalize direction
directionX /= distance;
directionY /= distance;
// Update velocity (gradual turn towards mouse)
const float turnSpeed = 0.2f;
velocityX += directionX * turnSpeed;
velocityY += directionY * turnSpeed;
// Limit maximum speed
const float maxSpeed = 5.0f;
float currentSpeed = std::sqrt(
velocityX * velocityX +
velocityY * velocityY);
if (currentSpeed > maxSpeed) {
velocityX = (velocityX / currentSpeed)
* maxSpeed;
velocityY = (velocityY / currentSpeed)
* maxSpeed;
}
// Update position based on velocity
positionX += velocityX;
positionY += velocityY;
}
}
private:
// ...
float velocityX{0.0f};
float velocityY{0.0f};
};
Here's what's happening in the Tick()
method:
SDL_GetMouseState()
This continuous tracking creates smooth movement that would be impossible to achieve using mouse events alone, since we need to update our position every frame regardless of whether the mouse has moved.
Finally, we need a method to render our projectile. Here, we’ll apply the techniques we learned earlier in the course and blit our projectile’s surface onto the window surface:
// Projectile.h
#pragma once
#include <SDL.h>
#include <cmath>
class Projectile {
// ...
void Render(SDL_Surface* windowSurface) {
// Create a rect for positioning our surface
SDL_Rect destRect{
// Center the square
static_cast<int>(positionX - 10),
static_cast<int>(positionY - 10),
20, // Width
20 // Height
};
// Draw the projectile to the window surface
SDL_BlitSurface(
surface, NULL,
windowSurface, &destRect
);
}
};
Finally, we can put it all together in our main loop:
#include <SDL.h>
#include "Projectile.h"
#include "Window.h"
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow;
Projectile Missile{GameWindow.SDLWindow};
SDL_Event Event;
while (true) {
while (SDL_PollEvent(&Event)) {
if (Event.type == SDL_QUIT) {
SDL_Quit();
return 0;
}
}
// Update
Missile.Tick();
// Render
GameWindow.Render();
Missile.Render(GameWindow.Surface);
// Swap
GameWindow.Update();
}
SDL_Quit();
return 0;
}
This example demonstrates why querying mouse state directly is sometimes preferable to using events. By checking the mouse position every frame, we can create smooth, continuous motion that responds immediately to the player's input.
If we tried to use mouse events instead, we would only receive updates when the mouse moves, making it impossible to implement this kind of fluid tracking behavior.
Try modifying these values to see how they affect the projectile's behavior:
turnSpeed
to make the projectile turn more or less quicklymaxSpeed
to change how fast the projectile can moveSDL provides functions for querying mouse state directly, giving us flexibility in how we handle mouse input beyond just responding to events. Key topics covered:
SDL_GetMouseState()
to get current mouse position and button statesLearn how to monitor mouse position and button states in real-time using SDL's state query functions
Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games