基于事件的游戏引擎基于实体的多态性

时间:2015-04-11 22:01:46

标签: c++ game-engine conceptual

我想创建一个简单的框架来投掷和捕捉游戏中的事件。事件可能是Collision之类的东西(根据类型)可以带几个参数(请注意,每个事件类型可能需要另外一些参数,而不仅仅是示例中的两个参数)。

然后我想基于多态实现函数/ classes / ...来处理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 ++中执行此操作?我们将不胜感激。

我的问题:请修改我的代码)

3 个答案:

答案 0 :(得分: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怎么办?...)当添加不同功能的负载时,它需要调度,这可能会对性能产生影响。
  • AB之间的碰撞应与BA之间的碰撞相同,但尚未正确处理。

解决这些问题不一定在这个问题的范围内恕我直言。

此外,给出的示例也应该适用于超过2个参数。 (多次发送,而不仅仅是双重发送。)

答案 1 :(得分:0)

您应首先确定所需的事件订阅模型。 它可能是信号/插槽机制,你可以找到很多库: https://code.google.com/p/cpp-events/http://sigslot.sourceforge.net/等。 或者当事件在父/子链(从事件源元素到其容器)上传播时,它可能是像HTML DOM中那样的冒泡/下沉事件。 甚至是其他架构。

使用现代C ++中的std :: function holder创建所需的内容非常容易。

答案 2 :(得分: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();
}

通过这种方式,您可以轻松添加新的碰撞求解器,只需定义类,并在 CollisionDispatcher 构造函数中注册t。

如果使用智能指针,则不需要在未注册的地图条目中设置零,但如果使用原始指针,则必须将它们设置为零或使用 unordered_map :: find 方法而不是仅使用 operator []

抓取解算器

希望它有所帮助!