Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Handling User Input  (Read 12167 times)

0 Members and 1 Guest are viewing this topic.

Megatron

  • Newbie
  • *
  • Posts: 22
    • View Profile
Handling User Input
« on: April 19, 2011, 06:40:42 pm »
This is abit more of a conceptual question, but I figured this was likely the best place to ask. I looking for advice and I was wondering how other people handle input in their games.

For my game, which is primarily an RPG, but it has some action elements where the player will need to time their inputs carefully.

The way I designed mine, at least at the moment, was to have a separate class that's part of the game as a whole. Every Key Event/Joystick Event/Mouse Event is fed into that class, and the class will map the various types of input to ingame actions using maps like below:

Code: [Select]
std::map<sf::Key::Code, InputActions> KeyMapper;
std::map<sf::Key::Code, InputActions> KeyMapper2;
std::map<unsigned int, InputActions> JoyMapper;


where InputActions is an enum for the various actions in the game (Up, Down, Left, Right, Cancel, Confirm, Start, Select, etc.).

The input class has a bool array
Code: [Select]
bool ActionSet[InputCount]; that is used to indicate what inputs are currently held. I activate the inputs as follows (the below are for keyboard keys, the joysticks use something similar)

Code: [Select]

//Used when the input module supports only one key active at a time
void HandleSingleKey(sf::Key::Code &key, bool released = false)
{
                      if (!released)
                      {  
for (int i = 0; i < InputCount; ++i)
{
if (i != sf::Mouse::Button::Left && i != sf::Mouse::Button::Right)
ActionSet[i] = false;
}
                        }
auto it = KeyMapper.find(key);
if (it != KeyMapper.end())
ActionSet[it->second] = !released;
}

//Used when the input module supports multiple keys at a time
void HandleMultiKey(sf::Key::Code &key, bool released = false)
{
auto it = KeyMapper.find(key);
if (it != KeyMapper.end())
{
ActionSet[it->second] = !released;
}
}


I'm currently only testing with multiple inputs enabled (the HandleMultKey function).

Basically, each object that needs to handle user input will be fed a pointer to this input module, and the current game time at the start of each loop iteration. It is up to the object to determine if it is ready to resample the input and find the changes in user input (like below). InputRes represents how much time needs to elapse between each input sampling

Code: [Select]
void QTriadGame::UpdateInput(InputModule* module, float time)
{
if (time - LastInputUpdate > InputRes )
{
                        //Omitted Code Handling inputs by looking up active keys from module
                        LastInputUpdate = time;
              }
}



Here's my concerns about the way I'm doing it:

 As you can tell form the earlier, I'm relying on the Released event to tell me when to mark an input as invalid. I noticed while debugging that if the program was paused during input handling, the released event would fail. I'm worried about this happening once the game is running normally, and I'm not entirely sure how to deal with it. Anything involving timeouts could be bad as it's possible the user holds down a button for an extended period of time.

Additionally, I'm likely going to need to know later on when the key became active, so I might change my array from a bool array to an array of bools/float where float indicates the gametime when that input became active

It just feels like I'm making this more complicated then it needs to be, so I was looking for some advice/suggestions or how other people did their use input.

Thanks in advance

Wizzard

  • Full Member
  • ***
  • Posts: 213
    • View Profile
Handling User Input
« Reply #1 on: April 20, 2011, 09:27:06 am »
You are over complicating this to an extreme. For one, don't use events to see if a key is down, just use the window's input.

Using the window's input tutorial (skip to "Getting real-time inputs")
Input (SFML 1.6)
Input (SFML 2.0)

If you want objects to be aware of input, just make the window's input globally accessable via a global variable, a function that returns a reference or pointer to the input, or via passing a reference or pointer to functions.

Quote from: "Megatron"
Additionally, I'm likely going to need to know later on when the key became active, so I might change my array from a bool array to an array of bools/float where float indicates the gametime when that input became active

