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

Event-based Game engine based on polymorphism of Entities

我想创建一个简单的框架来在游戏中抛出和捕捉事件。事件可以是 Collision 之类的东西,它(根据类型)可以接受多个参数(请注意,每个事件类型都可以接受其他数量的参数,而不是示例中的两个)。

然后我想基于多态性实现 functions/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++ 中执行此操作?一个示例将不胜感激。

(不是我的问题:请修正我的代码)

你应该首先决定你需要什么样的事件订阅模型。 它可能是 signal/slot 机制,你可以找到很多库: https://code.google.com/p/cpp-events/ , http://sigslot.sourceforge.net/ 之类的。 或者它可能是 bubbling/sinking 事件,如 HTML DOM 事件在 parent/child 链上传播(从事件源元素到其容器)。 甚至其他模式。

在现代 C++ 中使用 std::function holders 可以很容易地创建你需要的任何东西。

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 个以上的参数应该同样有效。 (多重分派,不仅仅是双重分派。)

对于您的案例来说,一个好的结构可能是这样的:

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

这样你就可以轻松添加新的碰撞解决器,只需定义 class,并在 CollisionDispatcher 构造函数中注册 t。

如果您使用智能指针,则无需在未注册的映射条目中设置零,但如果您使用原始指针,则必须将它们设置为零或使用 unordered_map ::find 方法,而不是仅仅使用 运算符 []

获取求解器

希望对您有所帮助!