开始游戏!
到了玩游戏的时候啦!现在我将告诉你如何来玩这个游戏。你可以在此了解到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是不是死亡,或者所有的怪物是不是都已经死亡。如果这些条件都不满足,游戏将继续。下图是我们的游戏截图:
这就是我们的简单的游戏了。
