Mouse State

Learn how to monitor mouse position and button states in real-time using SDL's state query functions
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Free, Unlimited Access
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated

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 pressed
  • SDL_BUTTON_RMASK checks if the right button is pressed

We 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:

Passing 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)
};

Example - Retrieving Mouse State on Keyboard Event

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

Getting Mouse State from Tick Functions

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};
};

Mouse State and Focus

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

Preview: 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.

Practical Example: Heat-Seeking Missile

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:

  1. We get the current mouse position using SDL_GetMouseState()
  2. Calculate the direction and distance to the mouse
  3. If we're not already at the target, update our velocity to move towards it
  4. Apply a speed limit to prevent the projectile from moving too fast
  5. Update the position based on our velocity

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.

Experimenting with the Example

Try modifying these values to see how they affect the projectile's behavior:

  • Change turnSpeed to make the projectile turn more or less quickly
  • Adjust maxSpeed to change how fast the projectile can move
  • Modify the surface size or color to create different visual effects
  • Experiment with different movement patterns by adjusting the velocity calculations

Summary

SDL provides functions for querying mouse state directly, giving us flexibility in how we handle mouse input beyond just responding to events. Key topics covered:

  • Using SDL_GetMouseState() to get current mouse position and button states
  • Working with bit masks to check mouse button states
  • Understanding when to use state queries vs event handling
  • Handling mouse tracking when cursor leaves window
  • Practical applications in game development scenarios

Was this lesson useful?

Next Lesson

Relative Mouse Mode

Learn how to restrict cursor movement to a window whilst capturing mouse motion continuously.
Abstract art representing computer programming
Ryan McCombe
Ryan McCombe
Updated
sdl2-promo.jpg
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Free, Unlimited Access
Mouse Input
sdl2-promo.jpg
This lesson is part of the course:

Game Dev with SDL2

Learn C++ and SDL development by creating hands on, practical projects inspired by classic retro games

Free, unlimited access

This course includes:

  • 67 Lessons
  • 100+ Code Samples
  • 91% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

Relative Mouse Mode

Learn how to restrict cursor movement to a window whilst capturing mouse motion continuously.
Abstract art representing computer programming
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved