Search code examples
c++game-engineconceptual

Event-based Game engine based on polymorphism of Entities


I would like to create a simple framework for throwing and catching events in a game. Events could be things like a Collision which (according to the type) can take several arguments (note that every Event type may take another amount of arguments, not just two as in the example).

I would then like to implement functions/classes/... to deal with a Collision, based on polymorphism. This example should illustrate the problem:

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

Note that I understand perfectly well why the above code doesn't work as intended, I included this example solely to illustrate my problem better.

The above example uses functions for the events, but I'm perfectly fine with classes or any other solution that is maintainable.

I hope it is clear that I would like C++ to decide which event handler to use based on the types of the arguments (presumably at runtime).

My question: How can I do this in C++? An example would be appreciated.

(not my question: fix my code please)


Solution

  • user2864740 provided enough clues for me to find a solution myself. Multiple dispatch was indeed the missing piece.

    The following code works as intended, making use of dynamic_cast to dispatch correctly.

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

    Although it works, I'd like to point out some problems with this code:

    • Manual editing of dispatchCollision needed when adding new Collisions.
    • Currently, the dispatcher using a simple kind of rule-based system. (Does it fit rule 1? What about rule 2? ...) When adding loads of different functions it needs to dispatch, that may have an impact on the performance.
    • A collision between A and B should be the same as a collision between B and A, but that isn't properly handled yet.

    Solving these problems is not necessarily in the scope of this question IMHO.

    Also, the example given should work just as well for more than 2 arguments. (Multiple dispatch, not just double dispatch.)