SDL Key/Mouse Simplified Intput Class

Started by
9 comments, last by iLoveGameProgramming 10 years, 9 months ago

Hello everybody

Today I decided to create controls class for my engine (when I say engine, It is VERY loosely said).

Basically I am trying to make SDL code a little simpler to use, the idea would be you create controls class and then

you just do


if (controls->isKeyDown(SLDK_ESCAPE))
{
std::cout << "Escape was Pressed" << std::endl;
}

if (controls->isKeyHeld(SLDK_ESCAPE))
{
std::cout << "Escape is Held" << std::endl;
}

if (controls->isKeyUp(SLDK_ESCAPE))
{
std::cout << "Escape was Released" << std::endl;
}

if (controls->isMouseUp(SDL_LEFT_MOUSE)
{
std::cout << "Left Mouse was Released" << std::endl;
}

and you can guess the rest for the mouse tongue.png

If there is already free controls class like that please let me know, or if there is better way of doing controls in SDL smile.png

Unfortunately it does not work the way I intended it too. I managed to get Key Presses working until you start pressing multiple keys, then everything goes messy and it starts thinking keys are pressed when they are not... It will know when key is pressed, held and released as long only that key is being pressed at that every moment.

The Mouse does not work more on other hand. It recognizes that the key was pressed/released but for example when I do


if (controls->isMouseDown(SDL_LEFT_MOUSE)
{
std::cout << "Left Mouse was Pressed" << std::endl;
}

and press any of the mouse buttons, it will read that line even when I use Mouse Wheel button. Also isMouseHeld doesn't work at all. It is never read.

Mouse controls also go messy when you start touching multiple mouse keys at once. Gets stuck on release, just like in controls. Thinks the mouse was released until 1 button is pressed and resets it.

If this class would work I think it could be not just useful to me but would save some time for anyone else smile.png Instead of reinventing the wheel each time...

I really hate posting large chunks of code, but I have no idea where the problem is in the code... so here is .h and .cpp file. I've commented .h file smile.png

Thank you for your time in advance smile.png

controls.h


#pragma once
#include "stdafx.h"

class DllExport controls
{
public:
	//event is passed down from the engine class
	controls(SDL_Event* theEvent);
	~controls(void);

	//different types of possible key states
	#define KEY_NONE -1
	#define KEY_PRESSED 0
	#define KEY_RELEASED 1
	#define KEY_HOLDING 2

	//returns true key was pressed
	bool isKeyDown(int SDLK_ID);
	//returns true key is being held
	bool isKeyHeld(int SDLK_ID);
	//returns true key was released
	bool isKeyUp(int SDLK_ID);

	//returns true if Mouse button was pressed
	bool isMouseDown(int MouseButton);
	//returns true if Mouse button is being held
	bool isMouseHeld(int MouseButton);
	//returns true if Mouse button was released
	bool isMouseUp(int MouseButton);

	//Gets SDL event
	SDL_Event* GetEvent();

	//this runs every frame
	void Update();
private:
	SDL_Event* events; //Pointer to an SDL events system

	//Holds Current Mouse states
	int LeftMouseState;
	int RightMouseState;
	int LeftMouseHeld;
	int RightMouseHeld;

	//Press and Release Key States
	int KEYS_STATE[322];  // 322 is the number of SDLK_DOWN events
	//Held Key States
	int KEYS_STATE2[322];
};

controls.cpp


#include "stdafx.h"
#include "controls.h"


controls::controls(SDL_Event* theEvents)
{
	events = theEvents;

	for(int i = 0; i < 322; i++) { // init them all to false
		KEYS_STATE[i] = KEY_NONE;
		KEYS_STATE2[i] = KEY_NONE;
	}

	LeftMouseState = KEY_NONE;
	RightMouseState = KEY_NONE;
	LeftMouseHeld = KEY_NONE;
	RightMouseHeld = KEY_NONE;
}


controls::~controls(void)
{
}

bool controls::isMouseDown(int MouseButton)
{

	if (MouseButton == SDL_BUTTON_LEFT)
	{
		if (LeftMouseState == KEY_PRESSED)
			return true;
	}
	else if (MouseButton == SDL_BUTTON_RIGHT)
	{
		if (RightMouseState == KEY_PRESSED)
			return true;
	}

	return false;
}

bool controls::isMouseHeld(int MouseButton)
{

	if (MouseButton == SDL_BUTTON_LEFT)
	{
		if (LeftMouseState == KEY_HOLDING) 
			return true;
	}
	else if (MouseButton == SDL_BUTTON_RIGHT)
	{
		if (RightMouseState == KEY_HOLDING)
			return true;
	}

	return false;
}

bool controls::isMouseUp(int MouseButton)
{

	if (MouseButton == SDL_BUTTON_LEFT)
	{
		if (LeftMouseState == KEY_RELEASED) 
			return true;
	}
	else if (MouseButton == SDL_BUTTON_RIGHT)
	{
		if (RightMouseState == KEY_RELEASED)
			return true;
	}

	return false;
}

void controls::Update()
{

	switch (events->type) {

	case SDL_MOUSEBUTTONDOWN:


		if (events->button.button == SDL_BUTTON_LEFT);
		{
			if (LeftMouseHeld == KEY_NONE)
				LeftMouseState = KEY_PRESSED;
			else
				LeftMouseState = KEY_NONE;

			LeftMouseHeld = KEY_HOLDING;

		}
		if (events->button.button == SDL_BUTTON_RIGHT);
		{
			if (RightMouseHeld == KEY_NONE)
				RightMouseState = KEY_PRESSED;
			else
				RightMouseState = KEY_NONE;

			RightMouseHeld = KEY_HOLDING;
		}
		break;
	case SDL_MOUSEBUTTONUP:

		if (events->button.button == SDL_BUTTON_LEFT);
		{

			if (LeftMouseHeld == KEY_HOLDING)
				LeftMouseState = KEY_RELEASED;
			else
				LeftMouseState = KEY_NONE;

			LeftMouseHeld = KEY_NONE;

		}
		if (events->button.button == SDL_BUTTON_RIGHT);
		{
			if (RightMouseHeld == KEY_HOLDING)
				RightMouseState = KEY_RELEASED;
			else
				RightMouseState = KEY_NONE;

			RightMouseHeld = KEY_NONE;
		}
		break;
	case SDL_KEYDOWN:

		if (KEYS_STATE2[events->key.keysym.sym] == KEY_NONE)
		{
			KEYS_STATE[events->key.keysym.sym] = KEY_PRESSED;
		}
		else
			KEYS_STATE[events->key.keysym.sym] = KEY_NONE;

		KEYS_STATE2[events->key.keysym.sym] = KEY_HOLDING;


		break; 
	case SDL_KEYUP:

		if (KEYS_STATE2[events->key.keysym.sym] == KEY_HOLDING)
		{
			KEYS_STATE[events->key.keysym.sym] = KEY_RELEASED;
		}
		else
			KEYS_STATE[events->key.keysym.sym] = KEY_NONE;

		KEYS_STATE2[events->key.keysym.sym] = KEY_NONE;

		break;
	default:
		break;

	}
}

bool controls::isKeyDown(int SDLK_ID)
{

	if (KEYS_STATE[SDLK_ID] == KEY_PRESSED)
	{
		return true;
	}
	else 
		return false;
}

bool controls::isKeyHeld(int SDLK_ID)
{

	if (KEYS_STATE2[SDLK_ID] == KEY_HOLDING)
	{
		return true;
	}
	else 
		return false;
}


bool controls::isKeyUp(int SDLK_ID)
{

	if (KEYS_STATE[SDLK_ID] == KEY_RELEASED)
	{
		return true;
	}
	else 
		return false;
}


SDL_Event* controls::GetEvent() { return events; }
Advertisement

Couple of quick tips:


//different types of possible key states
#define KEY_NONE -1
#define KEY_PRESSED 0
#define KEY_RELEASED 1
#define KEY_HOLDING 2

Why are you using #define? #define is evil and should be avoided. Use an enum or const, e.g.:


enum KeyState
{
    KEY_NONE = -1,
    KEY_PRESSED = 0,
    KEY_RELEASED = 1
    KEY_HOLDING = 2
};

I believe this:


#include "stdafx.h"
Makes your code less portable, although a person could delete it/comment it out, but yeah.
This:

if (KEYS_STATE[SDLK_ID] == KEY_PRESSED)
{
	return true;
}
else 
	return false;

Could simply be:


return KEYS_STATE[SDLK_ID] == KEY_PRESSED;

Also, KEYS_STATE2 and KEY_STATES are not not a very good names IMO. Perhaps make them more descriptive? KEYS_PRESSED_STATES and KEYS_HELD_STATES? BTW, you could probably use a struct/array to make it into one variable.

There's also a lot of some-what duplicate code, the only thing that's different is the variables, this could be avoided.

anax - An open source C++ entity system

Thank you for reply Pinebanana :)


Why are you using #define? #define is evil and should be avoided. Use an enum or const, e.g.:

good idea, no one ever told me they are bad, but I guess enum are easier to manage :)

