Animation in SDL
by Bob Pendleton05/15/2003
What is SDL?
The Simple DirectMedia Layer (SDL), a powerful, commercial grade and cross platform game development library, has been used to write or port more than 40 commercial games. SDL runs on pretty much any PC or PDA which has a graphic screen and something at least roughly like an operating system.
Full documentation for SDL can be downloaded or read online. You can also look at the SDL documentation project for user annotated documentation. You can find the latest version of SDL at http://www.libsdl.org/download-1.2.php. SDL has been extended by several auxiliary libraries of which SDL_image, SDL_net, SDL_mixer, and SDL_ttf are the most widely used. Although all the code in this article is C/C++, there are SDL bindings for languages from Ada to Ruby .
Animation In SDL
Every SDL program must first initialize SDL, then select and initialize a video mode. After that we get to the fun stuff: making things move on the screen.
Initializing SDL
You initialize SDL by calling SDL_Init()
and shut down SDL by calling SDL_Quit().
The only tricky part is that SDL_Init() lets you decide
which of the five SDL subsystems you want to start. You can
initialize any or all of SDL timers, audio, video, CD-ROM, and
joystick support. SDL_Init() also lets you turn on
threaded event processing on operating systems which support it. There
is also an option to turn off the SDL parachute, which is on by
default. (The SDL parachute tries to return the machine to a usable
state when you program crashes, but it has to be disabled to run a
debugger on an SDL program.) Typical code to initialize all subsystems
and to terminate SDL might look like this:
if (-1 == SDL_Init(SDL_INIT_EVERYTHING))
{
printf("Can't initialize SDL");
exit(1);
}
.
.
.
SDL_Quit();
Picking a Video Mode
The video mode controls how graphics are displayed and how your
program interacts with the screen. Set the video mode with
SDL_SetVideoMode(), which returns an
SDL_Surface. All graphics in SDL, including the screen,
are stored in a surface. Choose a video mode based on the following
information:
Full screen or windowed? Does your program try to take over the whole screen, which is normal for games, or does it run in a window? SDL hides the details of creating a window or taking over the screen for full screen games.
Screen size. What is the pixel size of the window or the screen resolution when running full screen? If you ask for a full screen size that is not supported on the current machine, SDL will switch to full screen, hiding all other windows. Your program will display in a smaller rectangle centered on the screen.
Window properties. If you choose to run in a window, SDL lets you decide whether or not your window has a border and whether or not it can be resized.
Bits per Pixel. You can choose how many bits per pixel to work with or you can use the current display depth. If you ask for a display depth that is not available, SDL will give you a software surface of the requested depth, converting your data to the actual display depth when it updates the visible screen.
Surface type. You can ask for a software surface (the default), a hardware surface, or an OpenGL surface. If you ask for a hardware surface, you can also ask for it to be double buffered.
|
Related Reading
Games, Diversions & Perl Culture |
The three kinds of display surfaces are stored and accessed differently, and each has its own pros and cons:
Software surfaces live in your computer's memory, not on the video card. All drawing done on those surfaces is done in software. They can be slow but you can count on being able to get a software surface.
Hardware surfaces live in your video card. Drawing on a hardware surfaces is done using a mixture of hardware and software, depending on the operating system and video card drivers. You may or may not be able to get a hardware surface; it depends on both the hardware and the operating system. Hardware surfaces are not necessarily faster than software surfaces.
OpenGL surfaces are completely controlled by the OpenGL hardware and software in your computer. If it is available, OpenGL is usually the fastest and most flexible way to do graphics under SDL.
The following code asks for a 640x480 full screen software surface with a pixel format that is that same as the current display setting.:
int options = (
SDL_ANYFORMAT |
SDL_FULLSCREEN |
SDL_SWSURFACE
);
SDL_Surface *screen = NULL;
screen = SDL_SetVideoMode(640, 480, 0, options);
if (NULL == screen)
{
printf("Can't set video mode");
exit(1);
}
The option SDL_ANYFORMAT tells SDL to leave the number
of bits per pixel the same as the current screen
settings. SDL_FULLSCREEN tells SDL to take over the whole
screen. SDL_SWSURFACE tells SDL that you want a software
surface. There is a little trickery going here. The value of
SDL_SWSURFACE is zero, so it is only there for
documentation purposes. If you leave it out, you still get a software
surface. The zero (0) in the call to
SDL_SetVideoMode() also tells SDL to not change the
number of bits per pixel. SDL_SetVideoMode() returns
NULL if it can't set the video mode, so we check for the
screen being NULL and give up if it is.
The Animation Loop
Animation is drawing an image on the screen, drawing it again with a small change, and then doing it again, until you are done. Each step of an animation is called a "frame". The time between frames is called a "frame time". The number of frames you can draw each second is the "frame rate". Animation is done in a two-part loop. The first part checks for input and lets you interact with the animation. The second part draws the next frame.
Any time you press a key or move the mouse, SDL stores an event in a queue and waits for your program to process it. The interactive part of the animation loop reads and processes these events. The following code outlines the most basic interactive animation loop:
int done = 0;
SDL_Event event;
while (!done)
{
while (!done && SDL_PollEvent(&event))
{
switch (event.type)
{
case SDL_KEYDOWN:
done = 1;
break;
}
}
.
.
draw something
.
.
SDL_Flip(screen);
}
As long as there are events waiting to be processed,
SDL_PollEvent() returns 1 (true) and
copies the event in the queue to the event structure pointed to by its
lone parameter. When the input queue is empty, it returns
0 (false) and the program falls out of the event
loop. The sample loop processes events until either the queue is empty
or a key is pressed. When a key press is processed, done
is set to 1 (true) and both loops eventually exit. When
all other events have been processed, the program draws the next frame
of the animation and the process continues.
Nothing you draw on a software surface is visible until it has been
copied from memory to the display buffer on the video card. SDL
provides two ways to do that:
SDL_Flip() and
SDL_UpdateRect(). SDL_Flip() copies the
entire software surface to the screen. If your screen is set to
640x480 at 4 bytes per pixel, SDL_Flip() will copy 1.2
megabytes per frame and your frame rate will be limited by how fast
your computer can copy images to the screen.
SDL_UpdateRect() is designed to let you use a "dirty
pixels" scheme. It lets you specify a list of rectangular areas that
have been changed and only copies those areas to the screen. This
technique is ideal for a game with a complex background but only a
small number of moving or changing items. Tracking dirty pixels can
give you a dramatic improvement in performance.
This is the simplest kind of animation loop you can write, but it
uses all available CPU time and gets in the way of anything else that
needs to run. Eventually the OS will pause your program to let
waiting tasks run and you will get jerky animation. There is also a
good chance that a program written this way will generate animation
frames at a rate faster than the refresh rate of the display. I've
never seen the point in generating more frames per second than the
hardware can display cleanly. Both of these problems are reasons to
have your game pause for a few milliseconds between frames. Pausing
will let other tasks run without interfering with your program. You
can limit the frame rate to something close to what the hardware can
display. Use SDL_Delay()
to pause briefly the animation loop between frames.
Many people make the mistake of thinking that their animation programs will generate frames at a constant rate and animate by moving objects by a fixed distance per frame. You quickly learn that, no matter what you do, sometimes the time between frames is not constant. In fact, it is surprisingly variable. Even if you tie the frame rate to the display refresh rate, you find that the refresh rate is different for each display your program runs on.
Basing animation on a constant frame rate is a fairy tale that produces jerky animation. When it comes to frame rates I like to hope for a constant frame rate, assume a random frame rate, and smooth it out by basing motion on the time that has elapsed since the last frame. Objects that move at a fixed distance per millisecond will appear on the screen where you expect them, appearing to move at a constant rate, no matter what the frame rate really is.
Sample Program
I've included a sample program (softlines.cpp) that uses all the features and deals with all of the problems described in this article. The program is lumped into one source file for convenience but is organized into four sections.
The first section is a simple Bresenham line drawing routine
tailored for use with SDL. SDL supports pixels that are 8, 16, 24, or
32 bits long, and SDL graphics code needs to support all four
depths. The internal structure of a pixel doesn't matter to graphics
routines like a line drawer--I count on SDL_MapRGB()
to set that up. Because SDL supports four pixel depths, I have a line
drawing routine for each pixel depth and the main routine
line() picks which one to use based on the pixel depth of
the drawing surface.
The fact that my sample program includes its own line drawing routine says something important about SDL. SDL is simple: there is nothing in it that isn't commonly used in game programming. Most games written for software buffers do their graphics by copying images. SDL has highly optimized code for copying images in software buffers. To remain simple, SDL has avoided the trap of trying to define a complete two dimensional graphics API. Of course, the SDL community has provided several libraries of graphics functions for use with SDL.
The second section is a class named sweepLine. This
class implements a moving line. When you create a
sweepLine you provide the starting positions of the two
end points of the line and their velocities. The initializer clips
the endpoints to make sure they are on the surface. When a
sweepLine is told to update(), it computes
new positions of the line end points based on the difference between
the current time and the last time it was called. If an end point
moves off the surface, it is forced back to the edge of the surface
and its velocity in the direction of that edge is reversed. The end
points appear to bounce off the edge of the screen. The bouncing is
not physically correct. It's good enough for a demo.
|
Related Article: SDL: The DirectX Alternative -- SDL has become known as an essential toolkit for Linux game development. It's similar to MS's DirectX API -- the big difference is that it's open source and supports multiple operating systems. |
sweepLine is an example of a time-based animation. By
updating positions based on elapsed time, the animation looks pretty
good even when the frame rate is far from consistent. Looking at the
code, you'll see that sweepLine will work even if time
runs backwards.
The third section of the code is a class named
gameTime. This class keeps track of the flow of time in
the game. It acts just like a clock that can be stopped and
started. If the game is paused, the clock does not advance. When the
clock restarts, it continues on without making sudden jumps. The
class has methods that return the current game time, start and stop
the clock, and test whether the clock is running or not. When used
with a time-based animation, stopping the clock stops the animation.
You can do some interesting things with the game clock. You can add
code to make it run slow or fast. A slow clock simulates slow motion
and a fast clock makes your animation look like it has gone into fast
forward. In the sample program, I use the game clock to implement a
pause key. Pressing F1 pauses the animation. Pressing it
again continues the animation where it left off.
The main() program puts everything together. It first
initializes SDL. Then it creates some pixel values for the color of
each sweepLine and for black to paint over the background
of the demo. Once the colors are defined, the program creates three
sweepLine objects that will be animated on the screen.
Before going into the animation loop, it starts the
gameTime object. The animation loop first checks for
events. It stops the program if it sees an escape key or
a SDL_QUIT event, which means you clicked on the close
button on the window. It also responds to the F1 key to
pause or to continue the program. Finally, the animation loop gets the
current game time and tells sweepLine objects to update
themselves based on the time.
For more details, consult the source code.
Conclusion
In this article we've covered SDL basics and provided an example of using software buffers to do animation. In the next article we'll move closer to the hardware with a look at SDL hardware buffers, and we'll go into the problem of matching hardware video modes to the needs of your application.
Bob Pendleton has been fascinated by computer games ever since his first paid programming job -- porting games from an HP 2100 minicomputer to a UNIVAC 1108 mainframe.
Return to the Linux DevCenter.