SDL2 Moving Square - How to Create a SDL2 Square that Moves and Bounces at the Edges of The Screen

In this tutorial we’ll learn how to draw a square using SDL2 that can move UP, DOWN, LEFT, or RIGTH according to directional keys input and also bounces when it hits the limits of the window so that it doesn’t go off-screen.

Additionally, we will add the ability to increase or decrease how fast the square moves on the screen and also the ability to stop it at any time, like pausing a game.

Example application in action
The final application

Step 1: Initialize the main window

This step was covered in details, including the final source code, on this article: How to Create a SDL2 Window. Get this one done first before proceeding.

You can copy the source code at the end of the article linked above, make it, ensure it runs with ./build/debub/play, and then this step is completed. You should see the black window poping up on your screen. You should be able to close it by pressing ESC on your keyboard or by clicking with your mouse on the X icon. Well done! 👏

Step 2: Draw a square at the center of the screen

Now that we have our window, our goal is to draw a green square at the center of the window.

Let’s start by adding the GREEN color our namespace Colors.

// Colors
namespace Colors {
  const SDL_Color BLACK = { 0, 0, 0, SDL_ALPHA_OPAQUE };
  const SDL_Color GREEN = { 0, 255, 0, SDL_ALPHA_OPAQUE };
}

Now, let’s declare the WIDTH and HEIGHT of our square.

A square is a special case of a rectangle where both with and height are equal. In this proggram, we will be using two dimensions with the same value to represent the square. One could use just one dimension for the sake of simplicity, but I think it’s already good to have two dimensions from the start so that you can explore other options later on when developing further your application.

int SQUARE_HEIGHT = 32;
int SQUARE_WIDTH = 32;

Now let’s declare the initial position of our square relative to the window top left corner.

We want to draw the square at the center of the window. Let’s set then half of the window’s width to the pos_x attribute and half of the window’s height to the pos_y attribute.

pos_x and pox_y will indicate the current position of the square.

int pos_x = (WINDOW_WIDTH / 2);
int pos_y = (WINDOW_HEIGHT / 2);

Now what we have the center point of our square, we have to aplly the offsets based on the square’s width and height in order to define its top left point. It is from the top left point that we will start drawing our square.

Image showing the top-left point of the green square
We need to find the top-left point of the square from the center point

Defining the top-left point of our square is not difficult.

The algorithm for it is as follows:

top_left_x = pos_x - (SQUARE_WIDTH / 2)
top_left_y = pos_y - (SQUARE_HEIGHT / 2)

We subtract half of the SQUARE_WIDTH from the pos_x and half of the SQUARE_HEIGHT from the pos_y.

Let’s create now the function that will draw our square on the screen based on a pair of coordinates.

We will use a SDL_Rect which is a struct that represents a rectangle. It stores the rectangle’s x and y coordinates as well as the width and height of the rectangle.

void DrawSquare(int x, int y) {
    SDL_Rect rect;
    rect.x = x - (SQUARE_WIDTH / 2);
    rect.y = y - (SQUARE_HEIGHT / 2);
    rect.w = SQUARE_WIDTH;
    rect.h = SQUARE_HEIGHT;

    SDL_SetRenderDrawColor(g_main_renderer, Colors::GREEN.r, Colors::GREEN.g, Colors::GREEN.b, Colors::GREEN.a );
    SDL_RenderFillRect(g_main_renderer, &rect);
}

In order to draw the square, the function DrawSquare() has also to define the renderer color and effectively draw a rectangle filled with the color we previously set to the renderer.

In order to achieve that we use the functions SDL_SetRendererDrawColor and SDL_RenderFillRect.

Now, let’s call the DrawSquare() function inside our main loop before we check for events, like in the example down below.