I am using that include because it needs to include SDL. I guess I could move it to include just SDL. It would make it more portable.

return KEYS_STATE[SDLK_ID] == KEY_PRESSED;

I had no idea you can do something like that! So this returns false if its anything else besides KEY_PRESSED, and true if it is?

I am aware that there is some duplicated code, trying to get it working properly first. Still didn't figure out how to get multiple key presses at once :(

Thanks it was very good feedback :)

if(x) triggers a branch of code if 'x' is true. It takes a bool.

A == B is a function that returns a bool.

&&, ||, !, >, <, <=, >=, also return bools.

This means you can go:


bool result = (a > 4) && (a < 6);
if(result)
{
    //Do something.
}

Which can be really useful when dealing with alot of conditionals. smile.png

About polling the current state with SDL, SDL already provides functions for that:

SDL_GetKeyState (renamed 'SDL_GetKeyboardState' in SDL 2)

SDL_GetMouseState

As for why #defines are generally considered bad practice, consider this:


#define FIVE 3 + 2
#define FOUR 4

std::cout << "The wrong result: " << FIVE * FOUR << std::endl;

As for why #defines are generally considered bad practice, consider this:


#define FIVE 3 + 2
#define FOUR 4

std::cout << "The wrong result: " << FIVE * FOUR << std::endl;

