背景

在游戏开发过程中,经常遇到这样一个问题。现在我们有几个功能系统:任务系统,成就系统等。这些系统都需要处理玩家击杀怪物的事件。通常的做法就是在击杀怪物的处理函数中调用这些功能系统的对应接口,代码如下:

1
2
3
4
5
6
7
// Battle.cpp
void KillMonster(Player* player, Monster* monster)
{
    ...
    player->GetTaskMgr().OnKillMonster(monster);
    player->GetAchievementMgr().OnKillMonster(monster);
}

然后在对应接口中做各自的计数,状态变更等等等等。

然后,来了一个新的需求。策划想要做一个活动,也需要处理这个事件。那么除了活动代码中需要增加接口OnKillMonsterBattle.cpp还需要做以下修改:

1
2
3
4
5
6
7
8
// Battle.cpp
void KillMonster(Player* player, Monster* monster)
{
    ...
    player->GetTaskMgr().OnKillMonster(monster);
    player->GetAchievementMgr().OnKillMonster(monster);
    player->GetActivityMgr().OnKillMonster(monster);
}

代码耦合度太高!

实现EventManager

实际上,我们可以用事件(例如上面的KillMonster)作关联,对应这个事件的处理函数(有可能有多个),即event->callbacks。因而我们可以实现一个EventManager,用来管理这种对应关系。实现采用C++11

event可以用事件名称(调用typeid(event).name()获取)作为标识,callback可以使用C++11中的function来表示,function类型为function<void (EventType&)>

这里有一个问题,每种event对应的callback类型各不相同,无法存入同一个容器中。这时,我们需要一种类型擦除的技术。可以使用boost::any。这里我自己实现了一个Any类。实现参考祁 宇的《深入应用C++11:代码优化与工程级应用》一书。

这时,就可以很简单地实现EventManager类了。使用C++11中的unordered_multimap来存储这种对应关系。

这里有一种设计模式:观察者模式

具体实现代码见github

使用EventManager

首先,我们需要在Event.h中定义对应的事件:

1
2
3
4
struct KillMonsterEvent
{
    Monster* monster;
};

然后在KillMonster处理中发出这个事件:

1
2
3
4
5
6
7
// Battle.cpp
void KillMonster(Player* player, Monster* monster)
{
    ...
    KillMonsterEvent event{monster};
    FIRE_EVENT(event);
}

这个函数中的代码编写完成之后,今后不管什么功能需要这个事件,都不需要再来修改这个函数了。新增系统的处理:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
// ActivityMgr.cpp
class ActivityMgr
{
public:
    ActivityMgr()
    {
        REGISTER_EVENT(&ActivityMgr::OnKillMonster, this);
    }

    void OnKillMonster(Monster* monster)
    {
        // ...
    }
};

功能与功能之间解耦了。随着时间的推移,积累的事件越来越多,编写程序越来越方便。

现在只有在添加新的事件时,需要修改对应位置的代码。

详细用法查看test.cpp文件。

实现细节

  • 非线程安全。
  • 多个相同事件的处理函数调用顺序不定。
  • 实现了一个简单的单例Singleton
  • 定义注册与发出事件的宏REGISTER_EVENTFIRE_EVENT,方便使用。

要求

  • 需要支持C++11的编译器。编译器支持情况可以看这里