Can't Figure Out Mechanism to Decelerate Player

I want my player to stop slowly moving after user stops pressing key. But I have failed to achieve this and I need help.
Here’s my code:
TextureManager.cpp: -----------------------------------------------------------------------------------------------------------

#include <SDL.h>
#include <SDL_image.h>
#include "Game.h"
#include "TextureManager.h"

void TextureManager::drawTexture(const char* path, int x, int y) {
	SDL_Texture* texture = nullptr;
	texture = IMG_LoadTexture(gRenderer, path);
	sprite = texture;

	SDL_Rect src, dest;

	src.x = 0;
	src.y = 0;
	src.w = 24;
	src.h = 24;

	dest.x = x;
	dest.x += x_shift; // horizontal motion
	dest.y = y;
	dest.w = src.w * 4;
	dest.h = src.h * 4;

	SDL_RenderCopy(gRenderer, sprite, &src, &dest);
}

void TextureManager::drawRect(int x, int y, int w, int h) {
	SDL_Rect rect;

	rect.x = x;
	rect.y = y;
	rect.w = w;
	rect.h = h;

	SDL_SetRenderDrawColor(gRenderer, 205, 150, 0, 1);
	SDL_RenderDrawRect(gRenderer, &rect);
	SDL_RenderFillRect(gRenderer, &rect);
}

void TextureManager::Update() {
	x_shift += speed * accel;
}

Game.cpp: -------------------------------------------------------------------------------------------------------------------------

#include "Game.h"
#include "TextureManager.h"
#include <iostream>
using namespace std;

SDL_Renderer* gRenderer = nullptr; // ~ this is a definition

TextureManager* manager = nullptr;

const int speed = 4;

int accel = 0;

int x_shift = 0;

void Game::init(const char* title, int xpos, int ypos, int width, int height, int flags) {

	if (SDL_Init(SDL_INIT_EVERYTHING) == 0) {

		mWindow = SDL_CreateWindow(title, xpos, ypos, width, height, flags);

		if (mWindow != NULL) {

			gRenderer = SDL_CreateRenderer(mWindow, -1, 0);

			isRunning = true;
		}
	}
	manager = new TextureManager();
}

void Game::handleEvents() {

	SDL_Event event; // SDL_Event class can only ever have one object

	while (SDL_PollEvent(&event)) {

		switch (event.type) {

		case SDL_QUIT:
			isRunning = false;
			break;

		case SDL_KEYDOWN:

			switch (event.key.keysym.sym) {

			case SDLK_d:
				accel++;
			}

		case SDL_KEYUP:

			switch (event.key.keysym.sym) {

			case SDLK_d:
				while (accel > 0) {
					accel--;
				}
		    }
		}
	}
}

void Game::update() {
	manager->Update();
}

void Game::render() {
	SDL_RenderClear(gRenderer);
	// --------------------------
	manager->drawTexture("assets/doux/doux_00.png", 100, 100);
	manager->drawRect(250, 250, 300, 50);

	manager->drawTexture("assets/tard/tard_00.png", 10, 10);
	// --------------------------
	SDL_SetRenderDrawColor(gRenderer, 64, 64, 64, 1);
	SDL_RenderPresent(gRenderer);
}

void Game::clean() {
	SDL_DestroyWindow(mWindow);
	SDL_DestroyRenderer(gRenderer);
	SDL_Quit();
}

Thank you for your attention!

This loop will be executed all at once, without waiting for a frame. The optimizing compiler may even turn it into an “if(accel>0) accel=0;”.

If you want things to happen gradually, they have to happen in increments between frames. You’d want to gradually reduce “accel” in your “TextureManager::Update” function when the key isn’t being held down.

Rather than handling the KEYDOWN and KEYUP events, you probably want to use SDL functions to check the state of the D key in your Update function. Events aren’t the only way to get input, SDL also gives you access to state information, it can tell you whether keys are pressed or unpressed. SDL_GetKeyboardState() will likely be useful.

It’s also good to add some of your own state tracking using the event handlers. For instance, you might want to know not only whether the D key is down at the time your Update function is called, but whether it has been pressed (there has been a KEYDOWN) since the last time your Update function was called, or when you started the game, unpaused, or regained focus. So you could set a flag on KEYDOWN and clear it at any of these other times.

I can’t perform event handling in TextureManager class, since I have declared SDL_Event event; in the Game class. So, I can’t gradually reduce accel in TextureManager::Update function when the button is not being pressed. And the implementations for SDL functions given on stackexchange seem really complex. So, is there a way to do it with just KEYDOWN and KEYUP?

KEYDOWN events are special because they get repeated while you hold the key down. No other type of event work like this AFAIK.

