Я хотел бы создать простую структуру для бросания и ловли событий в игре. События могут быть такими вещами, как Collision
который (в зависимости от типа) может принимать несколько аргументов (обратите внимание, что каждый тип события может принимать другое количество аргументов, а не только два, как в примере).
Затем я хотел бы реализовать функции / классы / … для решения Collision
, основанный на полиморфизме. Этот пример должен проиллюстрировать проблему:
#include <iostream>
#include <vector>
class Entity {};
class Player: public Entity {};
class Bomb: public Entity {
public:
bool exploded;
};
class MineSweeper: public Entity {};// For now, I only included Collisions, but I eventually want to extend it to
// more types of Events too (base class Event, Collision is derived class)
void onCollision(Player* p, Bomb* b) {
if (! b->exploded) {
std::cout << "BOOM";
b->exploded = true;
}
}
void onCollision(Entity* e, Entity* f) {
std::cout << "Unhandled collision\n";
}
// Possibility for Collision between Minesweeper and Bomb laterclass Game {
public:
std::vector<Entity*> board; // some kind of linear board
Game() {
board = {new Player, new Bomb, new MineSweeper};
}
void main_loop() {
onCollision(board[0], board[1]); // player and bomb!
onCollision(board[1], board[2]);
}
};int main() {
Game g;
g.main_loop();
}
Обратите внимание, что я прекрасно понимаю, почему приведенный выше код не работает должным образом, я включил этот пример исключительно для того, чтобы лучше проиллюстрировать мою проблему.
Приведенный выше пример использует функции для событий, но я отлично справляюсь с классами или любым другим решением, которое можно поддерживать.
Я надеюсь, что ясно, что я хотел бы, чтобы C ++ решал, какой обработчик событий использовать, основываясь на типах аргументов (предположительно, во время выполнения).
Мой вопрос: как я могу сделать это в C ++? Пример будет оценен.
(не мой вопрос: исправьте мой код, пожалуйста)
user2864740 предоставил мне достаточно подсказок, чтобы найти решение самому. Многократная отправка была действительно недостающей частью.
Следующий код работает как задумано, используя dynamic_cast
отправлять правильно.
#include <iostream>
#include <vector>
class Entity {
virtual void please_make_this_polymorphic() {}
// although this function does nothing, it is needed to tell C++ that it
// needs to make Entity polymorphic (and thus needs to know about the type
// of derived classes at runtime).
};
class Player: public Entity {};
class Bomb: public Entity {
public:
bool exploded;
};
class MineSweeper: public Entity {};
// For now, I only included Collisions, but I eventually want to extend it to
// more types of Events too (base class Event, Collision is derived class)
void onCollision(Player* p, Bomb* b) {
if (!b->exploded) {
std::cout << "BOOM\n";
b->exploded = true;
}
}
void onCollision(Entity* e, Entity* f) {
std::cout << "Unhandled collision\n";
}
void dispatchCollision(Entity* e, Entity* f) {
Player* p = dynamic_cast<Player*>(e);
Bomb* b = dynamic_cast<Bomb*>(f);
if (p != nullptr && b != nullptr) {
onCollision(p, b); // player and bomb
} else {
onCollision(e, f); // default
}
}class Game {
public:
std::vector<Entity*> board; // some kind of linear board
Game() {
board = {new Player, new Bomb, new MineSweeper};
}
void main_loop() {
dispatchCollision(board[0], board[1]); // player and bomb
dispatchCollision(board[1], board[2]);
}
};int main() {
Game g;
g.main_loop();
}
Хотя это работает, я хотел бы указать на некоторые проблемы с этим кодом:
dispatchCollision
необходимо при добавлении новых коллизий.A
а также B
должно быть таким же, как столкновение между B
а также A
, но это еще не правильно обработано.Решение этих проблем не обязательно входит в сферу этого вопроса ИМХО.
Кроме того, приведенный пример должен работать так же хорошо для более чем 2 аргументов. (Многократная отправка, а не просто двойная отправка.)
Сначала вы должны решить, какая модель подписки на события вам нужна.
Это может быть механизм сигнала / слота, и вы можете найти множество библиотек:
https://code.google.com/p/cpp-events/ , http://sigslot.sourceforge.net/ и тому подобное.
Или это могут быть всплывающие / утопающие события, как в HTML DOM, когда событие распространяется по родительской / дочерней цепочке (от элемента источника события до его контейнеров).
Или даже другая схема.
Довольно просто создать все, что вам нужно, с помощью держателей std :: function в современном C ++.
Может быть, хорошей структурой для вашего случая может быть что-то вроде этого:
class Entity{
public:
virtual int getType() = 0;
};
enum EntityTypes {
ACTOR,
BOMB,
MINESWEEPER,
};
class Actor : public Entity{
public:
virtual int getType() {return int(ACTOR);}
void applyDamage() {
std::cout << "OUCH";
}
};
class Bomb : public Entity{
public:
Bomb() : exploded(false) {}
virtual int getType() {return int(BOMB);}
void explode() {
this->exploded = true;
}
bool isExploded() {
return this->exploded;
}
protected:
bool exploded;
};
class MineSweeper : public Entity{
public:
virtual int getType() {return int(MINESWEEPER);}
};
class CollisionSolver {
public:
virtual solve(Entity* entity0, Entity* entity1) = 0;
};
class ActorBombCollisionSolver : public CollisionSolver {
public:
virtual solve(Entity* entity0, Entity* entity1) {
Actor* actor;
Bomb* bomb;
if (entity0->getType() == ACTOR && entity1->getType() == BOMB) {
actor = static_cast<Actor*>(entity0);
bomb = static_cast<Bomb*>(entity1);
}else if (entity1->getType() == ACTOR && entity0->getType() == BOMB) {
actor = static_cast<Actor*>(entity1);
bomb = static_cast<Bomb*>(entity0);
}else {
//throw error;
}
if (!bomb->isExploded()) {
bomb->explode();
actor->applyDamage();
}
}
};
class CollisionDispatcher {
public:
CollisionDispatcher() {
CollisionSolver* actorBombCollisionSolver = new ActorBombCollisionSolver;
this->solvers[ACTOR][BOMB] = actorBombCollisionSolver;
this->solvers[BOMB][ACTOR] = actorBombCollisionSolver;
// this part wouldn't be necessary if you used smart pointers instead of raw... :)
this->solvers[BOMB][MINESWEEPER] = 0;
this->solvers[MINESWEEPER][BOMB] = 0;
this->solvers[ACTOR][MINESWEEPER] = 0;
this->solvers[MINESWEEPER][ACTOR] = 0;
}
void dispatchCollision(Entity* entity0, Entity* entity1) {
CollisionSolver* solver = this->solvers[entity0->getType()][entity1->getType()];
if (!solver) {
return;
}
solver->solve(entity0, entity1);
}
protected:
unordered_map<int, unordered_map<int, CollisionSolver*> > solvers;
};
class Game {
public:
std::vector<Entity*> board; // some kind of linear board
Game() : dispatcher(new CollisionDispatcher)
{
board = {new Player, new Bomb, new MineSweeper};
}
void main_loop() {
dispatcher->dispatchCollision(board[0], board[1]);
dispatcher->dispatchCollision(board[0], board[2]);
dispatcher->dispatchCollision(board[1], board[2]);
}
protected:
CollisionDispatcher* dispatcher;
};int main() {
Game g;
g.main_loop();
}
Таким образом, вы можете легко добавить новые решатели столкновений, просто определите класс и зарегистрируйте t в CollisionDispatcher конструктор.
Если вы используете умные указатели, вам не нужно устанавливать нули в записях карты, которые не зарегистрированы, но если вы используете необработанные указатели, вы должны установить их на ноль ИЛИ использовать unordered_map :: найти метод вместо того, чтобы просто схватить решатель, используя оператор []
Надеюсь, поможет!