在游戏开发领域,一个普遍的共识是:游戏的核心在于玩法本身,而非其外观或声音效果。作为一名Introversion Software游戏的忠实粉丝,不禁思考,为何不将这种复古风格融入到即将开发的游戏之中呢?正如James Curran所言,Michael Birken的文章已经全面覆盖了游戏的各个方面,因此将更多地关注于构建一个非游戏机平台的2D游戏,并使其看起来像游戏机游戏的不同之处。
面临的第一个问题是,应该使用哪种技术来开发游戏:DirectX还是OpenGL?第二个问题更为重要:由于这是第一个图形项目,能否及时掌握所选技术并编写所需的所有代码?在网络搜索了一段时间后,偶然发现了IrrLicht这个网站。这是一个免费的图形引擎,支持DirectX和OpenGL,这也解决了第一个问题。这个引擎提供了2D和3D模式,非常完美。可以从2D开始,然后转向3D,而无需更换引擎。
当不使用游戏机时,处理输入的方式是最大的不同之一。游戏机程序是一个输入驱动的程序。在分析输入之后,执行所需的逻辑,然后重新绘制屏幕。而Windows(非游戏机)程序是一个事件驱动的程序。这意味着需要分析事件并创建自己的输入来驱动游戏逻辑。而且,因为它是一个窗口,需要自己绘制屏幕,并在等待输入的同时不断更新屏幕。
如果想要使用并编译此代码,还需要IrrLicht SDK,可以。它包含了开始所需的一切,包括已经构建好的DLL和lib文件。将SDK放置在磁盘上后,需要将包含文件和库文件的位置添加到Visual Studio中。打开“工具”菜单下的“选项”菜单。选择“项目和解决方案”选项以及子选项“VC++目录”。
现在可以准备使用并编译此代码了。从控制台项目模板开始,这有一个优势,即控制台将被用作输出和跟踪窗口,这使得调试引擎更加容易。在主函数中,创建了一个Game对象。这个对象包含了设置、运行和结束游戏的功能。
C++
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对象将允许在行为之间切换。游戏中有三个行为:IntroAct、PlayAct和CreditsAct。Intro将绘制一个《星际迷航》的标志,并展示游戏的目标。Play将包含实际的游戏,而Credits将在游戏结束时被调用,以向胜利的船长致敬,或者为有史以来最伟大的星舰的毁灭而哀悼。最后需要的一个对象是InputManager。这个对象将IrrLicht引擎返回的事件转换为希望被通知的输入。在情况下,这些将是KeyPress事件。
为了将KeyPress事件发送到行为,本可以使用一个简单的回调函数,但喜欢C#的委托概念。在C++中,这可以通过Functor来实现。boost库提供了几个类来做到这一点,但不想使用boost(至少不是在这篇文章中)。因此,为此任务创建了硬编码Functor。
C++
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对象只需要提供一个具有以下签名的函数:
C++
bool
OnKeyPressed(EKEY_CODE keyCode);
在runGame函数中,屏幕被渲染。对来说,这意味着将调用活动Act的draw函数。要么在这里绘制一切,将为IntroAct和CreditsAct这样做,因为它们并不多,或者加载一些IDrawable对象到Act中。
使用了两种类型的IDrawable,那些相对静态的和那些更动态的。一个静态可绘制的例子是ShipDisplay。这个类显示了屏幕右侧的船只和游戏状态。这些信息总是需要被绘制的。一个动态可绘制的例子是Torpedo,它将在屏幕上绘制一段时间,然后被移除。称之为Animator的这种类型实现了IAnimator接口,它扩展了IDrawable接口。一个例子是Phaser类。Animator提供了所有绘制和控制生命周期的代码。因此,Phaser只需要添加不同之处。
C++
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的情况下,目标船只被击中。
C++
void
Phaser::endAnimator()
{
if
(m_pVessel)
{
m_pVessel->
hitPhaser(m_iEnergy);
}
}
实现控制台界面是最困难的部分之一。Console类处理外观和输入。每当按下Enter或Escape键时,就会触发CommandManager。这个管理器存储了游戏输入的状态。
C++
enum
Mode
{
WaitForCommand,
NavigateWaitForCourse,
NavigateWaitForDistance,
LaunchWaitForEnergy,
LaunchWaitForHit,
LaunchWaitForCourse,
LaunchWaitForCoordinates,
TransferWaitForEnergy,
ComputerWaitForCommand,
WaitForAnimation
};
CommandManager验证输入并采取适当的行动,例如,让Enterprise对象将能量转移到护盾上。第三个类是Controller,这个对象实际上创建了Torpedo或Phaser动画器,并将它们添加到PlayAct中。runEnemyAI函数检查是否有运行的需要,以及是否有敌舰在该扇区。有三个船只:Enterprise、KlingonShip和StarBase,它们都实现了IVessel接口。
首先,想为使用像IrrLicht这样的伟大库来绘制一个简单的控制台外观而道歉。但是,将字符