Игровой движок на основе событий, основанный на полиморфизме сущностей

Я хотел бы создать простую структуру для бросания и ловли событий в игре. События могут быть такими вещами, как 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 ++? Пример будет оценен.

(не мой вопрос: исправьте мой код, пожалуйста)

0

Решение

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 необходимо при добавлении новых коллизий.
  • В настоящее время диспетчер использует простой вид системы, основанной на правилах. (Соответствует ли это правилу 1? Как насчет правила 2? …) При добавлении множества различных функций, которые необходимо распределить, это может повлиять на производительность.
  • Столкновение между A а также B должно быть таким же, как столкновение между B а также A, но это еще не правильно обработано.

Решение этих проблем не обязательно входит в сферу этого вопроса ИМХО.

Кроме того, приведенный пример должен работать так же хорошо для более чем 2 аргументов. (Многократная отправка, а не просто двойная отправка.)

1

Другие решения

Сначала вы должны решить, какая модель подписки на события вам нужна.
Это может быть механизм сигнала / слота, и вы можете найти множество библиотек:
https://code.google.com/p/cpp-events/ , http://sigslot.sourceforge.net/ и тому подобное.
Или это могут быть всплывающие / утопающие события, как в HTML DOM, когда событие распространяется по родительской / дочерней цепочке (от элемента источника события до его контейнеров).
Или даже другая схема.

Довольно просто создать все, что вам нужно, с помощью держателей std :: function в современном C ++.

0

Может быть, хорошей структурой для вашего случая может быть что-то вроде этого:

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 :: найти метод вместо того, чтобы просто схватить решатель, используя оператор []

Надеюсь, поможет!

0
По вопросам рекламы [email protected]