这是命令模式的正确实现和使用吗?
使用另一种模式(例如观察者模式)会“更好”(更高效,更可维护)吗?
我如何使其适应来自多个控制器的输入?
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(仅相关行)
#include "input_handler.hpp"
class Game
{
private:
bool exit;
InputHandler *input_handler;
Character *character;
void update();
public:
void execute();
};
game.cpp(仅相关行)
#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();
}
}
#1 楼
这是命令模式的正确实现和使用吗?
是的,我认为它足够好。
性能明智的做法是,很难确定其他解决方案是否会更快。
这个解决方案很容易维护且可扩展,但是在这种情况下我有偏见,因为我真的很欣赏Command模式。
如果要摆脱
您现在正在使用的
std::map
。您可以很好地使用数组,因为您的映射键只是整数常量。如果要确保枚举常量常量是连续的,则可以替换为:
std::map<int, Command*> commands;
std::map<int, State> state_map;
std::map<int, Action> action_map;
用普通数组:
Command* commands[MAX_COMMAND_INDEX];
State state_map[MAX_STATE_INDEX];
Action action_map[MAX_ACTION_INDEX];
或者更好,使用C ++ 11数组:
std::array<Command*, MAX_ACTION_INDEX> commands;
std::array<State, MAX_ACTION_INDEX> state_map;
std::array<Action, MAX_ACTION_INDEX> action_map;
常规改进:
您可以使用智能指针来实施更安全的对象所有权策略。
C ++ 11
std::shared_ptr
将是一个不错的选择:typedef std::shared_ptr<Command> CommandPtr;
这确保了
Command
传递时永远不会被破坏,从而免除了您在InputHandler
的析构函数中手动删除它们的负担。实际上,对于大多数(如果不是全部)对象,应该使用智能指针,其中包括
Character
指针:typedef std::shared_ptr<Character> CharacterPtr;
因此,在
virtual void execute(CharacterPtr character)
的Command
中,即使执行了character
指针,该指针始终有效。原始指针的引用在其他线程中丢失。更好的命名方式:
公共功能:
void configure(int key, Command *command);
InputHandler
的作用是将给定键绑定到命令,因此,将使用更好的名称进行绑定:
void bind(int key, CommandPtr command);
fill也是一个函数的模糊名称,该函数负责生成游戏框架的
输入命令:
bool fill(std::vector<Command*> &command_queue);
我建议将其重命名为:
bool generate_input(std::vector<CommandPtr> &command_queue);
,或更明确的名称:
bool generate_input_commands(std::vector<CommandPtr> &command_queue);
不要害怕使用长名。您留给歧义的余地越少越好。
bool input_mapping();
这个名字也有些可怜。请使用您在其调用站点上所写的推荐内容
,并尝试使用该功能的描述性名称:
// converts raw input datum to an action and/or state
您可以将其重命名为:
bool convert_inputs_to_actions();
bool map_inputs_to_actions();
bool do_input_to_action_mapping();
列表会继续。
void dispatcher(std::vector<Command*> &command_queue);
命令队列。您选择的名称适合于类型
(例如Dispatcher类),但不适用于函数。
您可以将其重命名为:
void dispatch_commands(std::vector<CommandPtr> &command_queue);
或更简单地说:
void fill_command_queue(std::vector<CommandPtr> &command_queue);
请注意您的控制流布局样式:
可以忽略此内容个人喜好,但我认为值得一提。
我建议您注意将return语句放在if子句同一行的样式。例如,在此代码块中:
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);
我发现样式和花括号的混合很难读。
如果在眼睛上看起来容易得多您应将其重新格式化为:
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);
,但如果在所有语句上添加统一的支撑,甚至会更好,更安全,甚至在单行语句中也是如此:
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);
}
}
使用统一的支撑,如果您需要在if结果中添加多个命令
,则没有做类似以下愚蠢操作的风险:
if (x == 42)
do_something();
do_some_other_thing();
正确的缩进会让您以为这两行都属于第一个if的结果
。如果程序员从一开始就做好了准备,那么他将不会受到此类错误的影响。
如何使其适应来自多个控制器的输入?
您已经将一些特定于游戏的逻辑硬编码到
InputHandler
的构造函数中。最直接,最可能的最佳解决方法是子类化。
您可以定义一个或多个必须实现的虚拟方法。由您的代码的客户端:
class InputHandler
{
// all the previous stuff...
protected:
// Classes extending InputHandler will define their
// own mappings inside this method.
virtual void setup_input_mappings() = 0;
};
,这样,对于每个您希望支持的硬件/平台,您就可以拥有一个
InputHandler
类型:class InputHandlerXBOXController : public InputHandler
{
void setup_input_mappings()
{
commands[XB_Y] = move_up;
commands[XB_X] = move_left;
commands[XB_A] = move_down;
commands[XB_B] = move_right;
commands[XB_L2] = jump;
}
};
例如,您可以使用模板方法的同一概念来隔离
InputHandler
与SDL的其余部分。#2 楼
我认为您没有以正确的方式实现命令模式。命令模式背后的想法是封装从
receiver
开始执行receiver
方法的逻辑。一个很好的例子是model
类。您不需要model
更改自己的状态。因此,实现此目的的一种好方法是将model
的所有设置方法封装在命令中。您的命令确实做到了这一点,但是它也是如此:
virtual InputType get_input_type() = 0;
这绝对不属于那里。
您正在使用命令状态来确定
client
(InputHandler)行为。您应该为此使用模型。考虑更好的初始化命令的方法,并考虑将
command_queue
也移至模型中,因此您的client
或controller
只会做逻辑,不会处理应用程序指出,即使它不是business logic
。
评论
\ $ \ begingroup \ $
对我来说,与CommandPtr CommandPointer ThisIsAPointerToAnObjectOfTypeCommand和自定义名称下定义的其他任何类型相比,Command *更清楚地表明它是指向Command类型的对象的指针。
\ $ \ endgroup \ $
– Nikos
18-09-15在10:53
\ $ \ begingroup \ $
使用C ++ 11和使用typedef?真?
\ $ \ endgroup \ $
–津安
18-10-25在8:11