... some code here

 while(running) {
    ClearScreen(g_main_renderer);

    DrawSquare(pos_x, pos_y); // <= Add the function here

    // Check and process I/O events
    if (SDL_PollEvent(&event))

... some code here

Finally, make your program, and run it using ./build/debub/play. If you see a green square at the center of the window, congratulations!! This step is completed. If you didn’t see it, please review the instructions above. It’s important that you complete this step successfully before proceeding to the next one.

Step 3: Move the square with the directional keys

Our program is listening to some events already. Our goal on this step is to listen to directional key presses in orer to define the direction in which the square should move and based on the state move the square on the screen.

Step 3.1: Managing the square’s state

Our square state now is idle. It’s not moving. When our square is moving in one direction, we want to know its state. Let’s declare these states using an enum and also declare the initial state idle.

enum SquareState { IDLE, MOVING_UP, MOVING_DOWN, MOVING_LEFT, MOVING_RIGHT };
SquareState state = IDLE;

Now, let’s create a function that will help us manage the square’s state.

void SetState(SquareState new_state) {
  std::cout << "State is chaning from " << state << " to " << new_state << std::endl;
  state = new_state;
}

And finally, let’s set the square state according to the directional key pressed.

We will have to change our routine that listen to SDL2 events inside our main loop and check if any of the directional keys was pressed. If that’s the case we will set the corresponding state as seen in the example below.

// Check and process the I/O events
if (SDL_PollEvent(&event)) {
  switch (event.type) {
    case SDL_KEYDOWN:
    {
      running = event.key.keysym.scancode != SDL_SCANCODE_ESCAPE;

      if (event.key.keysym.scancode == SDL_SCANCODE_UP) {
        SetState(MOVING_UP);
      }

      if (event.key.keysym.scancode == SDL_SCANCODE_DOWN) {
        SetState(MOVING_DOWN);
      }

      if (event.key.keysym.scancode == SDL_SCANCODE_LEFT) {
        SetState(MOVING_LEFT);
      }

      if (event.key.keysym.scancode == SDL_SCANCODE_RIGHT) {
        SetState(MOVING_RIGHT);
      }

      if (event.key.keysym.scancode == SDL_SCANCODE_S) {
        SetState(IDLE);
      }

      break;
    }
    case SDL_QUIT:
    {
      running = false;
      break;
    }
    default:
      break;
  }

Step 3.2: Moving the square based on its state

When moving, our square will move a certain by a certain amount of pixels. Let’s call this each movement a Step. In future tutorial we will explore the concept of Velocity. In this tutotial, we will use a simpler approach.

Our square will move two pixels every time. Let’s declare this as the DEFAULT_STEP. Also, let’s declare a variable step that will contain the current value of the square’s step. It will start having the default value.

int DEFAULT_STEP = 2;
int step = DEFAULT_STEP;

Now, knowing the current square’s state and how much the square will move on the screen, we can start moving it every frame.

In order to do that, inside our main loop, before we call DrawSquare() to draw our square on the screen, we need to check its state and define its center position. We will do that by using a switch statement to evalute the state of the square.

  ..some code here

  while(running) {
    ClearScreen(g_main_renderer);

    switch (state) {
      case MOVING_UP: {
       pos_y -= step;
       break;
      }
      case MOVING_DOWN: {
        pos_y += step;
        break;
      }
      case MOVING_LEFT: {
        pos_x -= step;
        break;
      }
      case MOVING_RIGHT: {
        pos_x += step;
        break;
      }
      default:
        break;
    }

    DrawSquare(pos_x, pos_y);

    ...some code here

The code above, depending on the state, is defining a new position for either the square’s pos_x or pos_y coordinates.

At this point, make your program, and run it using ./build/debub/play. You should be able to move the square on the screen using the directional keys. Notice that if you don’t change the direction, the square will move off-screen. That’s our next challenge. Prevent that the square goes off-screen.

Step 4: Detect when the square goes off-screen

In order to prevent the square going off-screen, we need to detect this situation before setting the new position either for the pos_y or pox_x coordinates.

Let’s add this logic to the part of the code where we evalute the square’s state and define its new position.

We need to compute the new position first. Then check the new position against the window constraints in order to know if that position will lead the square to be off-screen or not. If the new position will lead the square to be off-screen, we adjust it to the maximum we can move in that direction that will make the square stop at the edge of the window.

switch (state) {
  case MOVING_UP: {
    int new_pos_y = pos_y - step;
    if (new_pos_y > (SQUARE_HEIGHT / 2)) {
      pos_y -= step;
    } else {
      pos_y = (SQUARE_HEIGHT / 2);
    }

    break;
  }
  case MOVING_DOWN: {
    int new_pos_y = pos_y + step;
    if (new_pos_y + (SQUARE_HEIGHT / 2) < SCREEN_HEIGHT) {
      pos_y += step;
    } else {
      pos_y = new_pos_y - (new_pos_y + (SQUARE_HEIGHT / 2) - SCREEN_HEIGHT);
    }

    break;
  }
  case MOVING_LEFT: {
    int new_top_left_pos_x = pos_x - step - (SQUARE_WIDTH / 2);
    if (new_top_left_pos_x > step) {
      pos_x -= step;
    } else {
      pos_x = (SQUARE_WIDTH / 2);
    }

    break;
  }
  case MOVING_RIGHT: {
    int new_top_left_pos_x = pos_x + step;
    if (new_top_left_pos_x + (SQUARE_WIDTH / 2) <= SCREEN_WIDTH) {
      pos_x += step;
    }

    break;
  }
  default:
    break;
}

At this point, make your program, and run it using ./build/debub/play. You should be able to move the square on the screen using the directional keys and when moving along the same direction, the square should stop at the edges of the window.

If you made it so far, congratulations!! Well done! We are almost there, now just a few more features and our program will be done.

Step 5: Bounce the square in the oppostite direction when it hits the window’s borders

This step is an easy one. We already have the routine to stop the squre when it hits the edge of the window. We aldo have the routine to set the square’s state, which defines in which direction the square will move.

All we need to do in order for the square to bounce, is to set the opposite direction when we detect the hit.

Attention: The MOVING_RIGHT case does not have an else statement. We have to add one in order to change the direction.

switch (state) {
  case MOVING_UP: {
    int new_pos_y = pos_y - step;
    if (new_pos_y > (SQUARE_HEIGHT / 2)) {
      pos_y -= step;
    } else {
      pos_y = (SQUARE_HEIGHT / 2);
      SetState(MOVING_DOWN);
    }

    break;
  }
  case MOVING_DOWN: {
    int new_pos_y = pos_y + step;
    if (new_pos_y + (SQUARE_HEIGHT / 2) < SCREEN_HEIGHT) {
      pos_y += step;
    } else {
      pos_y = new_pos_y - (new_pos_y + (SQUARE_HEIGHT / 2) - SCREEN_HEIGHT);
      SetState(MOVING_UP);
    }

    break;
  }
  case MOVING_LEFT: {
    int new_top_left_pos_x = pos_x - step - (SQUARE_WIDTH / 2);
    if (new_top_left_pos_x > step) {
      pos_x -= step;
    } else {
      pos_x = (SQUARE_WIDTH / 2);
      SetState(MOVING_RIGHT);
    }

    break;
  }
  case MOVING_RIGHT: {
    int new_top_left_pos_x = pos_x + step;
    if (new_top_left_pos_x + (SQUARE_WIDTH / 2) <= SCREEN_WIDTH) {
      pos_x += step;
    } else {
      SetState(MOVING_LEFT);
    }

    break;
  }
  default:
    break;
}

That’s it. If you make your program, and run it using ./build/debub/play. You should be able to see the green square bouncing on the opposite direction when it hits the edges of the window.

Step 6: Stop que square’s movement

Stopping the movement is also an easy step. Let’s use the key S to stop our square. When the user press the S key on the keyboard we want to set the square’s state to idle.

// Check and process I/O events
if (SDL_PollEvent(&event)) {
  switch (event.type) {
    case SDL_KEYDOWN:
    {
      running = event.key.keysym.scancode != SDL_SCANCODE_ESCAPE;

      ..some checks happening here

      if (event.key.keysym.scancode == SDL_SCANCODE_S) {
        SetState(IDLE;
      }

      break;
    },

    ..more checks happening here

That’s it. If you make your program, and run it using ./build/debub/play. You should be able to stop the square movement by pression S on the keyboard.

Step 7: Increase or decrease how fast the square moves

To complete our program, we will add the ability to increase and decrease the square’s movement step. In order to do that let’s create two helper functions, one to increase and another to decrease the square’s movement step:

void IncreaseStep() {
  std::cout << "Increasing step from " << step << " to " << step * DEFAULT_STEP << std::endl;
  step = step * DEFAULT_STEP;
}

void DecreaseStep() {
  if (step > DEFAULT_STEP) {
    std::cout << "Decreasing step from " << step << " to " << step / DEFAULT_STEP << std::endl;
    step = step / DEFAULT_STEP;
  } else {
    std::cout << "Decreasing step from " << step << " to the default step " << DEFAULT_STEP << std::endl;
    step = DEFAULT_STEP;
  }
}

As we can increate the square’s step indefinitely, we just double the step value every time we increase it. Decreasing the square’s step requires a bit more care. We cannot decrease the step below the DEFAULT_STEP value.

Now, let’s define that when the user press the key i we increase the step, and when the user press the key d we decrease the step.

// Check and process I/O events
if (SDL_PollEvent(&event)) {
  switch (event.type) {
    case SDL_KEYDOWN:
    {
      running = event.key.keysym.scancode != SDL_SCANCODE_ESCAPE;

      ..some checks happening here

      if (event.key.keysym.scancode == SDL_SCANCODE_I) {
        IncreaseStep();
      }

      if (event.key.keysym.scancode == SDL_SCANCODE_D) {
        DecreaseStep();
      }

      break;
    },

    ..more checks happening here

That’s it. If you make your program, and run it using ./build/debub/play. You should be able to increase and decrease the square’s step, creating the impression or higher or lower “speed”.

Final Remarks

If you made it to this point, I want to congratulate you. It taks patience and perseverance to learn these basic steps a logic that will build the foundation for more complex programs and eventually some games :).

Take some time to explore the concepts learnt during this tutorial. Change the existing paramaeters, include new events, etc. Try to explore and use what you already know to improve this program.

Happy coding!