Relying on repeated KEYDOWN events for game play is usually not a good idea because:

  • There is no way to control how fast the events should be repeated. In many games you want the movement to be smooth so you want to update the position a little each time rather than having big updates once in a while (same when updating other things like speed, etc.).

  • There is a bigger delay before the first repeated event compared to later ones.
    |–––––––––––––|––|––|––|––|––|––|…

  • If you press more than one key only the last key will be repeated.

As mentioned, you can implement your own state tracking. For instance, a quick and dirty fix is to have a global variable “d_is_down”, when you set to true in your event handler when you get a KEYDOWN and set to false when you get a KEYUP, and in your Update, read d_is_down and change your accel accordingly.

However, as you need to track more keys and have your program work properly after more unusual events, it will quickly become more complicated than using the keyboard state tracking that’s built into SDL.

Doing things the way that’s easiest for you to make things work as soon as possible is fine, but when you learn about the proper way to do things and you’re not going to do it properly right away, at least make a comment like, “//FIXIT: use SDL_GetKeyboardState instead of d_is_down”.

SDL is quite a low-level library, for expert programmers. As a beginner, you’re going to encounter difficult ideas and interfaces that seem complicated, but if you persist in studying them, you’ll fill the gaps in your understanding and see how simple they really are. You won’t just be learning SDL, but also C programming and how to think like a programmer.

Stackexchange can be helpful, but with SDL the best documentation is the header files and the tests. Here is how you’d approach it:

Looking at the header files:

The comments on SDL_GetKeyboardState tell us that you must call SDL_PumpEvents to update the state array. However, if you read the SDL_PumpEvents documentation, you’ll see it gets called by SDL_PollEvent or SDL_WaitEvent, and you’re already calling SDL_PollEvent, so you don’t need to add a call to SDL_PumpEvents in your current program, but you should make a note of it anyway.

So what does SDL_GetKeyboardState do? The comment tells us it returns a pointer to a Uint8 array containing the states of all of the keys on the keyboard, with a 1 meaning the key is pressed down, and a 0 meaning the key is up. Furthermore, this array will be updated in place, it will always return the same pointer. You can also send it a pointer to an int, if you want it to tell you the length of the array, or you can send it a null pointer, if you don’t want the array length.

The comment further tells us that the array is indexed by SDL_Scancode value of the key, so “SDL_GetKeyboardState(0)[scancode_for_d_key]” should work, but how do we find “scancode_for_d_key” when we know SDLK_d, which is from the SDL_Keycode enum? Well, let’s keep skimming the keyboard.h file…

Soon we come across “SDL_GetScancodeFromKey”, which takes an SDL_Keycode and returns an SDL_Scancode. This suggests “SDL_GetKeyboardState(0)[SDL_GetScancodeFromKey(SDLK_d)]” will give us the state of the D key: returning 1 if it’s down, and 0 if it’s up.

If you don’t understand “SDL_GetKeyboardState(0)[SDL_GetScancodeFromKey(SDLK_d)]”, here it is unpacked into more explicit form, showing all of the types:

//SDL_PumpEvents(); //make sure the SDL keyboard state is updated (skip because SDL_PollEvent is being called, could also skip if SDL_WaitEvent is called)
int array_length;
int *array_length_ptr=0; //&array_length; //we're making this a null pointer instead of a pointer to array_length since don't need the length of the keyboard state array
Uint8 *keyboard_state_array_pointer;
keyboard_state_array_pointer=SDL_GetKeyBoardState(array_length_ptr);
SDL_Keycode d_keycode=SDLK_d;
SDL_Scancode d_scancode=SDL_GetScancodeFromKey(d_keycode);
Uint8 d_key_state=keyboard_state_array_pointer[d_scancode];
if(d_key_state){
 //do stuff because the D key is down
}else{
 //do stuff because the D key is up
}

It’s just calling a function to get a pointer to an array, calling a function to translate a key code into a scancode that’s an index into the array, and looking up the index in the array. You’ll need to be able to do stuff like this, so don’t shy away from learning it.

1 Like

It’s easier process the inputs outside the handleEvents than inside it.

For example:
Construct a struct that keep the key state:

struct Key {
  bool pressed = false;
  int key = 0;//key simbol
};

And so, the game object use the Key in this way:

class Game {
  //change to std::vector if you want it
  Key keys[3];//0 = left, 1 = right, 2 = jump

  handleEvents() {
    SDL_Event event;
    while (SDL_PollEvent(&event)) {
        if (event.type == SDL_KEYDOWN) {
           for (int i = 0; i < 3; i++)
             //if press the key
             if (keys[i].key == event.sym.keysym.sym)
                 keys[i].pressed = true;
        }
        else if (event.type == SDL_KEYUP) {
           for (int i = 0; i < 3; i++)
             //if unpress the key
             if (keys[i].key == event.sym.keysym.sym)
                 keys[i].pressed = false;
        }
    }
  }
}

And so, in the game update you can make sothing like this:


