Last week we discussed how to setup a basic game loop. We were missing a few critical components, one being the window! This week we are going to learn how to create a basic window and renderer using SDL. SDL 2.0 works hard to get us the optimal display for the system we are working on. Most often that means OpenGL or DirectX, although, SDL provides a wrapper to a build-in renderer to make drawing easy – so we won’t be considered with what type of renderer we get. If you wish to work on a 3D game (or 2D with shaders/effects) it is possible to do with SDL but you’ll have to write a custom renderer which communicates with OpenGL/DirectX.
First thing we’ll want to do is take last week’s anonymous “Game” structure and add a module for our screen. We’ll only use one screen here so the module will adhere to the same anonymous struct style which we used for “Game”.
struct {
// define "attributes"
SDL_bool running;
struct {
unsigned int w;
unsigned int h;
const char* name;
SDL_Window* window;
SDL_Renderer* renderer;
} screen;
// define "methods"
void (*init)(void);
void (*quit)(void);
} Game = {
SDL_FALSE,
{
SCREEN_SCALE*SCREEN_w,
SCREEN_SCALE*SCREEN_H,
SCREEN_NAME,
NULL,
NULL
},
game_init,
game_quit
};
So now we have access to Game.screen who’s properties are w the width of the window, h the height of the window, name the string which will appear and the top of our window, window a pointer to our SDL window, and renderer a pointer to our SDL renderer. The width, height, scale, and name are specified as follows in the game header file.
#define SCREEN_w 640
#define SCREEN_H 480
#define SCREEN_SCALE 1
#define SCREEN_NAME "Prototype"
Defining our variables in this way allows for the w, h, scale, and name to be accessed from other points within our code base. Now we need to rewrite void game_init(void); and void game_quit(void); to get our screen to display and to clean up any memory left when we’re finished.
void game_init(void) {
if(SDL_Init(SDL_INIT_EVERYTHING)!=0) {
printf("SDL error -> %sn", SDL_GetError());
exit(1);
}
unsigned int w = Game.screen.w;
unsigned int h = Game.screen.h;
const char* name = Game.screen.name;
Game.screen.window = SDL_CreateWindow(
name,
SDL_WINDOWPOS_CENTERED,
SDL_WINDOWPOS_CENTERED,
w, h, 0
);
Game.screen.renderer = SDL_CreateRenderer(
Game.screen.window, -1,
SDL_RENDERER_ACCELERATED|SDL_RENDERER_PRESENTVSYNC
);
Game.running = SDL_TRUE;
}
Here we make the necessary library calls to get a window and renderer. The SDL_WINDOWPOS_CENTERED flag will center our window when it pops up for the player. It is possible to setup a fullscreen window however if you are using pixel art this isn’t recommended for reasons that will become obvious once we write code for setting up our sprites. To destroy our renderer and window we’ll code void game_quit(void); the following way.
void game_quit(void) {
SDL_DestroyRenderer(Game.screen.renderer);
SDL_DestroyWindow(Game.screen.window);
Game.screen.window = NULL;
Game.screen.renderer = NULL;
SDL_Quit();
}
At this point if you are encountering errors it is recommended to wrap the library function returns with if-statements to print any errors to stdout as we have done in the past. Now we need to modify our main game loop to provide continuous rendering as well as exiting functionality. It’s quite interesting to note that a window not polling for events (i.e. keypresses, mouse movements, etc.) will show as being non-responsive by the operating system. If on the other hand you are polling for events but aren’t listening for when the window is closed (i.e. red X at top right on windows) then the window will remain open and the only way to close it will require Ctrl+Alt+Delete. Let’s write a new game loop which polls for events and listens for when the window is closed.
int main(int argc, char* argv[]) {
Game.init();
SDL_Event event;
while(Game.running) {
while(SDL_PollEvent(&event)) {
switch(event.type) {
case SDL_QUIT: {
Game.running = SDL_FALSE;
} break;
}
}
SDL_RenderClear(Game.screen.renderer);
SDL_RenderPresent(Game.screen.renderer);
}
Game.quit();
return 0;
}
Here we are simply polling for events and clearing/presenting an empty renderer. That’s all for this week! Next week we’ll look at how to read in a single image, split it into an array of sprites and display those sprites based on a gird/map. If you are getting the hang of C development I highly recommend picking up a GCW handheld – think of it as a hobby expense! Take care.