Lazy Foo' Productions


Force Feedback

Force Feedback screenshot

Last Updated: Mar 6th, 2022

Now that we know how to how to use joysticks with SDL, we can use either the old haptics API or the new game controller API to make our controller rumble.
//Game controller handler with force feedback
SDL_GameController* gGameController = NULL;

//Joystick handler with haptic
SDL_Joystick* gJoystick = NULL;
SDL_Haptic* gJoyHaptic = NULL;
Ok so this is a bit confusing so bear with me for a bit.

Say you are trying to use an Xbox controller in your application. Back in the SDL 1.x days, you used the joystick API we used in the previous tutorial. When SDL 2.0 rolled around, we got the new SDL_Haptic API. A haptic device is something that gives some sort of physical feedback. In this case, it makes the controller rumble but there is also rumble for devices like mice.

Then in more recent versions of SDL, there is the new game controller API. SDL is a wrapper for native APIs and the API for controllers like the Xbox is different from the old school joystick APIs. For this tutorial we are going to use both APIs to make the controller rumble depending on what is supported.

First we have the SDL_GameController which is an all in one controller/rumble interface. Then we have the SDL joystick and haptic to support older APIs.

Oh and please make sure you're on the latest version of SDL. I don't want to get a bunch of e-mail complaining how this code doesn't work because the game controller API is a relatively recent addition.
    //Initialize SDL
    if( SDL_Init( SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_HAPTIC | SDL_INIT_GAMECONTROLLER ) < 0 )
    {
        printf( "SDL could not initialize! SDL Error: %s\n", SDL_GetError() );
        success = false;
    }
Like with the joysticks subsystem, you need to make sure to initialize the haptic and game controller specific subsystems in order to use them.
        //Check for joysticks
        if( SDL_NumJoysticks() < 1 )
        {
            printf( "Warning: No joysticks connected!\n" );
        }
        else
        {
            //Check if first joystick is game controller interface compatible
            if( !SDL_IsGameController( 0 ) )
            {
                printf( "Warning: Joystick is not game controller interface compatible! SDL Error: %s\n", SDL_GetError() );
            }
            else
            {
                //Open game controller and check if it supports rumble
                gGameController = SDL_GameControllerOpen( 0 );
                if( !SDL_GameControllerHasRumble( gGameController ) )
                {
                    printf( "Warning: Game controller does not have rumble! SDL Error: %s\n", SDL_GetError() );
                }
            }
First we're going to attempt to open the joystick as a game controller by checking if it is compatible with the game controller interface with SDL_IsGameController. If it is, we open it with SDL_GameControllerOpen and check if it has rumble with SDL_GameControllerHasRumble.

And no, I did not forget to link to the documentation for the SDL controller API calls. The API is so new, it is not fully documented on the SDL website as of this writing. Fortunately, if you look in the headers, things are documented. Get used to it because in the industry, documentation is often a luxury and learning to figure out what undocumented code does is an important skill.
            //Load joystick if game controller could not be loaded
            if( gGameController == NULL )
            {
                //Open first joystick
                gJoystick = SDL_JoystickOpen( 0 );
                if( gJoystick == NULL )
                {
                    printf( "Warning: Unable to open joystick! SDL Error: %s\n", SDL_GetError() );
                }
                else
                {
                    //Check if joystick supports haptic
                    if( !SDL_JoystickIsHaptic( gJoystick ) )
                    {
                        printf( "Warning: Controller does not support haptics! SDL Error: %s\n", SDL_GetError() );
                    }
                    else
                    {
                        //Get joystick haptic device
                        gJoyHaptic = SDL_HapticOpenFromJoystick( gJoystick );
                        if( gJoyHaptic == NULL )
                        {
                            printf( "Warning: Unable to get joystick haptics! SDL Error: %s\n", SDL_GetError() );
                        }
                        else
                        {
                            //Initialize rumble
                            if( SDL_HapticRumbleInit( gJoyHaptic ) < 0 )
                            {
                                printf( "Warning: Unable to initialize haptic rumble! SDL Error: %s\n", SDL_GetError() );
                            }
                        }
                    }
                }
            }
If the joystick is not game controller interface compatible, we load it as a plain old joystick.

After we open the joystick, we need to get the haptics device from the joystick using SDL_HapticOpenFromJoystick on an opened joystick. If we manage to get the haptic device from controller we have to initialize the rumble using SDL_HapticRumbleInit.
void close()
{
    //Free loaded images
    gSplashTexture.free();

    //Close game controller or joystick with haptics
    if( gGameController != NULL )
    {
        SDL_GameControllerClose( gGameController );
    }
    if( gJoyHaptic != NULL )
    {
        SDL_HapticClose( gJoyHaptic );
    }
    if( gJoystick != NULL )
    {
        SDL_JoystickClose( gJoystick );
    }
    gGameController = NULL;
    gJoystick = NULL;
    gJoyHaptic = NULL;

    //Destroy window    
    SDL_DestroyRenderer( gRenderer );
    SDL_DestroyWindow( gWindow );
    gWindow = NULL;
    gRenderer = NULL;

    //Quit SDL subsystems
    IMG_Quit();
    SDL_Quit();
}
Once we're done with a game controller or joystick/haptic device, we should call the corresponding functions to close them.
                    //Joystick button press
                    else if( e.type == SDL_JOYBUTTONDOWN )
                    {
                        //Use game controller
                        if( gGameController != NULL )
                        {
                            //Play rumble at 75% strength for 500 milliseconds
                            if( SDL_GameControllerRumble( gGameController, 0xFFFF * 3 / 4, 0xFFFF * 3 / 4, 500 ) != 0 )
                            {
                                printf( "Warning: Unable to play game contoller rumble! %s\n", SDL_GetError() );
                            }
                        }
                        //Use haptics
                        else if( gJoyHaptic != NULL )
                        {
                            //Play rumble at 75% strength for 500 milliseconds
                            if( SDL_HapticRumblePlay( gJoyHaptic, 0.75, 500 ) != 0 )
                            {
                                printf( "Warning: Unable to play haptic rumble! %s\n", SDL_GetError() );
                            }
                        }
                    }
For this application, we'll make the controller shake when a button is pressed.

First we check if the game controller interface was loaded. If it is, we make the controller rumble at 75% strength for 500 milliseconds. Strength goes from 0 to 0xFFFF hex. Again, this is all documented in the headers.

If the haptic was loaded instead, we call SDL_HapticRumblePlay, which takes in the haptic device, strength in percentage, and duration of the rumble. Here we make the controller rumble at 75% strength for half a second whenever a SDL_JoyButtonEvent happens.

Now the SDL 2 game controller and haptics API has many more features not covered here including making custom effects, handling multi rumble devices, and handling haptic mice. You can check them out in the SDL 2 force feedback documentation or dig into the header files for the game controller documentation.