A lot of people begin game programming via SDL2, and a lot of them use the venerable LazyFoo tutorials to learn that.
Lazyfoo is good still, but SDL2 has added several features since Lazyfoo wrote their tutorials. I see a lot of people writing old SDL code that could be made more succinct with some modern functions, or can otherwise be made faster:
Initialization
SDL_Init(SDL_INIT_EVERYTHING); SDL_Window* window = SDL_CreateWindow( "SDL Tutorial", SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN ); SDL_Renderer* renderer = SDL_CreateRenderer( window, -1, SDL_RENDERER_ACCELERATED );
This is slightly verbose. Since most people these days are using hardware acceleration (and not surfaces), decoupling renderer and window initialization is not useful for most people. Instead, you can simply do this:
SDL_Init(SDL_INIT_EVERYTHING); SDL_Window* window; SDL_Renderer* renderer; SDL_CreateWindowAndRenderer( SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_SHOWN, &window, &renderer );
of course, make sure to check the return values of SDL_Init and SDL_CreateWindowAndRenderer to make sure initialization succeeded
Drawing
Most people draw things using SDL_Rect
for positioning, such as:
SDL_Rect renderQuad = { x, y, mWidth, mHeight }; SDL_RenderCopy(renderer, mTexture, clip, &renderQuad);
This works fine still, but if you try to implement your own camera system with scaling, then this doesn't really allow for smooth subpixel movement. Additionally, on the backend, SDL usually converts everything to a float
since it's interfacing with OpenGL, DirectX, Vulkan, and Metal. To avoid this cast for every draw call, you can instead use SDL_FRect and its associated render calls:
// x, y, width, and height are now floats in the entity class SDL_FRect renderQuad = { x, y, mWidth, mHeight }; SDL_RenderCopyF(renderer, mTexture, clip, &renderQuad);
This will give you a slight (probably imperceptible) performance increase as floats do not need to be cast many times per frame now. Additionally, it means that you can use floats in your movement and position vectors and not have to cast to integers just to render an SDL_Rect
. In fact, you can now use an SDL_FRect
(or SDL_FPoint
) directly in your physics calculations, then use the same structure directly for rendering.
Collision
A lot of people implement their own collision detection in simple tile-based games. SDL has a few collision detection functions:
These only operate on SDL_Rect (integer, not floating point) unfortunately, but they use the separating axis theorem so that you don't have to implement it yourself
Drawing Lots of Colored Squares
Sometimes, for very simple levels, people will draw everything as a colored square, and they might do something like this:
SDL_SetRenderDrawColor(renderer, 0xff, 0, 0, 0xff); // red for(int i = 0; i < level_width; i++){ for(int j = 0; j < level_height; j++){ SDL_RenderFillRect(renderer, &(tile[i][j])); } }
This seems simple, but it's not. On the backend, SDL has its own software rendering command queue. Every draw call for a quad (a rectangle) uses a custom SDL_alloc function to dynamically allocate the memory required for that quad and command, puts it on the queue, and then flushes and executes that queue when the program calls SDL_RenderPresent (which itself calls the actual command: SDL_RenderFlush). In order to pack many similar rectangle drawing command together and avoid a lot of allocations, you can use SDL_RenderFillRects (note the plural Rects). In addition, you can use SDL_RenderFillRectsF:
// this only works if the 2D array of tiles is one contiguous // chunk of memory. Mind your pointers! SDL_RenderFillRectsF( renderer, tile, level_width * level_height );
Rendering Something Other Than Rectangles
After many many years, SDL has finally added the ability to render arbitrary vertices (triangles)