...//inside main loop
//if press key left
if (keys[0].pressed) {
  //restart the current accelX
  accelX = -30;//example
}
//if press key right
else if (keys[1].pressed) {
  //restart the current accelX
  accelX = 30;//example
}
//if not press any directional key
else {
  //remove dampingX percents from accel each update
  accelX = accelX - (accelX * dampingX);
  //or
  //accelX = accelX * (1 - dampingX);
  //with this you evitates values very tiny
  if (fabs(accelX) <= 0.01)
    accelX = 0;
}

//and now, you can just sum up accelX in the current positionX
positionX = positionX + accelX;
//end game main loop

dampingX is the value of how much you want to remove from the accel.
dampingX must be a float ou double, and the positionX and accelX too.
For example:
float dampinX = 0.10;// removes 10% of the accelX each time from unpressed left and right
float positionx = 100;
float accelX = 10;

first update
press right
accelX = 30;
positionX = positionx + 30;

second update
release right and so, none key press
accelX = 30 - 30 * 0.10 = 30 - 3 = 27;
positionX = positionx + 27;

third update
without any press
accelX = 27 - 27 * 0.10 = ;24.3;
positionX = positionx + 24.3;

And so on until accelX going to be <= 0.01(threshold) and be setted to 0
With this, the player will be slowly stop.

Change the value of the damping until you get the correct for you.

Remember of separate the inputs and processing, this facilitates too much the programming.
Basically is: get the inputs from sdl_pollevents translated into variable values, and so, process the values on the update (not in the handleEvents()).

Good luck.

Thank you all for your replies!
@Funchucks, you taught me how to learn. Thank you!

So, I implemented SDL_GetKeyboardState and the input handling is definitely better than KEYDOWN and KEYUP.
Here’s the updated update() function in Game.cpp:

void Game::update() {
	//manager->Update();
	if (SDL_GetKeyboardState(0)[SDL_GetScancodeFromKey(SDLK_d)]) {
		x_shift += speed + accel;
		accel += 2;
	}
	else {
		while (x_shift != 0) {
			accel -= 1;
		}
	}
}

There is one problem that still persists.The player moves fine the first time SDLK_d is down and stops as intended when SDLK_d is up however after this first cycle the player stops moving altogether and doesn’t respond to key presses. Where am I going wrong?

Analyse your own code. There aren’t many lines there. What is the purpose of each line? Will it accomplish that purpose, or do something unwanted? Does it make sense and belong where it is? The computer will only do what you tell it, it doesn’t try to guess what you really want it to do.

You should be able to solve this for yourself. I will say there’s more than one problem with that function.

1 Like

Ok, I was able to fix that, here’s the updated update() function:

void Game::update() {
	//manager->Update();
	if (SDL_GetKeyboardState(0)[SDL_GetScancodeFromKey(SDLK_a)]) {
		right_accel = 0;
		x_shift -= speed + left_accel;
		left_accel += 2;
	}
	else {
		if (left_accel > 0) {
			left_accel -= 0.5;
		}
	}

	if (SDL_GetKeyboardState(0)[SDL_GetScancodeFromKey(SDLK_d)]) {
		left_accel = 0;
		x_shift += speed + right_accel;
		right_accel += 2;
	}
	else {
		if (right_accel > 0) {
			right_accel -= 0.5;
		}
	}
}

Thank you for your help!

I am trying to limit frame rate and I have one doubt, here’s the logic:

start_loop = SDL_GetTicks(); 

		game->handleEvents();
		game->update();
		game->render();

		delta = SDL_GetTicks() - start_loop;

		if (delta < desired_delta) {
			SDL_Delay(desired_delta - delta);
		}

shouldn’t it be delta = start_loop - SDL_GetTicks() since, according to what I understand delta is storing a negative value if delta = SDL_GetTicks() - start_loop since, start_loop will store time since loop was initialized and delta stores current time minus time passed till now, which should be a negative value.

No, that line is correct as-is.

SDL_GetTicks() always returns the time since the start of the program (since SDL was initialized).

Since start_loop stores an earlier time than the current time that you get from SDL_GetTicks() it means that SDL_GetTicks() - start_loop will give you a positive value.


However, I don’t think you’re only interested in the time that it takes to run those 3 functions. You’re interested in the total time since last time you reached that same piece of code (including SDL_Delay() which might take longer to return than you specify).

So what I would recommend you to do is to only call SDL_GetTicks() once per frame and instead compare against the previous value.

Uint32 last_time = SDL_GetTicks(); 

...

while (game_is_running)
{
	game->handleEvents();
	game->update();
	game->render();

	Uint32 current_time = SDL_GetTicks();
	Uint32 delta = current_time - last_time;
	last_time = current_time;

	if (delta < desired_delta) {
		SDL_Delay(desired_delta - delta);
	}
}
1 Like

Oh I get it now, Thank you! @Peter87