Create a timer that activates when the pressed event of the key you're measuring is triggered. Then, check on every frame to see if the timer has elapsed the amount of seconds you want it to and, if it has, do what you want to be done. Finally, make sure to ignore the timer if a released event is called for the key you're measuring.

Edit:
You should also ignore the timer if a LostFocus event occurs.

Fred_FS

  • Newbie
  • *
  • Posts: 48
    • View Profile
Handling User Input
« Reply #2 on: April 20, 2011, 11:12:33 am »
Megatron:
To handle the user inputs, I have two classes.
input_map contains maps for mouse and keyboard inputs
Code: [Select]

std::map<sf::Key::Code, boost::function<void()>> key_bindings;
std::map<sf::Mouse::Button, boost::function1<void, const sf::Input&>> mouse_bindings;

To use this map, I have to bind a key or a button to a function, which is supposed to be called, if the button/key is pressed(boost is very helpful ;)).
Code: [Select]

template<class T>
void register_input_id( sf::Key::Code key, void (T::*Func)(), T& obj)
{
key_bindings[key] = boost::bind(Func, boost::ref(obj));
}
template<class T>
void register_input_id( sf::Mouse::Button mouse, void (T::*Func)(const sf::Input&), T& obj)
{
mouse_bindings[mouse] = boost::bind(Func, boost::ref(obj), _1);
}

The scond class is an input-manager, which contains all the input-maps. At each update-step it checks, weather a key of an input-map is pressed and runs the function:
Code: [Select]

for( std::vector<input_map>::iterator action_it = input_maps_.begin();
action_it != input_maps_.end();
action_it++)
{
for( std::map<sf::Key::Code, boost::function<void()>>::iterator key_it = action_it->key_bindings.begin();
key_it != action_it->key_bindings.end();
key_it++)
{
if(input_.IsKeyDown(key_it->first))
key_it->second();
}

for( std::map<sf::Mouse::Button, boost::function1<void, const sf::Input&>>::iterator mouse_it = action_it->mouse_bindings.begin();
mouse_it != action_it->mouse_bindings.end();
mouse_it++)
{
if( input_.IsMouseButtonDown(mouse_it->first) && !mouse_button_pressed_.at( mouse_it->first ) )
{
mouse_it->second( input_ );
}
}
}


Well to use it, is very easy. An instance of an input-map is created in every class, where it is needed and some key events have to be registered:
Code: [Select]

input_map_.register_input_id( sf::Key::Back, &gs_game::exit_game, *this );
game_env_.input_manager.register_input_map( input_map_ );

And maybe my system is complicated, too, but it is also very easy to port and to use it with other input-frameworks(a few weeks ago I used it with OIS)

Quote from: "Wizzard"

If you want objects to be aware of input, just make the window's input globally accessable via a global variable, a function that returns a reference or pointer to the input, or via passing a reference or pointer to functions.

It's a very simple way indeed, but I wouldn't prefer it. If I got your point right, every single object(which is supposed to check inputs) has to get an input-variable and handle the input by itself. I don't think that this is a very good way, because I don't think that it is the business of every single class to handle their own inputs. And on the one hand you have to copy code and use the same code twice(or even more often) to process input for more than one object. If there is a mistake in your code, you will have to change it a couple of times. On the other hand I think that code, that is used more than once or twice, should be designed in another way, to use the same code for all instances. And this is what an input-manager does ;).

Wizzard

  • Full Member
  • ***
  • Posts: 213
    • View Profile
Handling User Input
« Reply #3 on: April 21, 2011, 03:28:07 am »
The input class, internally, is functionally equivalent to your class except that the input class does not register callback functions. Instead, the input class can be queried for the state of an input by calling the appropriate function. Here's the code to show you just how equivalent they are.
Code: [Select]
bool Input::IsKeyDown(Key::Code key) const
{
    return myKeys[key];
}


