A command is an object-oriented replacement for callbacks.
enum class Button { X, Y, A, B };
class InputHandler {};
void InputHandler::handleInput() {
if (isPressed(BUTTON::X)) jump();
else if (isPressed(BUTTON::Y)) fireGun();
else if (isPressed(BUTTON::A)) swapWeapon();
else if (isPressed(BUTTON::B)) lurchIneffectively();
}
Most games allow their buttons to be re-mapped, so this won’t work. We’ll need to find a way to swap out the action that occurs when a button is is pressed.
Define a base class that allows for this:
class Command {
public:
virtual ~Command() {}
virtual void execute() = 0;
};
Create subclasses for each action:
class JumpCommand : public Command {
public:
void execute() override { jump(); }
};
class FireCommand : public Command {
public:
void execute() override { fireGun(); }
};
In the input handler, store a pointer to a command for each button:
class InputHandler {
public:
void handleInput();
void bindToX(Command& c) { buttonX_ = c; }
void bindToY(Command& c) { buttonY_ = c; }
void bindToA(Command& c) { buttonA_ = c; }
void bindToB(Command& c) { buttonB_ = c; }
private:
* buttonX_;
Command* buttonY_;
Command* buttonA_;
Command* buttonB_;
Command};
Now the input handler can delegate to the command.
void InputHandler::handleInput() {
if (isPressed(BUTTON::X)) buttonX_->execute();
else if (isPressed(BUTTON::Y)) buttonY_->execute();
else if (isPressed(BUTTON::A)) buttonA_->execute();
else if (isPressed(BUTTON::B)) buttonB_->execute();
}
class Command {
public:
virtual ~Command() {}
void execute(GameActor& actor) = 0;
};
Next, we edit the comamnds slightly to act on an actor.
class JumpCommand : public Command {
public:
void execute(GameActor& actor) override {
.jump();
actor}
};
* InputHandler::handleInput() {
Commandif (isPressed(Button::X)) return buttonX_;
if (isPressed(Button::Y)) return buttonY_;
if (isPressed(Button::A)) return buttonA_;
if (isPressed(Button::B)) return buttonB_;
return nullptr;
}
Then, in the main loop:
* command = inputHandler.handleInput();
Commandif (command) {
->execute(actor);
command}
We’ve now made it so the Commands are decoupled from the Actor. As long as it can receive the commands, it can execute them.
Let’s try this with a move unit command.
class MoveUnitCommand : public Command {
public:
(Unit* unit, int x, int y) : unit_(unit), x_(x), y_(y) {}
MoveUnitCommandvoid execute() { unit_->moveTo(x_, y_); }
private:
* unit_;
Unitint x_;
int y_;
};
This command allows us to move a unit that’s passed into it.
Now we can handle its commands like so:
* handleInput() {
Command* unit = getSelectedUnit();
Unit
if (isPressed(Button::UP))
return new MoveUnitCommand(unit, unit->x(), unit->y() - 1);
if (isPressed(Button::DOWN))
return new MoveUnitCommand(unit, unit->x(), unit->y() + 1);
if (isPressed(Button::LEFT))
return new MoveUnitCommand(unit, unit->x() - 1, unit->y());
if (isPressed(Button::RIGHT))
return new MoveUnitCommand(unit, unit->x() + 1, unit->y());
return nullptr;
}
Let’s make commands undoable:
class Command {
public:
virtual ~Command() {}
void execute() = 0;
void undo() = 0;
};
Let’s add that undo method to the MoveUnitCommand
.
class MoveUnitCommand : public Command {
public:
(Unit* unit, int x, int y) : unit_(unit), xBefore_(0), yBefore_(0), x_(x), y_(y) {}
MoveUnitCommandvoid execute() override {
xBefore_ = unit_->x();
yBefore_ = unit_->y();
unit_->moveTo(x_, y_);
}
void undo() override { unit_->moveTo(xBefore_, yBefore_); }
private:
* unit_;
Unitint xBefore_;
int yBefore_;
int x_;
int y_;
};
Now, each unit remembers the last command it executed. If you want to introduce undo and redo for more commands, keep a list of commands and a reference to the current one. When a player executes a commad, we append to the list and point current to it.
Next: flyweight