Joystick Events
Learn how to handle Joystick events with our own callbacks
Source Code
- The code for this tutorial can be found on Github : Learn SFML – Joystick Event Scene
SFML Joystick Events
SfML supports up to 8 Joysticks. Interacting with any of your joysticks will generate five (5) types of events
- sf::Event::JoystickConnected : Emitted when a joystick get connected (for example by plugging its USB cable)
- sf::Event::JoystickDisconnected : Emitted when a joystick get disconnected (for example by unplugging its USB cable)
- sf::Event::JoystickButtonPressed : Emitted when a button is pressed
- sf::Event::JoystickButtonReleased : Emitted when a button is released
- sf::Event::JoystickMoved : Emitted when one of your joysticks Axes is moved. Axes can be : Analog 1 and 2, DPad and Shoulder Buttons (L2, R2)
void handleEvent(const sf::Event& event) { if (event.type == sf::Event::JoystickConnected) { unsigned int joystickId = event.joystickConnect.joystickId; //Ids are numbered from 0 to 7 } else if (event.type == sf::Event::JoystickDisconnected) { unsigned int joystickId = event.joystickConnect.joystickId; //Ids are numbered from 0 to 7 } if (event.type == sf::Event::JoystickButtonPressed) { unsigned int joystickId = event.joystickButton.joystickId; //Ids are numbered from 0 to 7 unsigned int button = event.joystickButton.button; //Button are numbered from 0 to 31 } if (event.type == sf::Event::JoystickButtonReleased) { unsigned int joystickId = event.joystickButton.joystickId; //Ids are numbered from 0 to 7 unsigned int button = event.joystickButton.button; //Button are numbered from 0 to 31 } if (event.type == sf::Event::JoystickMoved) { unsigned int joystickId = event.joystickMove.joystickId; //Ids are numbered from 0 to 7 sf::Joystick::Axis axis = event.joystickMove.axis; //X, Y, Z, R, U, V, POV X and POV Y float position = event.joystickMove.position; //Position is between -100 and 100 //Axes depend on your GamePad and OS System, but here is an example //Axis POV X and POV y = DPad (Directional Pad) ,first axis is horizontal (left-right), second is vertical (up-down) //Axis X and Y = First Analog ,first axis is horizontal (left-right), second is vertical (up-down) //Axis Z and R = Second Analog ,first axis is horizontal (left-right), second is vertical (up-down) //Axis U and V = Shoulder (L2, R2) ,first axis is L2, second is R2 } }
Joystick Events Callbacks
Using the Joysticks Events as provided by SFML is really complicated and cumbersome. Buttons are called by numbers and Axes do not really mean anything to us. If we were using a PS4 controller, for example, it would great to know that we pressed the Square button or move the left Analog Up. To make things easier let’s create our own Joystick Events callbacks but also our own Gamepad System. For the callbacks, we’ll use the following three (3) callbacks
- onJoystickConnection(const unsigned int& joystickId, const bool& connected)
- onJoystickButton(const unsigned int& joystickId, const unsigned int& button, const bool& isPressed)
- onJoystickAxis(const unsigned int& joystickId, const sf::Joystick::Axis& axis, const float& position)
- Which controller are we using : I will use the PS4 Dualshock Controller
- How does our OS System recognizes our Controller Buttons and Axis : I will use Window 10
The PS4 Controller
I will be using the PS4 Dualshock controller (Image below). I am using Window 10 and the controller works simply by connecting it through USB cable or Bluetooth. No installation of any kind is required, just connect and use. The controller has 14 buttons (0 to 13) and 8 Axes. The Dpad (Left, Right, Up, Down) is an axis, not four (4) buttons and the buttons L2 and R2 are both buttons and Axes.
- Buttons : Square, Cross, Circle, Triangle, Shoulder Buttons (L1, L2, R1, R2), Analog Buttons (L3, R3), Share, Options, PS Button (I will call this one Start Button), and the Touchpad button
- Axes : Dpad (PovX, PovY), Left Analog (X, Y), Right Analog (Z, R), Shoulder Buttons L2 and R2 (U, V)

The Gampad Utility File
In the source code, you can see that our Engine has a new file named GamepadUtil : Simple Engine – GampadUtil File.
Inside this file, we do two important things
- Create a list of our Buttons and Axes
- Create a mapping between our controller buttons/ases named and the numbers used by SFML
enum class JSButton { //Action ,Square ,Cross ,Circle ,Triangle //.. continued };
enum class JSAxis { DPadX ,DPadY ,LeftAnalogX ,LeftAnalogY ,RightAnalogX ,RightAnalogY ,ShoulderX ,ShoulderY };
Now that we have the list of Buttons and Axes with the appropriate names, it’s time to create a Mapping between those names and the SFML Buttons numbers and Axes names. We can use a simple std::map for that. The code below shows two std::map renamed JSButtonMapping and JSAxisMapping.
typedef std::map<unsigned int, JSButton> JSButtonMapping; typedef std::map<sf::Joystick::Axis, JSAxis> JSAxisMapping;
Below you can see our Mappings called JSPs4ButtonMapping and JSPs4AxisMapping. This mapping may be different for you. You have to try each button and axis one by one and find out on your own the appropriate Mapping.
const JSButtonMapping JSPs4ButtonMapping {{0, JSButton::Square}, {1, JSButton::Cross}, {2, JSButton::Circle}, {3, JSButton::Triangle}, {4, JSButton::L1}, {5, JSButton::R1}, {6, JSButton::L2}, {7, JSButton::R2}, {8, JSButton::Share}, {9, JSButton::Option}, {10, JSButton::L3}, {11, JSButton::R3}, {12, JSButton::Start}, {13, JSButton::Touchpad}};
const JSAxisMapping JSPs4AxisMapping {{sf::Joystick::X, JSAxis::LeftAnalogX}, {sf::Joystick::Y, JSAxis::LeftAnalogY}, {sf::Joystick::Z, JSAxis::RightAnalogX}, {sf::Joystick::R, JSAxis::RightAnalogY}, {sf::Joystick::PovX, JSAxis::DPadX}, {sf::Joystick::PovY, JSAxis::DPadY}, {sf::Joystick::U, JSAxis::ShoulderX}, {sf::Joystick::V, JSAxis::ShoulderY}};
Finally the GamepadUtil file contains the function JString (for Joystick String) that will help us print the Buttons and Axis names when it comes to debug our code.
std::string JString(const JSButton& button) std::string JString(const JSAxis& axis)
The Gampad Class
In addition to the GamepadUtil file, our Engine now has a new Gamepad Class : Simple Engine – Gamepad Class. This class will represent our Game Controller. The code below shows how to create a new Gamepad. The new Gamepad is given an ID (ONE to EIGHT), a Button Mapping, an Axis Mapping, a connection status. Finally, we adjust the sensitivity of the axes for all controllers.
//Create a Gamepad ng::Gamepad gamepadOne; //New gamepad with no id (id = ng::Gamepad::NONE) gamepadOne.setId(ng::Gamepad::ONE) //SFML supports 8 controllers, so we have eight IDs ONE to EIGHT gamepadOne.setButtonMapping(ng::JSPs4ButtonMapping); //Set our PS4 Buttons mapping gamepadOne.setAxisMapping(ng::JSPs4AxisMapping) //Set our PS4 Axes mapping gamepadOne.setConnected(sf::Joystick::isConnected(mGamepadOne.getId())); //Update the connection status of the Gamepad using SFML. //A little setting //The Axes (Analog for example) can be very sensitive and generate many unwanted events, so we adjust the sensitivity. //This applies to all eight (8) controllers. getRenderWindow().setJoystickThreshold(ng::Gamepad::AXIS_SENSIVITY);
The real utility of this new class is its ability to convert SFML buttons and axes IDs into our JSButton and JSAxis. This is done using the two methods below. The methods use the Button and Axis mappings provided in order to make the conversion.
JSButton getButton(const unsigned int& buttonId); JSAxis getAxis(const sf::Joystick::Axis& axisId);
The Callbacks
We’re finally at the end. Now that we have our Gamepad Class with the correct mapping, we can use our new Callbacks. As you see, it’s now easy to know which Button has been pressed or which Axis has been moved.
void onJoystickConnection(const unsigned int& joystickId, const bool& connected) { if(mGamepadOne.getId() == joystickId) { mGamepadOne.setConnected(connected); std::string info = connected ? "gamepad connection" : "gamepad disconnection"; nero_log(info); //do something //inform the player that the controller has just disconnected } } void onJoystickButton(const unsigned int& joystickId, const unsigned int& button, const bool& isPressed) { if(mGamepadOne.getId() == joystickId) { ng::JSButton jsButton = mGamepadOne.getButton(button); std::string info = ng::JString(jsButton) + " " + (isPressed ? "Pressed" : "Released"); nero_log(info); if(jsButton == ng::JSButton::Square) { //do something //shoot a bullet //through a punch } } } void onJoystickAxis(const unsigned int& joystickId, const sf::Joystick::Axis& axis, const float& position) { if(mGamepadOne.getId() == joystickId) { ng::JSAxis jsAxis = mGamepadOne.getAxis(axis); nero_log(ng::JString(jsAxis) + " " + _se(position)); if(jsAxis == ng::JSAxis::LeftAnalogX) { if(position < 0) { //move the player right } else if(position > 0) { //move player left } } } }
The code in our handleEvent method now looks like this
void handleEvent(const sf::Event& event) { if (event.type == sf::Event::JoystickConnected) { //callback onJoystickConnection(event.joystickConnect.joystickId, true); } else if (event.type == sf::Event::JoystickDisconnected) { //callback onJoystickConnection(event.joystickConnect.joystickId, false); } if (event.type == sf::Event::JoystickButtonPressed) { //callback onJoystickButton(event.joystickButton.joystickId, event.joystickButton.button, true); } if (event.type == sf::Event::JoystickButtonReleased) { //callback onJoystickButton(event.joystickButton.joystickId, event.joystickButton.button, false); } if (event.type == sf::Event::JoystickMoved) { //callback onJoystickAxis(event.joystickMove.joystickId, event.joystickMove.axis, event.joystickMove.position); } }