void Input::OnEvent(const Event& event)
{
    switch (event.Type)
    {
        // Key events
        case Event::KeyPressed :  myKeys[event.Key.Code] = true;  break;
        case Event::KeyReleased : myKeys[event.Key.Code] = false; break;

        // Mouse event
        case Event::MouseButtonPressed :  myMouseButtons[event.MouseButton.Button] = true;  break;
        case Event::MouseButtonReleased : myMouseButtons[event.MouseButton.Button] = false; break;

        // Mouse move event
        case Event::MouseMoved :
            myMouseX = event.MouseMove.X;
            myMouseY = event.MouseMove.Y;
            break;

        // Joystick button events
        case Event::JoyButtonPressed :  myJoystickButtons[event.JoyButton.JoystickId][event.JoyButton.Button] = true;  break;
        case Event::JoyButtonReleased : myJoystickButtons[event.JoyButton.JoystickId][event.JoyButton.Button] = false; break;

        // Joystick move event
        case Event::JoyMoved :
            myJoystickAxis[event.JoyMove.JoystickId][event.JoyMove.Axis] = event.JoyMove.Position;
            break;

        // Lost focus event : we must reset all persistent states
        case Event::LostFocus :
            ResetStates();
            break;

        default :
            break;
    }
}


Futhermore, SFML runs the OnEvent callback of the Input class always. This means that you always have the SFML input manager running even when you're not using it. It's unnecessary to have two implementations of the same thing running at the same time. Even if you want to create your own input manager for portability to other input managing libraries, you should simply rebind the functions of your input manager to use SFML's input manager. You won't be able to use callback functions like this, but instead, just give the objects in your application an update method that is run every frame and checks for it's own input. Those callback functions add some extremely minor overhead too that is unnecessary. Your method doesn't have any code encapsulation benefits over my method either. Both implementations make classes fully aware of what input they're going to receive. The same amount of internal information is exposed.

Fred_FS

  • Newbie
  • *
  • Posts: 48
    • View Profile
Handling User Input
« Reply #4 on: April 21, 2011, 11:46:10 am »
Quote from: "Wizzard"
Futhermore, SFML runs the OnEvent callback of the Input class always. This means that you always have the SFML input manager running even when you're not using it.

That's a good point. Thank you. I probably will adjust my system to the event callback and integrate my own callback-system :). I do prefer this callback-system, because it is for example very easy to script and there is no difficulty to provide a custom assignment of keys.
But I think this is a matter of taste and I prefer the callback system.

Nexus

  • SFML Team
  • Hero Member
  • *****
  • Posts: 6286
  • Thor Developer
    • View Profile
    • Bromeon
Handling User Input
« Reply #5 on: April 21, 2011, 06:40:35 pm »
Hey, a few days ago I released a library which provides a callback system for SFML events, also using the function class. Maybe you could have a look at it (I need to advertise for it :D). However, there is currently only one indirection: You can map the different events like KeyPressed, MouseMoved etc. to callbacks.
Code: [Select]
sf::RenderWindow window(...);
thor::SfmlEventSystem system(window);
system.Connect(sf::Event::KeyPressed, &OnKeyPressed);
system.Connect(sf::Event::Closed, std::tr1::bind(&sf::Window::Close, &window));

// Main loop
while (window.IsOpened())
{
// Handle SFML events
system.PollEvents();

// Draw window
...
}

But I've already planned to go a step further, that is, to map different key strokes to different functions. In fact, you could already do that with the EventSystem template, but it's probably not too user-friendly at the moment... ;)
Zloxx II: action platformer
Thor Library: particle systems, animations, dot products, ...
SFML Game Development:

Fred_FS

  • Newbie
  • *
  • Posts: 48
    • View Profile
Handling User Input
« Reply #6 on: April 22, 2011, 01:09:20 am »
Quote from: "Nexus"
You can map the different events like KeyPressed, MouseMoved etc. to callbacks.

Nice framework and nice system. I began to wrote a similar system(this is part of my new plan to implement an event-manager), because I didn't know that you've already done this ;). But my event-manager is supposed to bind callbacks to both keys and events(as you're planning^^). But many functions of our framework, I have already implemented by myself, because they're very usefull. So it is just natural, that you have already written an useful event-callback-manager ;).