2015-04-11 2 views
0

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


class 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++? Пример был бы оценен.

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

+2

Похоже, вы ищете форму «множественной отправки». – user2864740

+0

В чем смысл 'b-> exploded = true'? –

+0

@NeilKirk, ах забыл '!' там. – evertheylen

ответ

1

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, но это пока неправильно обрабатывается.

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

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

0

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

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

+0

Я думаю, что механизм сигнала/слота подойдет мне лучше всего, но я до сих пор не знаю, как сделать «отправку» полиморфизма. – evertheylen

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(); 
} 

Таким образом, вы можете легко добавлять новые решатели столкновения, просто определить класс, и register t в CollisionDispatcher конструктор.

Если вы используете смарт-указатели вам не нужно будет устанавливать нули в записи карты не зарегистрированы, но если вы используете сырые указатели вы должны установить их на ноль или использовать unordered_map :: найти метод вместо того, чтобы просто захват решатель с использованием оператор []

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

+0

Мне не нравятся перечисления. Похоже, вы изобретаете систему самого высокого уровня самого C++. – evertheylen

+0

Использование unordered_maps решает проблему 2 моего ответа (что приятно), но для этого требуется трюк с перечислениями. Кроме того, этот трюк перестанет работать, когда ваше дерево наследования «выше». – evertheylen

+0

Если вам не нравятся перечисления, но вам нравится структура, вы всегда можете использовать std :: type_index (typeid (объект класса ИЛИ)) вместо перечислений, мне лично они нравятся, потому что IMO они делают код более понятным для понимания , Но, конечно, если вы используете type_index (typeid()) вместо перечислений, вы исправите проблему, о которой вы упомянули, когда вы сказали: «Трюк перестанет работать, когда ваше дерево наследования« выше ». –

Смежные вопросы