The online book Game Programming Patterns briefly describes the use of the command pattern to handle input in a game. I've attempted to write my own "one size fits all" input handling system based on the command pattern, as shown below.
- Is this a correct implementation and use of the command pattern?
- Would it be "better" (more efficient, more maintainable) to use another pattern (e.g: observer pattern)?
- How do I adapt it to handle input from multiple controllers?
input_handler.hpp
#ifndef INPUT_HANDLER_HPP
#define INPUT_HANDLER_HPP
#include <map>
#include <vector>
#include <SDL2/SDL.h>
#include "character.hpp"
#include "input_constants.hpp"
class Command
{
public:
virtual ~Command() {}
virtual void execute(Character *character) = 0;
virtual InputType get_input_type() = 0;
};
class InputHandler
{
private:
// Pointers to all commands
Command *move_up;
Command *move_down;
Command *move_left;
Command *move_right;
Command *jump;
std::map <int, Command*> commands;
// Gameplay context
std::map <int, State> state_map;
std::map <int, Action> action_map;
bool input_mapping();
void dispatcher(std::vector<Command*> &command_queue);
void keydown(SDL_Event &event);
void keyup(SDL_Event &event);
bool is_held(int key);
bool was_pressed(int key);
public:
InputHandler();
~InputHandler();
bool fill(std::vector<Command*> &command_queue);
void configure(int key, Command *command);
};
class MoveUp : public Command
{
public:
void execute(Character *character) { character->move_up(); }
InputType get_input_type() { return STATE; }
};
class MoveLeft : public Command
{
public:
void execute(Character *character) { character->move_left(); }
InputType get_input_type() { return STATE; }
};
class MoveRight : public Command
{
public:
void execute(Character *character) { character->move_right(); }
InputType get_input_type() { return STATE; }
};
class MoveDown : public Command
{
public:
void execute(Character *character) { character->move_down(); }
InputType get_input_type() { return STATE; }
};
class Jump : public Command
{
public:
void execute(Character *character) { character->jump(); }
InputType get_input_type() { return ACTION; }
};
#endif // INPUT_HANDLER_HPP
input_handler.cpp
#include "input_handler.hpp"
InputHandler::InputHandler()
{
// Create pointers to all commands (to apply the flyweight pattern)
move_up = new MoveUp();
move_down = new MoveDown();
move_left = new MoveLeft();
move_right = new MoveRight();
jump = new Jump();
// Player 1
commands[SDLK_UP] = move_up;
commands[SDLK_LEFT] = move_left;
commands[SDLK_DOWN] = move_down;
commands[SDLK_RIGHT] = move_right;
commands[SDLK_SPACE] = jump;
// Player 2
//commands[SDLK_w] = move_up;
//commands[SDLK_a] = move_left;
//commands[SDLK_s] = move_down;
//commands[SDLK_d] = move_right;
//commands[SDLK_LSHIFT] = jump;
}
void InputHandler::configure(int key, Command *command)
{
commands[key] = command; // key points to newly assigned command
}
bool InputHandler::fill(std::vector<Command*> &command_queue)
{
bool exit = input_mapping(); // converts raw input datum to an action and/or state
if (exit) return true;
else {
dispatcher(command_queue); // fills command queue
action_map.clear(); // clears key presses
return false;
}
}
bool InputHandler::input_mapping()
{
SDL_Event event;
while (SDL_PollEvent(&event))
if (event.type == SDL_QUIT) return true;
else if (event.type == SDL_KEYDOWN) {
if (event.key.keysym.sym == SDLK_ESCAPE) return true;
keydown(event);
}
else if (event.type == SDL_KEYUP)
keyup(event);
return false;
}
void InputHandler::dispatcher(std::vector<Command*> &command_queue)
{
std::map<int, Command*>::iterator iter;
for (iter = commands.begin(); iter != commands.end(); iter++) {
if (is_held(iter->first) && iter->second->get_input_type() == STATE)
command_queue.push_back(iter->second);
else if (was_pressed(iter->first) && iter->second->get_input_type() == ACTION)
command_queue.push_back(iter->second);
}
}
void InputHandler::keydown(SDL_Event &event)
{
if (state_map[event.key.keysym.sym] == RELEASED)
action_map[event.key.keysym.sym] = EXECUTE;
state_map[event.key.keysym.sym] = PRESSED;
}
void InputHandler::keyup(SDL_Event &event)
{
state_map[event.key.keysym.sym] = RELEASED;
}
bool InputHandler::is_held(int key)
{
return state_map[key];
}
bool InputHandler::was_pressed(int key)
{
return action_map[key];
}
InputHandler::~InputHandler()
{
// Delete all command pointers
std::map<int, Command*>::iterator iter;
for (iter = commands.begin(); iter != commands.end(); iter++)
delete iter->second;
}
input_constants.hpp
#ifndef INPUT_CONSTANTS_HPP
#define INPUT_CONSTANTS_HPP
enum InputType
{
ACTION,
STATE,
RANGE
};
enum Action
{
EXECUTE = true,
STOP = false
};
enum State
{
PRESSED = true,
RELEASED = false
};
#endif // INPUT_CONSTANTS_HPP
game.hpp (only relevant lines)
#include "input_handler.hpp"
class Game
{
private:
bool exit;
InputHandler *input_handler;
Character *character;
void update();
public:
void execute();
};
game.cpp (only relevant lines)
#include "game.hpp"
Game::Game(int screen_width, int screen_height)
{
exit = false;
// Initialize input handler
input_handler = new InputHandler();
// Create character
character = new Character("Rouge", 100, 300);
// Command queue
std::vector<Command*> command_queue;
}
void Game::execute()
{
while(!exit) {
// Handle input
exit = input_handler->fill(command_queue);
update ();
}
}
void Game::update()
{
// Update character state
while (!command_queue.empty()) {
command_queue.back()->execute(character);
command_queue.pop_back();
}
}