In this lesson, we'll explore SDL's window management features, learning how to implement both desktop and exclusive fullscreen modes. We’ll cover:
SDL_WINDOW_FULLSCREEN_DESKTOP
SDL_WINDOW_FULLSCREEN
SDL_SetWindowFullscreen()
lets you change modes at runtimeWhen our game is running in a window, we typically don’t need to care that much about things like the resolution and refresh rate of that screen. Our window is just one of potentially many that are running on that display.
However, when we’re running in exclusive full screen mode, we need to be more aware of the characteristics of that screen, and potentially even change them.
These characteristics are grouped together into a display mode, which includes the resolution (width and height in pixels), refresh rate (how often the screen updates per second, measured in Hz), and pixel format (how color data is stored).
Game developers need precise control over how their games appear on screen. In this lesson, we'll cover SDL's display management functions, learning how to adjust brightness, work with gamma curves, and implement color correction.
Players are often asked to configure the brightness of our game the first time they launch it. This is because the displays we develop the game on are likely to have different settings to what a player is using, and the environment they’re playing in has different lighting to the environment where we made the game.
By letting players calibrate the brightness, it lets them get the experience closer to what we intended:
Modern displays come in various resolutions and pixel densities. In this lesson, we'll learn how to create SDL applications that look great regardless of the display being used. We'll explore DPI awareness, scaling techniques, and cross-platform considerations.
The relationship between pixel dimensions and physical dimensions is usually represented in terms of pixels per inch, or equivalently, dots per inch (DPI)
A higher pixel density (that is, higher DPI) is desirable as it allows graphics to be more detailed. The following image shows a close-up photo of the battery indicator on a phone using a 163DPI screen, compared to the same design rendered on a 326DPIÂ display:
In this lesson, we’ll explore how objects are copied in more depth. There are two scenarios where our objects get copied. The first is when a new object is created by passing an existing object of the same type to the constructor:
struct Weapon{/*...*/};
int main() {
Weapon SwordA;
// Create SwordB by copying SwordA
Weapon SwordB{SwordA};
}
This copying process also happens when we pass an argument by value to a function. The function parameter is created by copying the object provided as the corresponding argument:
struct Weapon{/*...*/};
void SomeFunction(Weapon W) {/*...*/}
int main() {
Weapon Sword;
// Create the W parameter by copying Sword
SomeFunction(Sword);
}
The second scenario is when an existing object is provided as the right operand to the =
operator. In the following example, we’re expecting an existing PlayerTwo
to be updated by copying values from PlayerOne
:
struct Weapon{/*...*/};
int main() {
Weapon SwordA;
Weapon SwordB;
// Update SwordA by copying values from SwordB
SwordA = SwordB;
}
As we’ve likely noticed, C++ supports these behaviors by default, even when the objects we’re copying use our custom types. In this lesson, we’ll explore what that default behavior does, and learn how to override it when our classes and structs have more complex requirements.
Modern games and applications often require precise control over display management. In this lesson, you'll learn to retrieve monitor counts, display names, and manage window placement across different displays.
In the context of games, the concepts we cover in this lesson are primarily useful for letting players choose which monitor they want our game to run on:
As we likely noticed, platforms typically add additional decorations to our windows, such as a title bar. These decorations include things like title bars and borders. But what if you need a clean, borderless look?
In this lesson, we’ll cover everything you need to know about customizing SDL2 window styles, from removing decorations to dynamically adding them back.
The following screenshot is from Windows 11. It shows a decorated window on the left and an equivalent window without those decorations on the right:
In this lesson, we'll explore how to control window titles in SDL2 dynamically. We’ll cover how to set, update, and retrieve titles to provide a polished user experience.
As we’ve seen previously, the first argument to SDL_CreateWindow
is a string, representing what we want the title to be. Below, we create a window whose title is "Sample Window":
SDL_CreateWindow(
"Sample Window",
100, 100, 700, 300, 0
);
On some platforms, we can set the opacity of our window. In this lesson, we explore how to adjust the transparency of SDL2 windows using SDL_SetWindowOpacity()
and SDL_GetWindowOpacity()
. We’ll cover how to handle errors, retrieve the current opacity, and understand the practical uses of window transparency.
Opacity is represented by a floating-point number ranging from 0.0
to 1.0
. The default value is 1.0
, which represents a completely opaque window. A window with an opacity of 0.0
is fully transparent.
Intermediate values between 0.0
and 1.0
are used to make our windows semi-transparent. The following window has an opacity of 0.5
:
In this lesson, we’ll explore how to manage multiple windows using SDL2. We’ll also introduce one of the primary use cases for these techniques, which is creating menus and tooltips.
As we might expect, our program can manage multiple windows by performing multiple invocations to SDL_CreateWindow()
:
#include <SDL.h>
#include "Window.h"
int main(int argc, char** argv) {
SDL_Init(SDL_INIT_VIDEO);
Window GameWindow1;
Window GameWindow2;
SDL_Event E;
while (true) {
while (SDL_PollEvent(&E)) {
if (E.type == SDL_QUIT) {
SDL_Quit();
return 0;
}
}
GameWindow1.Update();
GameWindow2.Update();
GameWindow1.Render();
GameWindow2.Render();
}
}
Our windows do not need to be created at the same time. We can open additional windows in response to user events. Below, our program opens a second window when the user presses their space bar, and closes it when they press escape: