【译】另外一个星际迷航游戏(复古的那一款)
原文地址:http://www.codeproject.com/Articles/38454/Calcoolation-A-Math-Puzzle-Board-Game
版权:作者版权声明为http://www.codeproject.com/info/cpol10.aspx,可以翻译
一、简介
在读了MichaelBirken的文章以后,你必须承认这再次证明只有一个要素决定了是否是一个好游戏:游戏性。不是表现,不是声音。作为复古游戏的狂热粉丝,我想我为什么不加入到这个游戏中来呢。
就像James Curran说的那样,Michael Birken的文章把游戏的内容从上到下都覆盖了。所以我更加关注构建一个2D游戏的差异性上,这看起来就像是一个控制台游戏
二、背景
第一个要解决的问题是我要用什么技术来做这个游戏。DirectX还是OpenGL?第二个问题更加重要一点,既然这是我图形方面的第一个项目,我能掌握我选择的技术并且按时写完需要的代码么?
在网上搜索了一会以后,我偶然发现下列网站:IrrLicht。这是一个免费的图形引擎同支持DirectX和 OpenGL,这解决了我的第一个问题。这个引擎同时支持2D和3D,这很完美。我将从2D开始,然后慢慢过度到3D而无需换引擎。
不使用控制台带来最大的不同是我们输入的方式。一个控制台游戏是输入驱动的。分析输入以后,我们执行特定的逻辑然后重新绘制屏幕。一个Windows程序是时间驱动的程序。这意味着我需要分析事件创建我自己的输入来驱动游戏逻辑。并且,因为它是一个窗口,我需要更新屏幕并在等待输入的时候持续绘制屏幕的内容。
三、写代码之前
如果你需要使用并编译这段代码,你还需要IrrLicht SDK。它包括你所有需要启动游戏的内容,包括一个构建好的DLL和lib。
在磁盘上放好你的SDK以后,你需要把位置信息加到VS的头文件和库文件里面。打开工具栏的选项菜单,选择Project andSolution然后选择子选项VC++ directories.
四、使用代码
你已经准备使用和变异代码了。我的工程会从一个控制台项目模板开始。它的有时是用控制台作为输出和跟踪窗口,使得更加容易调试。在Main函数里面,我创建了一个Game对象,这个对象负责启动、运行和结束游戏。
int _tmain(int argc, _TCHAR* argv[])
{
Game* pTheGame =Game::getInstance();
if(pTheGame)
{
pTheGame->setupGame();
pTheGame->createData();
pTheGame->runGame();
pTheGame->endGame();
}
return 0;
}
setupGame()方法创建了游戏窗口和游戏对象。游戏对象负责执行游戏逻辑。我把游戏逻辑放在Act对象里。Act对象交由一个Director对象控制。Director对象允许你切换Act对象。共有三种类型的Act对象:IntroAct, PlayAct和 CreditsAct。Intro负责绘制星际迷航Logo并展现游戏的目标。Play负责具体的游戏。当游戏结束的时候会调用Credits,显示游戏的结果。最后一个需要的对象是InputManager。它负责把IrrLicht的事件转给我们需要的事件,主要是KeyPress事件。
为了把KeyPress事件发给act对象,我使用了简单的回调函数,但是我喜欢C#里面的delegate概念。在C++里面可以用Functor实现。Boost库提供了几个类来做这个事情,但是我不想用Boost,所以我自定义了一个Functor。
struct FKeyPressed
{
virtual ~FKeyPressed() {};
virtual bool operator()(EKEY_CODE) = 0;
};
template class KeyPressed : public FKeyPressed
{
public:
typedef bool (ACTOR::*FunctionType)(EKEY_CODE);
public:
KeyPressed(ACTOR*pActor, FunctionType pFunctor)
{
m_pActor = pActor;
m_pFunctor =pFunctor;
}
virtual ~KeyPressed() {};
virtual bool operator()(EKEY_CODE keyCode)
{
return (m_pActor->*m_pFunctor)(keyCode);
}
protected:
ACTOR* m_pActor;
FunctionType m_pFunctor;
};
需要接受KeyPress事件的Act物体需要提供一个签名如下的函数
bool OnKeyPressed(EKEY_CODE keyCode);
在runGame函数里,屏幕会被绘制。对我们来说,这意味着活跃的Act的绘制函数会被调用。同样的行为也会被施加到IntroAct和CreditsAct上。
我使用了两种类型的IDrawable,一种是静态,一种是动态。静态的例子是ShipDisplay。这个类会在屏幕右边显示飞船的状态。这些信息总是需要被绘制的。一个动态绘制的例子是Torpedo,这个会一段时间显示、一段时间不显示。被称为动画器的类型会拓展IDrawable接口并实现IAnimator接口。一个这样的例子是Phaser。动画器控制代码绘制以及生命周期。
void Phaser::updateInfo(Info& rInfo)
{
if(rInfo.Alpha >= 0)
{
rInfo.Alpha +=rInfo.Fade;
}
if(rInfo.Alpha >= 255)
{
rInfo.Alpha = 255;
rInfo.Fade = -rInfo.Fade;
}
}
当Phaser的生命周期结束的时候,endAnimator会被调用,对Phaser而言,会调用对应的容器。
void Phaser::endAnimator()
{
if (m_pVessel)
{
m_pVessel->hitPhaser(m_iEnergy);
}
}
最困难的一个点是实现控制界面。Console控制表现和输入,当Enter和Escape键被按下的时候,CommandManager被触发。这个管理器还保存着输入的状态。
enum Mode
{
WaitForCommand,
NavigateWaitForCourse,
NavigateWaitForDistance,
LaunchWaitForEnergy,
LaunchWaitForHit,
LaunchWaitForCourse,
LaunchWaitForCoordinates,
TransferWaitForEnergy,
ComputerWaitForCommand,
WaitForAnimation
};
CommandManager验证输入并且采取合适的动作,比如让Enterprise转能量为护盾。第三个类是Controller,它负责创建Torpedo和Phaser动画器,并把他们加到PlayAct上。runEnemyAI函数判断是否有敌人的船只以及是否需要跑。有三种类型的容器:Enterprise,KlingonShip和StarBase,都实现了IVessel接口
五、感兴趣的点
首先,我使用了一个像IrrLich这样伟大的库,但是却仅仅是绘制了一些简单的图形,我很抱歉。但是很容易把字符改成2D图像。或者你可以更进一步让它变成全3D。它应该很容易把Controller和CommandManager类从回合制变成即时制。