65.9K
CodeProject 正在变化。 阅读更多。
Home

另一个星际迷航游戏(复古版)

starIconstarIconstarIconstarIconstarIcon

5.00/5 (12投票s)

2008年8月27日

CPOL

6分钟阅读

viewsIcon

55936

downloadIcon

2266

重构的星际迷航游戏,采用 2D 复古风格

引言

在阅读了 Michael Birken 的文章后,不得不承认,文章再次证明了一个好游戏只有一个要素:玩法。而不是画面,也不是声音。作为 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++ 目录

Using the Code

您现在可以开始使用和编译此代码了。我从一个控制台项目模板开始;这样做的好处是控制台将用作输出和跟踪窗口,从而更易于调试引擎。在 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 对象将允许您在不同的动作之间切换。这款游戏有三个动作:IntroActPlayActCreditsAct。Intro 将绘制星际迷航标志并展示游戏目标。Play 将包含实际游戏,而 Credits 将在游戏结束后被调用,以纪念我们胜利的舰长,或者为最伟大星舰的毁灭而哭泣。我们需要最后一个对象是 InputManager。这个对象将把 IrrLicht 引擎返回的事件转换为我们希望接收通知的输入。在我们的例子中,这些将是 KeyPress 事件。

为了将 KeyPress 事件发送到各个动作,我本来可以使用一个简单的回调函数,但我喜欢 C# 的委托概念。在 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 函数中,屏幕被渲染。对我们来说,这意味着将调用活动 Actdraw 函数。我们可以在这里绘制所有内容(对于 IntroActCreditsAct,由于内容不多,我将这样做),或者我们将一些 IDrawable 对象加载到 Act 中。

我使用了两种类型的 IDrawable:一种是相对静态的,另一种则更动态。静态可绘制对象的例子是 ShipDisplay。此类在屏幕右侧显示飞船和游戏的状态。此信息始终需要绘制。动态可绘制对象的例子是 Torpedo,它将显示一段时间然后从屏幕上移除。这种类型,我称之为 Animator,实现了 IAnimator 接口,该接口扩展了 IDrawable 接口。例如 Phaser 类。Animator 提供了所有绘制代码和生命周期控制代码。因此,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,该对象实际创建 TorpedoPhaser 动画程序并将它们添加到 PlayActrunEnemyAI 函数检查是否有运行的需要,以及该区域内是否有敌舰。有三个飞船:EnterpriseKlingonShipStarBase,它们都实现了 IVessel 接口。

关注点

首先,我想为使用像 IrrLicht 这样优秀的库来仅绘制一个简单的控制台外观而道歉。但是,将字符 <E> 更改为企业号的 2D 图像非常容易。或者,您可以做得更进一步,将其制成完整的 3D。修改 ControllerCommandManager 类以使其成为实时而非回合制也应该很容易。

参考文献

历史

  • 2008 年 8 月 27 日 - 本文首次发布
© . All rights reserved.