【译】另外一个星际迷航游戏(复古的那一款)

发表于2016-03-07
评论0 3.3k浏览

  原文地址: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,这解决了我的第一个问题。这个引擎同时支持2D3D,这很完美。我将从2D开始,然后慢慢过度到3D而无需换引擎。

  不使用控制台带来最大的不同是我们输入的方式。一个控制台游戏是输入驱动的。分析输入以后,我们执行特定的逻辑然后重新绘制屏幕。一个Windows程序是时间驱动的程序。这意味着我需要分析事件创建我自己的输入来驱动游戏逻辑。并且,因为它是一个窗口,我需要更新屏幕并在等待输入的时候持续绘制屏幕的内容。


三、写代码之前

  如果你需要使用并编译这段代码,你还需要IrrLicht SDK。它包括你所有需要启动游戏的内容,包括一个构建好的DLLlib

  在磁盘上放好你的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, PlayActCreditsActIntro负责绘制星际迷航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的绘制函数会被调用。同样的行为也会被施加到IntroActCreditsAct上。

  我使用了两种类型的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控制表现和输入,当EnterEscape键被按下的时候,CommandManager被触发。这个管理器还保存着输入的状态。

enum Mode

{

    WaitForCommand,

   NavigateWaitForCourse,

   NavigateWaitForDistance,

   LaunchWaitForEnergy,

   LaunchWaitForHit,

   LaunchWaitForCourse,

   LaunchWaitForCoordinates,

   TransferWaitForEnergy,

   ComputerWaitForCommand,

   WaitForAnimation

};

  CommandManager验证输入并且采取合适的动作,比如让Enterprise转能量为护盾。第三个类是Controller,它负责创建TorpedoPhaser动画器,并把他们加到PlayAct上。runEnemyAI函数判断是否有敌人的船只以及是否需要跑。有三种类型的容器:Enterprise,KlingonShipStarBase,都实现了IVessel接口

五、感兴趣的点

  首先,我使用了一个像IrrLich这样伟大的库,但是却仅仅是绘制了一些简单的图形,我很抱歉。但是很容易把字符改成2D图像。或者你可以更进一步让它变成全3D。它应该很容易把ControllerCommandManager类从回合制变成即时制。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引