到了玩游戏的时候啦!现在我将告诉你如何来玩这个游戏。你可以在此了解到 PluginManager 如何初始化、怪物怎样被创建、战斗如何开始等。我们从 main() 开始。
下面是 main() 函数的代码。直接跳过所有的 #include 语句,集中精力看看DummyInvokeService() 函数。这个函数作为由插件接收的 PF_PlatformServices 结构体的 invokeService。这里它实际不会做任何事情,但在正式程序中,它将是主系统为插件提供服务的主要角色。
#ifdef WIN32 #include "stdafx.h" #endif #include "plugin_framework/PluginManager.h" #include "plugin_framework/Path.h" #include "BattleManager.h" #include "static_plugin/static_plugin.h" #include <string> #include <iostream> using std::cout; using std::endl; apr_int32_t DummyInvokeService(const apr_byte_t * serviceName, void * serviceParams) { return 0; } #ifdef WIN32 int _tmain(int argc, _TCHAR* argv[]) #else int main(int argc, char * argv[]) #endif { cout << "Welcome to the great game!" << endl; if (argc != 2) { cout << "Usage: great_game <plugins dir>" << endl; return -1; } // Initialization ::apr_initialize(); PluginManager & pm = PluginManager::getInstance(); pm.getPlatformServices().invokeService = DummyInvokeService; pm.loadAll(Path::makeAbsolute(argv[1])); PluginManager::initializePlugin(StaticPlugin_InitPlugin); // Activate the battle manager BattleManager::getInstance().go(); ::apr_terminate(); return 0; }
main() 函数可以自动适应 Windows 和 UNIX 系统。游戏是可移植的。我测试过 Windows XP SP2、Vista、Mac OS X 10.4 (Tiger)、Mac OS X 10.5 (Leopard) 和 Kubuntu 7.10 (Gutsy Gibbon)。这是我认为的一些比较重要的操作系统。对于另外的系统或者这些系统的不同版本,我们的游戏应该也可以正常运行。
main() 函数中有一个检测,用于查看用户是否通过命令行参数传入了插件目录。APR 库在一开始就被初始化,同样也获得了 PluginManager 实例。这个类是一个单例,在应用程序结束时销毁。下一步是将 DummyInvokeService 赋值给平台服务结构体。一旦 invokeService 准备好,插件就可以初始化。首先,插件目录以 argv[1] 的形式传入,经过处理后,其中的所有动态插件都被加载进来,静态插件则显式初始化。这并不是一个令人愉快的方法,但是在 Windows 下,我们找不到更合适的替代方案。一旦所有插件都初始化完成,BattleManager 获得控制权。最后,APR 库清理。
我们的解决方案很直接:检查命令行参数、初始化全局资源、加载插件、向应用程序业务逻辑移交控制权,最后清理全局资源。
BattleManager 是游戏的核心。下面是完整的 go() 函数。它首先导出所有 PluginManager 已注册的怪物类型。
void BattleManager::go() { // 获取所有怪物类型 PluginManager & pm = PluginManager::getInstance(); const PluginManager::RegistrationMap & rm = pm.getRegistrationMap(); for (PluginManager::RegistrationMap::const_iterator i = rm.begin(); i != rm.end(); ++i) { monsterTypes_.push_back(i->first); } // 显示所有怪物 for (MonsterTypeVec::iterator i = monsterTypes_.begin(); i != monsterTypes_.end(); ++i) { std::string m = *i; std::cout << m.c_str() << std::endl; } // 将主角添加进来(随后添加那些怪物) ActorInfo ai, heroInfo; hero_.getInitialInfo(&heroInfo); // 不要保留主角的 IActor *,因为它需要特殊处理 actors_.insert(std::make_pair((IActor *)0, heroInfo)); heroFaction_.push_back(&actors_[0]); // 随机初始化某些怪物 for (apr_int32_t i = 0; i < MONSTER_COUNT; ++i) { IActor * monster = createRandomMonster(rm); monster->getInitialInfo(&ai); ai.id = i+1; // 主角的 id 为 0 actors_.insert(std::make_pair(monster, ai)); enemyFaction_.push_back(&actors_[monster]); } while (!gameOver_) { playTurn(); } heroInfo = actors_[0]; if (heroInfo.health > 0) std::cout << "Hero is victorious!!!" << std::endl; else std::cout << "Hero is dead :-(" << std::endl; }
这是一个动态的步骤。BattleManager 并不知道有什么样的怪物——也不需要关心。它甚至不知道FidgetyPhantom 是由静态插件提供的。为清楚起见,我们将所有怪物类型输出到控制台(也为了能够知道是不是所有怪物都正确注册)。然后,它将 Hero(这是需要特殊对待的)添加到 actor 列表。BattleManager 需要知道 Hero,因为 Hero 的命运关系着整个游戏的命运,Hero 如果阵亡,游戏也就结束。怪物使用 createRandomMonster() 函数随机创建。最后,我们开始主循环:“如果游戏没有结束,开始下一轮”。当游戏结束时,需要显示总结信息:主角胜利还是失败。正如你看到的那样,我们的游戏实在是太简单了。
下面是 createRandomMonster() 函数的代码。它使用索引随机选择怪物,基于已注册怪物的总数,使用 ActorFactory::createActor() 函数创建怪物,其参数为怪物类型。
IActor * BattleManager::createRandomMonster( const PluginManager::RegistrationMap & rm) { // Select monster type apr_size_t index = ::rand() % monsterTypes_.size(); const std::string & key = monsterTypes_[index]; const PF_RegisterParams & rp = rm.find(key)->second; // Create it IActor * monster = ActorFactory::createActor(key); return monster; }
ActorFactory 是应用程序相关的对象适配器,继承自由插件框架提供的通用的 ObjectAdapter。从 BattleManager 的角度来看,所有创建的怪物都实现了 IActor 接口。事实上,有些是适配的 C 对象,有些则是从另外的插件获取的。
这是一个回合策略制游戏,那么,在主循环中,一个回合代表着什么?下面就是 playTurn() 函数的代码,这就是答案。
void BattleManager::playTurn() { // 遍历所有 actor(从主角开始) // 为每一个 actor 准备回合信息(友军和敌人) Turn t; ActorInfo & ai = actors_[(IActor *)0]; t.self = &ai; std::copy(heroFaction_.begin(), heroFaction_.end(), std::back_inserter(t.friends.vec)); std::copy(enemyFaction_.begin(), enemyFaction_.end(), std::back_inserter(t.foes.vec)); hero_.play(&t); ActorInfo * p = NULL; ActorMap::iterator it; for (it = actors_.begin(); it != actors_.end(); ++it) { if (!it->first || isDead(&it->second)) continue; t.self = &(it->second); std::copy(heroFaction_.begin(), heroFaction_.end(), std::back_inserter(t.foes.vec)); std::copy(enemyFaction_.begin(), enemyFaction_.end(), std::back_inserter(t.friends.vec)); it->first->play(&t); } // 清理已死亡的敌人 Faction::iterator last = std::remove_if(enemyFaction_.begin(), enemyFaction_.end(), isDead); while (last != enemyFaction_.end()) { enemyFaction_.erase(last++); } // 检查游戏是否结束(主角死亡或者所有敌人死亡) if (isDead(&actors_[(IActor *)0]) || enemyFaction_.empty()) { gameOver_ = true; return; } }
BattleManager 开始的时候在栈上创建一个 Turn 对象。每一个存活的 actor 都要获取其带有适当信息的 Turn 对象,并在其基础上做出一定的动作。首先是 Hero。BattleManager 调用其 play()函数,将回合对象传递给它。Hero 进行移动和攻击。BattleManager 知道这所有的动作,因为保存动作信息的数据结构 ActorInfo 就是 BattleManager 管理的。一旦主角完成,其它 actor 则获得各自的机会。当所有 actor 都完成各自需要的动作之后,就需要将死亡的对象从战场移除。这由标准算法 std::remove_if 和 isDead() 谓词决定。该谓词需要检查 actor 的血量是否为 0。回合结束之前,BattleManager 检查 Hero 是不是死亡,或者所有的怪物是不是都已经死亡。如果这些条件都不满足,游戏将继续。下图是我们的游戏截图:
这就是我们的简单的游戏了。