Trouble With SDL Input

Started by
4 comments, last by JLateralus 14 years, 2 months ago
Ok, here's my problem. I'm writing my first Tetris clone in C++, using SDL for graphics and input. I have everything working great except for the input. When I increase the speed at which my game loop runs, the input runs faster and faster as well. My intention is to allow left, right, and down key holding on the keyboard to allow constant movement in that direction. However, you need to press up (rotate) or space (hold block) each time you want to perform that function. In other words, I don't want a player to be able to hold the up key to rotate constantly. With the way I have things set up, I think I know why the single key pressing buttons (up and space) don't work right - they currently rotate constantly with a hold. My problem is that I'm not quite sure how to change things to have them work the way I want. Should I be using the a timer somehow? I feel like it's just something simple... Anyway, on to the code. This is the function that handles input during the game.

void HandleGameInput()
{
	g_Input->readInput();

	//this section of keys can be held down
	bool* keysHeld = g_Input->getInput();

	if(keysHeld[SDL_QUIT])
	{
		//while stack isn't empty, pop
		while(!g_StateStack.empty())
		{
			g_StateStack.pop();
			return;
		}
	}

	if(keysHeld[SDLK_ESCAPE])
	{
		g_StateStack.pop();
		return;
	}

	if(keysHeld[SDLK_DOWN])
	{
		if(!CheckWallCollisions(g_FocusBlock, DOWN) && !CheckEntityCollisions(g_FocusBlock, DOWN))
		{
			g_FocusBlock->Move(DOWN);
			return;
		}
	}
	if(keysHeld[SDLK_LEFT])
	{
		if(!CheckWallCollisions(g_FocusBlock, LEFT) && !CheckEntityCollisions(g_FocusBlock, LEFT))
		{
			g_FocusBlock->Move(LEFT);
			return;
		}
	}
	if(keysHeld[SDLK_RIGHT])
	{
		if(!CheckWallCollisions(g_FocusBlock, RIGHT) && !CheckEntityCollisions(g_FocusBlock, RIGHT))
		{
			g_FocusBlock->Move(RIGHT);
			return;
		}
	}

	if(keysHeld[SDLK_SPACE])
	{
		HoldFocusBlock();
		return;
	}

	if(keysHeld[SDLK_UP])
	{
		//check collisions before rotating
		if(!CheckRotationCollisions(g_FocusBlock))
		{
			g_FocusBlock->Rotate();
			return;
		}
	}
}


Here is the code for the readInput() method of the g_Input object:

void Input::readInput()
{
	if(SDL_PollEvent(&keyEvent_))
	{
		if(keyEvent_.type == SDL_QUIT)
		{
			closedWindow_ = true;
		}

		if(keyEvent_.type == SDL_KEYDOWN)
		{
			keysHeld_[keyEvent_.key.keysym.sym] = true;
		}

		if(keyEvent_.type == SDL_KEYUP)
		{
			keysHeld_[keyEvent_.key.keysym.sym] = false;
		}
	}
}


And the getInput() method of the object just returns a bool* to the keysHeld array. So... any thoughts on how I can improve? Thanks!
Advertisement
Quote:
                if(keyEvent_.type == SDL_KEYDOWN)		{			keysHeld_[keyEvent_.key.keysym.sym] = true;		}



That's where you want to execute the rotation/etc. if you want it to be per-press instead of while-hold. How you do that depends on your design. Perhaps a HandleKeyDown(SDLKey key) function can be called in there, and then you'd put this code block:
Quote:
                //check collisions before rotating		if(!CheckRotationCollisions(g_FocusBlock))		{			g_FocusBlock->Rotate();			return;		}


inside HandleKeyDown() instead of HandleGameInput() (of course, only if the appropriate key got pressed).

On another note, there's no point in using your own boolean array. Just use SDL_GetKeyState(), which returns an array of the states of the keys:
uint8_t *keys = SDL_GetKeyState();if (keys[SDKL_ESCAPE]) /* do stuff */;


Also, SDL_QUIT is an event type, not a key. This doesn't make sense: if(keysHeld[SDL_QUIT])
You'll want to change this line:
if(SDL_PollEvent(&keyEvent_))
To this:
while(SDL_PollEvent(&keyEvent_))
As is, you're only processing one pending event per update, but in general you should process *all* pending events. (Otherwise, events will get 'backed up', and you may get noticeable lag or other similar problems.)

Also, you might check out SDL_GetKeyState() (it looks like you're basically duplicating the functionality that it already offers).

As for the problem you describe, yes, you'll most likely want to use a timer. There are a couple of different ways to handle keyboard input with SDL: one is to check an 'is down' flag for each key as you're doing, and the other is to respond to individual 'key down' and 'key up' messages. In your case, for (e.g.) piece rotation, all you're really interested in is the corresponding 'key down' message; when the key is pressed, you rotated the piece, and assuming you've disabled 'key repeat', the piece won't rotate again until the player releases the key and presses it again.

For linear motion, you'll want to create and start an 'interval timer' when the key is pressed, and then allow it to continue to run as long as the key is down. Every time the timer 'trips', you move the piece. The timer should trip when it starts (so that the piece moves immediately), and at a regular interval of your choosing thereafter. The implementation details are somewhat incidental, but a fairly straightforward solution is to create a simple 'interval timer' class that manages the details of tracking elapsed time and sending a 'trip' signal at the appropriate intervals.

[Edit: Fixed copy-and-paste error.]

[Edited by - jyk on February 18, 2010 9:39:32 PM]
Quote:Original post by jyk
You'll want to change this line:
if(SDL_PollEvent(&keyEvent_))
To this:
if(SDL_PollEvent(&keyEvent_))
I'm going to take a guess that you meant it should be this:
while(SDL_PollEvent(&keyEvent_))

C++: A Dialog | C++0x Features: Part1 (lambdas, auto, static_assert) , Part 2 (rvalue references) , Part 3 (decltype) | Write Games | Fix Your Timestep!

Quote:I'm going to take a guess that you meant it should be this:
while(SDL_PollEvent(&keyEvent_))
Epic fail :-| Thanks for the catch - will fix it right now.
Thanks for the help all... your suggestions make much sense. Muchly appreciated.

This topic is closed to new replies.

Advertisement