Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
SFML Game Development.pdf
Скачиваний:
209
Добавлен:
28.03.2016
Размер:
4.19 Mб
Скачать

Chapter 4

Customizing key bindings

A big part with input management in a game is allowing the user to customize how he interacts with it, like the keys. Most of the time, you can find the most popular key bindings and see them written directly in the code. But there will always be people that want to do stuff their way.

We have to provide tools in order to dynamically bind the keys to specific actions.

With the command queue introduced in the previous sections, this becomes a much easier task to accomplish.

With the command queue, we already define specific actions for a specific key. The difference is that right now, we have it hardcoded as follows:

if (sf::Keyboard::isKeyPressed(sf::Keyboard::Left)) commands.push(moveLeft);

if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right)) commands.push(moveRight);

The problem with this code is that it is very inflexible. We have to change a lot in order to allow any key to respond to any action. We have two clearly separate sets of data that we want to link together. We have the actual action that we want to perform, such as moving left or right. Then, we have the input key, the key that is to trigger the action.

Now the keys in sf::Keyboard are just part of an enum. So on top of this, we create an enum in our Player class that represents the different kinds of actions we associate with the pressed keys:

class Player

{

public:

enum Action

{

MoveLeft,

MoveRight,

...

};

 

void

assignKey(Action action,

 

sf::Keyboard::Key key);

sf::Keyboard::Key

getAssignedKey(Action action) const;

...

 

[ 109 ]

www.it-ebooks.info

Command and Control – Input Handling

private:

 

static bool

isRealtimeAction(Action action);

private:

std::map<sf::Keyboard::Key, Action> mKeyBinding;

std::map<Action, Command>

mActionBinding;

};

 

Here, we have divided the input into two abstractions. We have the key binding to a specific action, and we have the binding of an action to a specific command. This is all we need to remove any hardcoded segments of input.

Next step is actually using this to translate an input to a command, which will essentially be our key bindings. We do this best by iterating through our key bindings map and just performing a simple check if the key is being pressed. If it is, we tell the command queue to insert our command we provide it with. The function

Player::isRealtimeAction() returns if the specified action is triggered by the real-time input (as opposed to events).

void Player::handleRealtimeInput(CommandQueue& commands)

{

FOREACH(auto pair, mKeyBinding)

{

if (sf::Keyboard::isKeyPressed(pair.first) && isRealtimeAction(pair.second))

commands.push(mActionBinding[pair.second]);

}

}

Just remember that this function has to be called once every frame, otherwise nothing will happen.

The two functions assignKey() and getAssignedKey() set and get the key mapped to a specific action. Their implementations perform map operations as expected, the only notable thing is that assignKey() checks that no two keys map to the same action.

The command stored under an action in mActionBinding knows exactly what it is supposed to do, so you don't have to provide any extra code to handle the key

press, since it has been abstracted out into its own entity. You have already done this previously in the chapter. The difference now is that we store the commands in the map together instead of unique variables.

[ 110 ]

www.it-ebooks.info

Chapter 4

The following is the constructor, where we set the default settings that you expect and prepare everything to work:

Player::Player()

{

mKeyBinding[sf::Keyboard::Left] = MoveLeft; mKeyBinding[sf::Keyboard::Right] = MoveRight;

mActionBinding[MoveLeft].action = [] (SceneNode& node, sf::Time dt)

{

node.move(-playerSpeed * dt.asSeconds(), 0.f);

};

mActionBinding[MoveRight].action = [] (SceneNode& node, sf::Time dt)

{

node.move(playerSpeed * dt.asSeconds(), 0.f);

};

FOREACH(auto& pair, mActionBinding) pair.second.category = Category::PlayerAircraft;

}

We assign the actions to the keys and achieve our initial key binding. After that we create the commands and implement the lambda function to be executed. The last iteration is just to ensure that the commands are only applied on the player aircraft. It should all look familiar.

Now this itself is not really dynamic. All we have provided is the possibility to make it dynamic. We don't have a setting screen where you can manipulate the bindings or a way to parse the data from a config file. We will actually create a settings screen later in the book in the GUI chapter. This would have been a way too big topic to cover in this chapter as well.

Why a player is not an entity

Well, the Player class can very much be considered as an entity, but in our case it is just a controller of entities. The class represents the player's input in the world of the game. In our gameplay, this entails only to manipulating a node in our scene graph.

Take this with a grain of salt. This is something that people do differently, we could have created a player entity and a player controller or have everything in the player entity.

Our opinion while writing was that this is a much cleaner way to do it and gives a nice separation of the external input signals from the player and the game logic. We felt much more comfortable working like this, instead of potentially having a giant blob inside the hierarchy of entities.

[ 111 ]

www.it-ebooks.info

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]