Which is why it's recommended to do this:


#define FIVE (3 + 2)
#define FOUR (4)
std::cout << "The correct result: " << FIVE * FOUR << std::endl;

I do agree that constants or an enum is a better choice though.

Just thought I'd defend #define a bit. rolleyes.gif

It's very useful at times. Just needs to be used when it should be used.

Which is why it's recommended to do this:

#define FIVE (3 + 2)
#define FOUR (4)
std::cout << "The correct result: " << FIVE * FOUR << std::endl;

Yep, but it's the simplest illustration of why it can cause problems - it just does a copy+paste of the define with what it was defined to.

I also use defines in my project; not frequently, but they are useful in a good number of circumstances. They just should be avoided for things that there are superior options for, like when using them for constant values.

if(x) triggers a branch of code if 'x' is true. It takes a bool.

A == B is a function that returns a bool.

&&, ||, !, >, <, <=, >=, also return bools.

This means you can go:


bool result = (a > 4) && (a < 6);
if(result)
{
    //Do something.
}

Oh wow, thank you for this information. I always thought that the if statements are doing the job of returning the value. Not that I can use this, this way. I can see how it could be useful! Everyday learning something new :D

I've replaced #define with enum :)

I am using the SDL-1.2.15 unfortunately, I guess it's time to upgrade to SDL2 :)

Maybe upgrading will fix my problem with toggling full screen and losing loaded textures in OpenGL. But that fix is for another day :)

Once I get controls class working I'll post it here, so anyone else can use it. If not I'll keep asking questions :)

The two functions I linked to are for SDL 1.2, and do exactly what I think you are asking for. smile.png

Hehe yeah I've miss read it my bad. I still managed to upgrade my engine with SDL2.0 and everything seems to work besides the controls :P. Just want to let you guys know I haven't forgotten about this post and I will post this class here once its absolutely glitches :) So maybe it could help someone in the future. Hopefully I will find some time tomorrow to finish it up.

One other thing to consider (although this could be done at a higher layer). Typically, users want to map their keys/buttons to do different things. So, in my Input manager, I give it enumerations like KEY_MOVE_LEFT, KEY_MAIN_FIRE, KEY_SPECIAL_FIRE, KEY_JUMP, etc to test for key presses. And the InputManager reads some configuration file to determine which actual keys (or buttons) map to what ability. So, shooting a gun might be Left Mouse Button for one user, but Right Ctrl for another.

Good luck!

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)

This topic is closed to new replies.

Advertisement