UDN
Search public documentation:

DevelopmentKitGemsRTSStarterKitJP
English Translation
中国翻译
한국어

Interested in the Unreal Engine?
Visit the Unreal Technology site.

Looking for jobs and company info?
Check out the Epic games site.

Questions about support via UDN?
Contact the UDN Staff

UE3 ホーム > Unreal Development Kit Gems > RTS Starter Kit
UE3 ホーム > 入門編 : プログラミング > RTS Starter Kit

RTS Starter Kit (RTS スターターキット)


2011年9月に UDK で最終テスト実施済み

概要


このスターターキットには、サンプルコードが含まれています。このサンプルコードは、リアルタイム ストラテジー (RTS) ゲームを開発する際の土台として使用することができます。(リアルタイム ストラテジー ゲームには、UDK を使用して現在開発中である Hostile Worlds などがあります)。

RTSKit_HostileWorlds.jpg

同梱されているものについて


プラットフォーマー スターターキットレーサー スターターキット とは異なり、このスターターキットには、大量のコードとコンテンツが含まれています。また、リアルタイム ストラテジー ゲームにはそれほど関連のない高度な機能もいくつか含まれていますが、他の開発分野でも役立つことがあるでしょう。

  • Platform abstraction (プラットフォーム抽象化) - RTS Starter Kit は、プレイヤーがプレイしているプラットフォームを識別することができます。プレイヤーが使用しているプラットフォームに応じて、異なるプレイヤーのコントローラおよび HUD クラスが使用されます。ただし、PC および コンソール プラットフォームのクラスは、スタブアウトされているにすぎません。現在のところ、モバイルプラットフォームだけが完全にサポートされています。
  • Camera (カメラ) - RTS Starter Kit には、俯瞰カメラのように振る舞うカメラを作成するためのサンプルが含まれています。パンおよびタッチパン、ズームをサポートしています。
  • Structures (建造物) - RTS Starter Kit には、プレイヤーが建造できる建造物を扱うサンプルコードが含まれています。
  • Units (ユニット) - RTS Starter Kit には、プレイヤーが命令を出すことができるユニット (部隊) を扱うサンプルコードが含まれています。
  • Skills (スキル) - RTS Starter Kit には、ユニットのスキルを扱うサンプルコードが含まれています。
  • Resource Management (リソース管理) - RTS Starter Kit には、プレイヤーのためのリソースを扱うサンプルコードが含まれています。
  • AI - RTS Starter Kit には、敵の AI プレイヤーを作成する方法を示すサンプルコードが含まれています。この AI プレイヤーは、建造物とユニットを作ることができ、プレイヤーと戦うことができます。
  • Upgrades (アップグレード) - RTS Starter Kit には、ユニットのためのアップグレードを作成する方法を示すサンプルコードが含まれています。
  • Structure Upgrades (建造物のアップグレード) - RTS Starter Kit には、アップグレードすることによってより高度な形態に発展する建造物の作成方法を示すサンプルコードが含まれています。
  • Networking (ネットワーク) - RTS Starter Kit は、ネットワークを考慮に入れて作成されています。そのため、レプリケーションがセットアップされています。
  • Documentation (ドキュメンテーション) - RTS Starter Kit は、 完全にドキュメント化されています。 Javadocs と類似したスタイルをとっています。
  • UI (ユーザーインターフェイス) - RTS Starter Kit には、小規模なカスタムの UI コードベースが含まれています。これは、単純なボタンを扱います。

ALERT! 注意: RTSスターターキットのモバイルバージョンのみが含まれています。

コードの構造


RTS Starter Kit は大規模で複雑なプロジェクトです。したがって、RTS Starter Kit を拡張、改変してゲームを作成するには、事前に、あらゆる部分を理解しておくことが重要となります。

マウスまたは指の「真下」にあるものをどのように検知するか

マウスのインターフェイスコードは、 Mouse Interface Development Kit Gem (マウスインターフェイス開発キット Gem) とは異なる動作をします。RTS ゲームの場合、遠方のユニットが選びづらくなっては困ります。また、さまざまな事柄に優先順位をつけて、いつでも選択できるようにする必要があるでしょう。たとえば、ユニットの重要度を建造物の重要度よりも高くし、建造物の重要度をリソースポイントの重要度よりも高くしなければならない場合があります。RTS Starter Kit は、これらの問題を解決するために、スクリーン空間ボックスを作成して、スクリーン上でユニットの大きさを表わします。遠方のユニットが小さなスクリーン ボックスになるという問題に対処するには、ボックスのサイズを人工的に水増しすることができます。(しかし、カメラのスタイルにより、開発中にこの問題が実際に起きることはありませんでした)。優先順位の問題を解決するには、ユニットを最初にイタレートし、次に建造物を、その次にリソースをイタレートします。タッチはすでにスクリーン内でキャプチャされているため、ボックス内のその地点から比較を実行することによって、マウスまたは指が何の上にあるかを調べます。マウスまたは指が何もタッチしていない場合は、ワールドであったと想定されることになります。

RTSKit_BoundingBoxes.jpg

次のコードスニペットでは、ワールド内にある全リソースをイタレートし、各リソースのためのスクリーン バウンディング ボックスを計算しています。

UDKRTSMobileHUD.uc
// Calculate the screen bounding boxes for all of the resources
for (i = 0; i < UDKRTSGameReplicationInfo.Resources.Length; ++i)
{
  if (UDKRTSGameReplicationInfo.Resources[i] != None)
  {
    UDKRTSGameReplicationInfo.Resources[i].ScreenBoundingBox = CalculateScreenBoundingBox(Self, UDKRTSGameReplicationInfo.Resources[i], UDKRTSGameReplicationInfo.Resources[i].CollisionCylinder);

    // Render the debug bounding box
    if (ShouldDisplayDebug('BoundingBoxes'))
    {
      Canvas.SetPos(UDKRTSGameReplicationInfo.Resources[i].ScreenBoundingBox.Min.X, UDKRTSGameReplicationInfo.Resources[i].ScreenBoundingBox.Min.Y);
      Canvas.DrawColor = UDKRTSGameReplicationInfo.Resources[i].BoundingBoxColor;
      Canvas.DrawBox(UDKRTSGameReplicationInfo.Resources[i].ScreenBoundingBox.Max.X - UDKRTSGameReplicationInfo.Resources[i].ScreenBoundingBox.Min.X, UDKRTSGameReplicationInfo.Resources[i].ScreenBoundingBox.Max.Y - UDKRTSGameReplicationInfo.Resources[i].ScreenBoundingBox.Min.Y);
    }
  }
}

次のコードスニペットでは、スクリーン バウンディング ボックスを計算しています。 Actor Selection Brackets Development Kit Gem (アクタ選択ブラケット開発キット Gem) と同様のものです。

UDKRTSHUD.uc
function Box CalculateScreenBoundingBox(HUD HUD, Actor Actor, PrimitiveComponent PrimitiveComponent)
{
  local Box ComponentsBoundingBox, OutBox;
  local Vector BoundingBoxCoordinates[8];
  local int i;

  if (HUD == None || PrimitiveComponent == None || Actor == None || WorldInfo.TimeSeconds - Actor.LastRenderTime >= 0.1f)
  {
    OutBox.Min.X = -1.f;
    OutBox.Min.Y = -1.f;
    OutBox.Max.X = -1.f;
    OutBox.Max.Y = -1.f;

    return OutBox;
  }

  ComponentsBoundingBox.Min = PrimitiveComponent.Bounds.Origin - PrimitiveComponent.Bounds.BoxExtent;
  ComponentsBoundingBox.Max = PrimitiveComponent.Bounds.Origin + PrimitiveComponent.Bounds.BoxExtent;

  // Z1
  // X1, Y1
  BoundingBoxCoordinates[0].X = ComponentsBoundingBox.Min.X;
  BoundingBoxCoordinates[0].Y = ComponentsBoundingBox.Min.Y;
  BoundingBoxCoordinates[0].Z = ComponentsBoundingBox.Min.Z;
  BoundingBoxCoordinates[0] = HUD.Canvas.Project(BoundingBoxCoordinates[0]);
  // X2, Y1
  BoundingBoxCoordinates[1].X = ComponentsBoundingBox.Max.X;
  BoundingBoxCoordinates[1].Y = ComponentsBoundingBox.Min.Y;
  BoundingBoxCoordinates[1].Z = ComponentsBoundingBox.Min.Z;
  BoundingBoxCoordinates[1] = HUD.Canvas.Project(BoundingBoxCoordinates[1]);
  // X1, Y2
  BoundingBoxCoordinates[2].X = ComponentsBoundingBox.Min.X;
  BoundingBoxCoordinates[2].Y = ComponentsBoundingBox.Max.Y;
  BoundingBoxCoordinates[2].Z = ComponentsBoundingBox.Min.Z;
  BoundingBoxCoordinates[2] = HUD.Canvas.Project(BoundingBoxCoordinates[2]);
  // X2, Y2
  BoundingBoxCoordinates[3].X = ComponentsBoundingBox.Max.X;
  BoundingBoxCoordinates[3].Y = ComponentsBoundingBox.Max.Y;
  BoundingBoxCoordinates[3].Z = ComponentsBoundingBox.Min.Z;
  BoundingBoxCoordinates[3] = HUD.Canvas.Project(BoundingBoxCoordinates[3]);

  // Z2
  // X1, Y1
  BoundingBoxCoordinates[4].X = ComponentsBoundingBox.Min.X;
  BoundingBoxCoordinates[4].Y = ComponentsBoundingBox.Min.Y;
  BoundingBoxCoordinates[4].Z = ComponentsBoundingBox.Max.Z;
  BoundingBoxCoordinates[4] = HUD.Canvas.Project(BoundingBoxCoordinates[4]);
  // X2, Y1
  BoundingBoxCoordinates[5].X = ComponentsBoundingBox.Max.X;
  BoundingBoxCoordinates[5].Y = ComponentsBoundingBox.Min.Y;
  BoundingBoxCoordinates[5].Z = ComponentsBoundingBox.Max.Z;
  BoundingBoxCoordinates[5] = HUD.Canvas.Project(BoundingBoxCoordinates[5]);
  // X1, Y2
  BoundingBoxCoordinates[6].X = ComponentsBoundingBox.Min.X;
  BoundingBoxCoordinates[6].Y = ComponentsBoundingBox.Max.Y;
  BoundingBoxCoordinates[6].Z = ComponentsBoundingBox.Max.Z;
  BoundingBoxCoordinates[6] = HUD.Canvas.Project(BoundingBoxCoordinates[6]);
  // X2, Y2
  BoundingBoxCoordinates[7].X = ComponentsBoundingBox.Max.X;
  BoundingBoxCoordinates[7].Y = ComponentsBoundingBox.Max.Y;
  BoundingBoxCoordinates[7].Z = ComponentsBoundingBox.Max.Z;
  BoundingBoxCoordinates[7] = HUD.Canvas.Project(BoundingBoxCoordinates[7]);

  // Find the left, top, right and bottom coordinates
  OutBox.Min.X = HUD.Canvas.ClipX;
  OutBox.Min.Y = HUD.Canvas.ClipY;
  OutBox.Max.X = 0;
  OutBox.Max.Y = 0;

  // Iterate though the bounding box coordinates
  for (i = 0; i < ArrayCount(BoundingBoxCoordinates); ++i)
  {
    // Detect the smallest X coordinate
    if (OutBox.Min.X > BoundingBoxCoordinates[i].X)
    {
      OutBox.Min.X = BoundingBoxCoordinates[i].X;
    }

    // Detect the smallest Y coordinate
    if (OutBox.Min.Y > BoundingBoxCoordinates[i].Y)
    {
      OutBox.Min.Y = BoundingBoxCoordinates[i].Y;
    }

    // Detect the largest X coordinate
    if (OutBox.Max.X < BoundingBoxCoordinates[i].X)
    {
      OutBox.Max.X = BoundingBoxCoordinates[i].X;
    }

    // Detect the largest Y coordinate
    if (OutBox.Max.Y < BoundingBoxCoordinates[i].Y)
    {
      OutBox.Max.Y = BoundingBoxCoordinates[i].Y;
    }
  }

  // Check if the bounding box is within the screen
  if ((OutBox.Min.X < 0 && OutBox.Max.X < 0) || (OutBox.Min.X > HUD.Canvas.ClipX && OutBox.Max.X > HUD.Canvas.ClipX) || (OutBox.Min.Y < 0 && OutBox.Max.Y < 0) || (OutBox.Min.Y > HUD.Canvas.ClipY && OutBox.Max.Y > HUD.Canvas.ClipY))
  {
    OutBox.Min.X = -1.f;
    OutBox.Min.Y = -1.f;
    OutBox.Max.X = -1.f;
    OutBox.Max.Y = -1.f;
  }
  else
  {
    // Clamp the bounding box coordinates
    OutBox.Min.X = FClamp(OutBox.Min.X, 0.f, HUD.Canvas.ClipX);
    OutBox.Max.X = FClamp(OutBox.Max.X, 0.f, HUD.Canvas.ClipX);
    OutBox.Min.Y = FClamp(OutBox.Min.Y, 0.f, HUD.Canvas.ClipY);
    OutBox.Max.Y = FClamp(OutBox.Max.Y, 0.f, HUD.Canvas.ClipY);
  }

  return OutBox;
}

次のコードスニペットでは、タッチイベントが検出されます。ユニット (pawn) の配列と建造物 (structure) の配列がイタレートされています。タッチが、ユニットまたは建造物のスクリーン バウンディング ボックス内に位置している場合は、選択され、その HUD アクションが登録されます。

UDKRTSMobilePlayerController.uc
// Check if we touch any game play relevant objects
if (PlayerReplicationInfo != None)
{
  UDKRTSTeamInfo = UDKRTSTeamInfo(PlayerReplicationInfo.Team);
  if (UDKRTSTeamInfo != None)
  {
    UDKRTSMobileHUD = UDKRTSMobileHUD(MyHUD);
    if (UDKRTSMobileHUD != None)
    {
      // Are we touching a pawn?
      if (TouchEvent.Response == ETR_None && UDKRTSTeamInfo.Pawns.Length > 0)
      {
        for (i = 0; i < UDKRTSTeamInfo.Pawns.Length; ++i)
        {
          if (UDKRTSTeamInfo.Pawns[i] != None && class'UDKRTSMobileHUD'.static.IsPointWithinBox(TouchLocation, UDKRTSTeamInfo.Pawns[i].ScreenBoundingBox) && TouchEvents.Find('AssociatedActor', UDKRTSTeamInfo.Pawns[i]) == INDEX_NONE)
          {
            UDKRTSTeamInfo.Pawns[i].Selected();
            UDKRTSTeamInfo.Pawns[i].RegisterHUDActions(UDKRTSMobileHUD);

            TouchEvent.AssociatedActor = UDKRTSTeamInfo.Pawns[i];
            TouchEvent.Response = ETR_Pawn;
            break;
          }
        }
      }

      // Are we touching a structure
      if (TouchEvent.Response == ETR_None && UDKRTSTeamInfo.Structures.Length > 0)
      {
        for (i = 0; i < UDKRTSTeamInfo.Structures.Length; ++i)
        {
          if (class'UDKRTSMobileHUD'.static.IsPointWithinBox(TouchLocation, UDKRTSTeamInfo.Structures[i].ScreenBoundingBox) && TouchEvents.Find('AssociatedActor', UDKRTSTeamInfo.Structures[i]) == INDEX_NONE)
          {
            UDKRTSTeamInfo.Structures[i].Selected();
            UDKRTSTeamInfo.Structures[i].RegisterHUDActions(UDKRTSMobileHUD);

            TouchEvent.AssociatedActor = UDKRTSTeamInfo.Structures[i];
            TouchEvent.Response = ETR_Structure;
            break;
          }
        }
      }
    }
  }
}

建造物 / ユニットを選択するとどのようにボタンが HUD 上に表示されるか?

RTS Starter Kit では、ボタンのことを HUD アクションと呼びます。HUD アクションは、UDKRTSHUD で定義される構造体です。

RTSKit_HUDActionsAndHealthBar.jpg

次のコードスニペットでは、HUD アクションによって、まず、テクスチャとテクスチャの UV 座標が定義されています。これらの変数は、Unreal Editor にエクスポーズされるため、ゲーム開発者が値をセットすることができます。残りの 4 つの変数は、インゲームで使用されて、他の関数を実行します。これについては、後ほど解説します。

UDKRTSHUD.uc
// HUD actions on the HUD
struct SHUDAction
{
  var() Texture2D Texture;
  var() float U;
  var() float V;
  var() float UL;
  var() float VL;
  var EHUDActionReference Reference;
  var int Index;
  var bool PostRender;
  var delegate<IsHUDActionActive> IsHUDActionActiveDelegate;
};

HUD アクションの構造体は、さらに、SAssociatedHUDAction という別の構造体内部で使用されます。これは、HUD アクションの配列をアクタと関連づけるものです。

UDKRTSHUD.uc
// HUD actions that are associated to an actor on the HUD
struct SAssociatedHUDAction
{
  var Actor AssociatedActor;
  var array<SHUDAction> HUDActions;
};

ユーザーが建造物 / ユニットを選択すると (モバイル バージョンの場合は建造物 / ユニットをタッチします)、RegisterHUDActions() 関数が建造物 / ユニット上で呼び出されます。この関数は、関連する HUD アクションを HUD に登録します。次のコードスニペットでは、Portrait 変数が SHUDAction となっており、多くの RTS ゲームでよく使用されるキャラクターのポートレートを定義しています。ここから、さらに、Reference 変数をセットすることによって、当該の HUD アクションの処理方法をゲームの残りの部分に伝えるとともに、Index 変数をセットすることによって、メタデータを格納し、さらに、PostRender ブール型をセットします。HUD アクションの設定が完了すると、登録が行われます。

UDKRTSPawn.uc
simulated function RegisterHUDActions(UDKRTSMobileHUD HUD)
{
  local int i;
  local SHUDAction SendHUDAction;

  if (HUD == None || OwnerReplicationInfo == None || HUD.AssociatedHUDActions.Find('AssociatedActor', Self) != INDEX_NONE || Health <= 0)
  {
    return;
  }

  // Register the camera center HUD action
  if (Portrait.Texture != None)
  {
    SendHUDAction = Portrait;
    SendHUDAction.Reference = EHAR_Center;
    SendHUDAction.Index = -1;
    SendHUDAction.PostRender = true;

    HUD.RegisterHUDAction(Self, SendHUDAction);
  }
}

次のコードスニペットは、HUD アクションが登録される場合の動作を示しています。AssociatedHUDActions 配列の内部を調べて、挿入すべきか否かを判断しています。これは、HUD アクションが重複しないようにするための措置です。チェックに成功すると、HUD アクションが追加されます。

UDKRTSPawn.uc
function RegisterHUDAction(Actor AssociatedActor, SHUDAction HUDAction)
{
  local SAssociatedHUDAction AssociatedHUDAction;
  local int IndexA, IndexB;

  // Get index A
  IndexA = AssociatedHUDActions.Find('AssociatedActor', AssociatedActor);
  if (IndexA != INDEX_NONE)
  {
    // Get index B
    IndexB = AssociatedHUDActions[IndexA].HUDActions.Find('Reference', HUDAction.Reference);
    if (IndexB != INDEX_NONE && AssociatedHUDActions[IndexA].HUDActions[IndexB].Index == HUDAction.Index)
    {
      return;
    }
  }

  if (IndexA != INDEX_NONE)
  {
    // Add the associated HUD action
    AssociatedHUDActions[IndexA].HUDActions.AddItem(HUDAction);
  }
  else
  {
    // Add the associated HUD action
    AssociatedHUDAction.AssociatedActor = AssociatedActor;
    AssociatedHUDAction.HUDActions.AddItem(HUDAction);
    AssociatedHUDActions.AddItem(AssociatedHUDAction);
  }
}

このコードスニペットは、HUD がどのように HUD アクションをレンダリングするかを示しています。AssociatedHUDActions 配列をイタレートしています。AssociatedHUDActions 配列のアイテムごとに、新たな行を作成しています。行ごとに、HUD アクションがスクリーンに描画されます。これによって、複数のアクタが HUD アクションをスクリーン上に登録することができるようになるとともに、プレイヤーがそれらにアクセスすることができるようになります。

UDKRTSHUD.uc
event PostRender()
{
  Super.PostRender();

  if (AssociatedHUDActions.Length > 0)
  {
    Offset.X = PlayableSpaceLeft;
    Offset.Y = 0;
    Size.X = SizeX * 0.0625f;
    Size.Y = Size.X;

    for (i = 0; i < AssociatedHUDActions.Length; ++i)
    {
      if (AssociatedHUDActions[i].AssociatedActor != None && AssociatedHUDActions[i].HUDActions.Length > 0)
      {
        Offset.X = HUDProperties.ScrollWidth;

        for (j = 0; j < AssociatedHUDActions[i].HUDActions.Length; ++j)
        {
          if (AssociatedHUDActions[i].HUDActions[j].IsHUDActionActiveDelegate != None)
          {
            IsHUDActionActive = AssociatedHUDActions[i].HUDActions[j].IsHUDActionActiveDelegate;

            if (!IsHUDActionActive(AssociatedHUDActions[i].HUDActions[j].Reference, AssociatedHUDActions[i].HUDActions[j].Index, false))
            {
              Canvas.SetDrawColor(191, 191, 191, 191);
            }
            else
            {
              Canvas.SetDrawColor(255, 255, 255);
            }

            IsHUDActionActive = None;
          }
          else
          {
            Canvas.SetDrawColor(255, 255, 255);
          }

          Canvas.SetPos(Offset.X, Offset.Y);
          Canvas.DrawTile(AssociatedHUDActions[i].HUDActions[j].Texture, Size.X, Size.Y, AssociatedHUDActions[i].HUDActions[j].U, AssociatedHUDActions[i].HUDActions[j].V, AssociatedHUDActions[i].HUDActions[j].UL, AssociatedHUDActions[i].HUDActions[j].VL);

          if (AssociatedHUDActions[i].HUDActions[j].PostRender)
          {
            UDKRTSHUDActionInterface = UDKRTSHUDActionInterface(AssociatedHUDActions[i].AssociatedActor);
            if (UDKRTSHUDActionInterface != None)
            {
              UDKRTSHUDActionInterface.PostRenderHUDAction(Self, AssociatedHUDActions[i].HUDActions[j].Reference, AssociatedHUDActions[i].HUDActions[j].Index, Offset.X, Offset.Y, Size.X, Size.Y);
            }
          }

          Offset.X += Size.X;
        }
      }

      Offset.Y += Size.Y;
    }
  }
}

この関数ルーチンでは デリゲート 変数である IsHUDActionActiveDelegate が使用されていることが分かります。この変数によって、HUD アクション自身もデリゲートの参照を格納することによって、HUD がそれ自身のデリゲートにアサインすることができるようになります。これによって、どのオブジェクトがその関数を所有しているかを特に知ることなく、HUD がデリゲートを実行することができるようになります。ここでは、HUD アクションがアクティブであるか否かを HUD にチェックさせるために使用しています。(すなわち、HUD アクションをプレイヤーが実行することができるか否かをチェックしています)。

PostRender 変数が true である場合は、関連づけられているアクタが UDKRTSHUDActionInterface インターフェイス にキャストされます。クラスは、任意の数のインターフェイスを実装することができます。AssociatedActor がどのような種類のアクタであるかを HUD が明確に分かっていないため役に立ちます。基本アクタクラスを変更するのではなく、インターフェイスを実装することによって、コードをこのプロジェクト専用のままにしておくことができます。そこから、PostRenderHUDAction 関数が呼び出され、関連づけられているアクタが HUD 上においてレンダリングできるようになります。

これは特に建造物 / ユニットのヘルス値をレンダリングするために使用されています。

UDKRTSPawn.uc
simulated function PostRenderHUDAction(HUD HUD, EHUDActionReference Reference, int Index, int PosX, int PosY, int SizeX, int SizeY)
{
  local float HealthPercentage;
  local float HealthBarWidth, HealthBarHeight;

  if (HUD == None || HUD.Canvas == None || Health <= 0)
  {
    return;
  }

  if (Reference == EHAR_Center)
  {
    // Get the health bar percentage
    HealthPercentage = float(Health) / float(HealthMax);

    // Render the health bar border
    HealthBarWidth = SizeX - 2;
    HealthBarHeight = 8;
    HUD.Canvas.SetPos(PosX + 1, PosY + SizeY - HealthBarHeight - 1);
    HUD.Canvas.SetDrawColor(0, 0, 0, 191);
    HUD.Canvas.DrawBox(HealthBarWidth, HealthBarHeight);

    HealthBarWidth -= 4;
    HealthBarHeight -= 4;

    // Render the missing health
    HUD.Canvas.SetPos(PosX + 3, PosY + SizeY - HealthBarHeight - 3);
    HUD.Canvas.SetDrawColor(0, 0, 0, 127);
    HUD.Canvas.DrawRect(HealthBarWidth, HealthBarHeight);

    // Render the health bar
    HUD.Canvas.SetPos(PosX + 3, PosY + SizeY - HealthBarHeight - 3);
    HUD.Canvas.SetDrawColor(255 * (1.f - HealthPercentage), 255 * HealthPercentage, 0, 191);
    HUD.Canvas.DrawRect(HealthBarWidth * HealthPercentage, HealthBarHeight);
  }
}

HUD アクションが押された場合どのような処理が行われるか?

タッチ入力が UDKRTSMobilePlayerController によって受け取られると、まず、HUD に渡されて、タッチの位置が HUD アクションの範囲内にあるかどうかがチェックされます。範囲内である場合は、最初に HUD アクションのアクティブなデリゲートを呼び出します。このデリゲートによって、アクタが結びつくことができるようになり、HUD アクションによって何らかの動作が実行されるか否かが決まります。HUD アクションの有効化が許可されている場合は、UDKRTSPlayerController 内の StartHUDAction を呼び出します。これによって、クライアントからサーバーへのリモートプロシージャコールにおいて、HUD アクションがラップされるようになります。さらに、HUD アクションに関連づけられているアクタは、UDKRTSHUDActionInterface にキャストされます。キャストに成功すると、HandleHUDAction が呼び出されて、アクタが何らかを実行します。

UDKRTSMobileHUD.uc
function ETouchResponse InputTouch(Vector2D ScreenTouchLocation)
{
  // Check the HUD action controls
  if (AssociatedHUDActions.Length > 0)
  {
    Offset.X = PlayableSpaceLeft;
    Offset.Y = 0;
    Size.X = SizeX * 0.0625f;
    Size.Y = Size.X;

    for (i = 0; i < AssociatedHUDActions.Length; ++i)
    {
      if (AssociatedHUDActions[i].AssociatedActor != None && AssociatedHUDActions[i].HUDActions.Length > 0)
      {
        Offset.X = HUDProperties.ScrollWidth;

        for (j = 0; j < AssociatedHUDActions[i].HUDActions.Length; ++j)
        {
          if (ScreenTouchLocation.X >= Offset.X && ScreenTouchLocation.Y >= Offset.Y && ScreenTouchLocation.X <= Offset.X + Size.X && ScreenTouchLocation.Y <= Offset.Y + Size.Y)
          {
            if (AssociatedHUDActions[i].HUDActions[j].IsHUDActionActiveDelegate != None)
            {
              IsHUDActionActive = AssociatedHUDActions[i].HUDActions[j].IsHUDActionActiveDelegate;

              if (!IsHUDActionActive(AssociatedHUDActions[i].HUDActions[j].Reference, AssociatedHUDActions[i].HUDActions[j].Index, true))
              {
                IsHUDActionActive = None;
                return ETR_HUDAction;
              }
              else
              {
                IsHUDActionActive = None;
              }
            }

            // Start the HUD action
            UDKRTSMobilePlayerController.StartHUDAction(AssociatedHUDActions[i].HUDActions[j].Reference, AssociatedHUDActions[i].HUDActions[j].Index, AssociatedHUDActions[i].AssociatedActor);
            return ETR_HUDAction;
          }

          Offset.X += Size.X;
        }

        Offset.Y += Size.Y;
      }
    }
  }
}

UDKRTSPlayerController.uc
/**
 * Send an action command to an actor
 *
 * @param    Reference    HUD action reference
 * @param    Index      HUD action index
 * @param    Actor      Associated actor
 */
simulated function StartHUDAction(EHUDActionReference Reference, int Index, Actor Actor)
{
  // Sync with the server
  if (Role < Role_Authority && class'UDKRTSUtility'.static.HUDActionNeedsToSyncWithServer(Reference) && UDKRTSHUDActionInterface(Actor) != None)
  {
    ServerHUDAction(Reference, Index, Actor);
  }

  BeginHUDAction(Reference, Index, Actor);
}

/**
 * Sync the action command for an actor
 *
 * @param    Reference    HUD action reference
 * @param    Index      HUD action index
 * @param    Actor      Associated actor
 */
reliable server function ServerHUDAction(EHUDActionReference Reference, int Index, Actor Actor)
{
  BeginHUDAction(Reference, Index, Actor);
}

/**
 * Begin an action command to an actor
 */
simulated function BeginHUDAction(EHUDActionReference Reference, int Index, Actor Actor)
{
  local UDKRTSHUDActionInterface UDKRTSHUDActionInterface;

  UDKRTSHUDActionInterface = UDKRTSHUDActionInterface(Actor);
  if (UDKRTSHUDActionInterface != None)
  {
    UDKRTSHUDActionInterface.HandleHUDAction(Reference, Index);
  }
}

リソースはどのように処理されるか?

デフォルトでは、3 つのリソースが本スターターキットにあります。これらは、UDKRTSPlayerReplicationInfo の中で、レプリケーション通知 int 型変数として定義されています。レプリケーションのコードブロックでは、変数が「ダーティ」である場合 (すなわち、クライアントとサーバーで異なる場合)、これら 3 つの変数がクライアントに対してのみレプリケートされ、レプリケーションが一方通行であることが定められています (サーバーのみがこの変数をクライアントに対してレプリケートするのであって、クライアントはサーバーに対してこれら変数をレプリケートすることはできません)。

UDKRTSPlayerReplicationInfo.uc
// How much resources the player has
var RepNotify int Resources;
// How much power the player has
var RepNotify int Power;
// Players current population cap
var RepNotify int PopulationCap;

// Replication block
replication
{
  if (bNetDirty && Role == Role_Authority)
    Resources, Power, PopulationCap;
}

クライアント側でこれらの変数に変化があった場合は、RepNotify によって ReplicatedEvent 関数 (Actor.uc 内で定義されています) が呼び出されることになっていました。ただし、スターターキットには必要がなかったため、使用されていません。これを使用する場合は、次のようにして使用することができます。

UDKRTSPlayerReplicationInfo.uc
/**
 * Called when a variable with the property flag "RepNotify" is replicated
 *
 * @param  VarName  Name of the variable that was replicated
 */
simulated event ReplicatedEvent(name VarName)
{
  if (VarName == 'Resources')
  {
    // Resources variable has changed
  }
  else if (VarName == 'Power')
  {
    // Power variable has changed
  }
  else if (VarName == 'PopulationCap')
  {
    // PopulationCap variable has changed
  }
  else
  {
    Super.ReplicatedEvent(VarName);
  }
}

ユニット作成をどのように処理するか?

プレイヤーは、自身のコントローラーのリモート・プロシージャ―・コールを通じてユニット作成処理をします。この方法は、プレイヤーとサーバー間でも最も直接的なルートになります。

下記のサンプルコードは、最終的にサーバーがプレイヤーのために新ユニットをスポーンする要因となる、構造フォームが構造したユニットをどのようにキューするかを論証したものです。このコードスニペットは、HandleHUDAction内にあります。この関数は、プレイヤーのコントローラーがクライアントとサーバーコールをHandleHUDActionへ同期するため、サーバーとクライアントで同時に実行されます( UDKRTSPlayerController.StartHUDAction()、 UDKRTSPlayerController.ServerHUDAction()と UDKRTSPlayerController.BeginHUDAction()を参照)。最初にチェックされるのは、要求されたユニットインデックスが配列バウンド内にあるか否か、そしてプレイヤーによるユニットの構築が可能か否かです(プレイヤーが利用可能なリソースを十分持っているか、利用可能な人口等)。これらの条件をパスすると、ユニットの構築が開始したことをプレイヤーにサウンドで知らせます。そしてユニット構築のため、リソースが使用されます。この処理は応答時間の短縮のシミュレーションとしてクライアント側で実行されます。サーバー側でもコードは実行されるので、クライアント側のリソース値と違いが生じた場合、正確な値がサーバーで再現されます。この段階で新しいHUDアクションが作成され、プレイヤーのHUDへ追加されます。この段階でプレイヤーの指がまだ建造物に置かれていて、建造物のHUDアクションが可視化出来るためこのような処理となります。そして購入されたユニットのアーキタイプが、ユニットプロダクションキューへ追加されます。ユニット構築タイマーが開始していない場合、ここでスタートさせてください。

UDKRTSStructure.uc
if (Index >= 0 && Index < BuildablePawnArchetypes.Length && class'UDKRTSPawn'.static.CanBuildPawn(BuildablePawnArchetypes[Index], OwnerReplicationInfo, false))
{
  // Play the building sound
  class'UDKRTSCommanderVoiceOver'.static.PlayBuildingSoundCue(OwnerReplicationInfo);

  // Take resources away
  OwnerReplicationInfo.Resources -= BuildablePawnArchetypes[Index].ResourcesCost;
  OwnerReplicationInfo.Power -= BuildablePawnArchetypes[Index].PowerCost;

  // Update the player controller's HUD actions
  PlayerController = PlayerController(OwnerReplicationInfo.Owner);
  if (PlayerController != None)
  {
    UDKRTSMobileHUD = UDKRTSMobileHUD(PlayerController.MyHUD);
    if (UDKRTSMobileHUD != None)
    {
      SendHUDAction = BuildablePawnArchetypes[Index].BuildHUDAction;
      SendHUDAction.Reference = EHAR_Building;
      SendHUDAction.Index = QueuedUnitArchetypes.Length;
      SendHUDAction.PostRender = true;

      UDKRTSMobileHUD.RegisterHUDAction(Self, SendHUDAction);
    }
  }

  // Add the unit to the queue
  QueuedUnitArchetypes.AddItem(BuildablePawnArchetypes[Index]);

  // Start the building unit timer if it isn't activated
  if (!IsTimerActive(NameOf(BuildingUnit)))
  {
    SetTimer(BuildablePawnArchetypes[Index].BuildTime, false, NameOf(BuildingUnit));
  }
}

前の関数で設定したタイマーによってUDKRTSStructure.BuildingUnit()が呼ばれます。タイマーはユニットの「構築」にかかる時間を処理します。問題のない状況でBuildingUnit()が呼ばれたことが確認されると、最初にHUDアクションリストが更新されます。その後、サーバーによってポーンが要求されます。ここでユニットの実際の構築が処理されます(のちに詳細を説明します)。最後に、プロダクションキューへの最初の入力値が削除されます。更なるユニットがキューされている場合、タイマーを再スタートして、プロセスを再起動してください。

UDKRTSStructure.uc
simulated function BuildingUnit()
{
  local Vector SpawnLocation;
  local Rotator R;
  local UDKRTSMobileHUD UDKRTSMobileHUD;
  local PlayerController PlayerController;
  local int i;
  local SHUDAction SendHUDAction;

  // Check if the structure is able to build a unit
  if (!IsConstructed || QueuedUnitArchetypes.Length <= 0)
  {
    return;
  }

  // Update the HUD action list
  PlayerController = PlayerController(OwnerReplicationInfo.Owner);
  if (PlayerController != None)
  {
    UDKRTSMobileHUD = UDKRTSMobileHUD(PlayerController.MyHUD);
    if (UDKRTSMobileHUD != None && UDKRTSMobileHUD.AssociatedHUDActions.Find('AssociatedActor', Self) != INDEX_NONE)
    {
      UDKRTSMobileHUD.UnregisterHUDActionByReference(Self, EHAR_Building);

      if (QueuedUnitArchetypes.Length > 0)
      {
        for (i = 0; i < QueuedUnitArchetypes.Length; ++i)
   {
     if (QueuedUnitArchetypes[i] != None)
     {
       SendHUDAction = QueuedUnitArchetypes[i].BuildHUDAction;
       SendHUDAction.Reference = EHAR_Building;
       SendHUDAction.Index = i;
       SendHUDAction.PostRender = true;

       UDKRTSMobileHUD.RegisterHUDAction(Self, SendHUDAction);
     }
   }
      }
    }
  }

  // Get the appropriate spawn location
  if (Role == Role_Authority)
  {
    if (RallyPointLocation == Location)
    {
      R.Yaw = Rand(65536);
      SpawnLocation = Location + Vector(R) * (QueuedUnitArchetypes[0].CylinderComponent.CollisionRadius + UnitSpawnRadius);
    }
    else
    {
      SpawnLocation = Location + Normal(RallyPointLocation - Location) * (QueuedUnitArchetypes[0].CylinderComponent.CollisionRadius + UnitSpawnRadius);
    }

    SpawnLocation.Z -= CollisionCylinder.CollisionHeight;
    // Request the pawn
    RequestPawn(QueuedUnitArchetypes[0], SpawnLocation);
  }

  // Remove the unit from the queue
  QueuedUnitArchetypes.Remove(0, 1);

  // If there are still units left in the queue then start the building unit timer again
  if (QueuedUnitArchetypes.Length > 0)
  {
    SetTimer(QueuedUnitArchetypes[0].BuildTime, false, NameOf(BuildingUnit));
  }
}

UDKRTSStructure.RequestPawn()によって、実際のユニットをスポーンします。関数がクライアント側で実行されると、自動的にサーバーと同期されます。最後に、 UDKRTSStructure.HandleRequestForPawn()を呼び出します。そして、UDKRTSStructure.HandleRequestForPawn()がUDKRTSGameInfo.RequestPawn()を呼び出します。UDKRTSGameInfo.RequestPawn()がポーンをスポーンして初期化します。結集点で要求がされた場合、ポーンがその場を離れるように求められます。

UDKRTSGameInfo.uc
function RequestPawn(UDKRTSPawn RequestedPawnArchetype, UDKRTSPlayerReplicationInfo RequestingReplicationInfo, Vector SpawnLocation, bool InRallyPointValid, Vector RallyPoint, Actor RallyPointActorReference)
{
  local UDKRTSPawn UDKRTSPawn;
  local UDKRTSAIController UDKRTSAIController;
  local UDKRTSResource UDKRTSResource;

  if (RequestedPawnArchetype == None || RequestingReplicationInfo == None)
  {
    return;
  }

  UDKRTSPawn = Spawn(RequestedPawnArchetype.Class,,, SpawnLocation + Vect(0.f, 0.f, 1.f) * RequestedPawnArchetype.CylinderComponent.CollisionHeight,, RequestedPawnArchetype);
  if (UDKRTSPawn != None)
  {
    if (UDKRTSPawn.bDeleteMe)
    {
      `Warn(Self$":: RequestPawn:: Deleted newly spawned pawn, refund player his money?");
    }
    else
    {
      UDKRTSPawn.SetOwnerReplicationInfo(RequestingReplicationInfo);
      UDKRTSPawn.SpawnDefaultController();

      UDKRTSAIController = UDKRTSAIController(UDKRTSPawn.Controller);
      if (UDKRTSAIController != None)
      {
        if (RallyPointActorReference != None)
        {
          UDKRTSResource = UDKRTSResource(RallyPointActorReference);
          if (UDKRTSResource != None && UDKRTSPawn.HarvestResourceInterval > 0)
          {
            UDKRTSAIController.HarvestResource(UDKRTSResource);
          }
        }
        else if (InRallyPointValid)
        {
          UDKRTSAIController.MoveToPoint(RallyPoint);
        }
      }
    }
  }
}

それではスポーンされたポーンは、プレイヤーのチームにどのように収まるのでしょうか?これはポーンのオーナーレプリケーション情報を設定することによって処理が出来ます。この情報がサーバー側で呼ばれると、OwnerReplicationInfo が設定されます。OwnerReplicationInfo自体が通知された変数のレプリケーションなので、クライアントにレプリケーションされた時、ReplicatedEvent()がSetOwnerReplicationInfo()を呼びます。ポーンのマテリアルが、オーナーのレプリケーション情報がTeamInfoをまだレプリケーションしていないという理由で更新されていない場合、チーム情報をポーリングするためループが設定されます。オーナーのレプリケーション情報であるTeamInfo変数がレプリケーションされると、ループタイマーが無効となり、チームカラーがポーンのために設定されます。サーバー側のポーンは、武器がインスタンス化されて付与されます。最後に、これでポーンがプレイヤーの所有となったことを告げるメッセージがクライアントへ送信されます。上述にあるように、プレイヤーのチームのポーンは自動的にスクリーン上の境界ボックスが計算され、好きなように操ることが出来ます。

UDKRTSPawn.uc
simulated function SetOwnerReplicationInfo(UDKRTSPlayerReplicationInfo NewOwnerReplicationInfo)
{
  local UDKRTSTeamInfo UDKRTSTeamInfo;

  if (NewOwnerReplicationInfo == None)
  {
    return;
  }

  // Unit is possibly being converted to another team
  if (OwnerReplicationInfo != None && OwnerReplicationInfo != NewOwnerReplicationInfo)
  {
    UDKRTSTeamInfo = UDKRTSTeamInfo(OwnerReplicationInfo.Team);
    if (UDKRTSTeamInfo != None)
    {
      UDKRTSTeamInfo.RemovePawn(Self);
    }
  }

  // Assign the team
  OwnerReplicationInfo = NewOwnerReplicationInfo;
  if (!UpdateTeamMaterials())
  {
    SetTimer(0.1f, true, NameOf(CheckTeamInfoForOwnerReplicationInfo));
  }

  // Give the pawn its default weapon, if it doesn't have one right now
  if (Role == Role_Authority && WeaponArchetype != None && UDKRTSWeapon == None)
  {
    UDKRTSWeapon = Spawn(WeaponArchetype.Class, Self,, Location, Rotation, WeaponArchetype);
    if (UDKRTSWeapon != None)
    {
      UDKRTSWeapon.SetOwner(Self);
      UDKRTSWeapon.UDKRTSWeaponOwnerInterface = UDKRTSWeaponOwnerInterface(Self);
      UDKRTSWeapon.Initialize();
      UDKRTSWeapon.AttachToSkeletalMeshComponent(Mesh, LightEnvironment, WeaponSocketName);
    }
  }

  // Send the client a world message that the pawn was trained
  OwnerReplicationInfo.ReceiveWorldMessage(FriendlyName@"trained.", class'HUD'.default.WhiteColor, Location, Portrait.Texture, Portrait.U, Portrait.V, Portrait.UL, Portrait.VL);
  class'UDKRTSCommanderVoiceOver'.static.PlayUnitReadySoundCue(OwnerReplicationInfo);
}

建造物作成をどのように処理するか?

スターターキットでは、ポーンのみが建造物を作成出来る仕組みです。通常は、プレイヤーがポーンに何かをさせたい時、「コマンド」メッシュが表示されます。動作が白い中空円として可視化されます。プレイヤーがストラクチャーアイコンのどれかを押すと、コマンドメッシュをプレイヤーが構築しようとしている建造物の透過バージョンへと変換します。 

UDKRTSPawn.uc
simulated function HandleHUDAction(EHUDActionReference Reference, int Index)
{
  // Snip
  // Build commands
  case EHAR_Build:
    if (Index >= 0 && Index < BuildableStructureArchetypes.Length)
    {
      CommandMesh.SetSkeletalMesh(BuildableStructureArchetypes[Index].PreviewSkeletalMesh);
      CommandMode = ECM_BuildStructure;
    }
    break;

  // Snip
}

プレイヤーがタッチスクリーン上で指を動かすと、コマンドメッシュの位置が更新されます。その位置に建造物の作成が可能か否かを示すため、建造物の色を変更する必要があります。これはHUDの更新ごとに呼ばれるUDKRTSPawn.SetCommandMeshTranslation()関数で処理されます。

UDKRTSPawn.uc
simulated function SetCommandMeshTranslation(Vector NewTranslation, bool NewHide)
{
  // Snip
  case ECM_BuildStructure:
    // Check if any buildings are within radius, if so, turn it red to signify that we cannot build here
    if (CommandIndex >= 0 && CommandIndex < BuildableStructureArchetypes.Length)
    {
      CanBuildStructure = true;

      ForEach VisibleCollidingActors(class'Actor', Actor, BuildableStructureArchetypes[CommandIndex].PlacementClearanceRadius, NewTranslation, true,, true)
      {
        CanBuildStructure = false;
        break;
      }

      Material = (CanBuildStructure) ? BuildableStructureArchetypes[CommandIndex].CanBuildMaterial : BuildableStructureArchetypes[CommandIndex].CantBuildMaterial;
    }
    break;
  // Snip
}

このスターターキットのモバイルバージョンはアクションのコントロールにHUDクラスを使用するので、プレイヤーの指がタッチスクリーンから離れた時にコードが実行されて、希望のアクションが実行されます。ここでは建造物の作成を実行します。スターターキットは、UDKRTSMobilePlayerControllerから建造物を要求して処理をし、実際にポーンがその位置で建造物を作成しているかのように見せます。UDKRTSMobilePlayerController は要求があった場合、自動的にサーバーと同期されるように設定され、最後にUDKRTSGameInfo.RequestStructure()を呼びます。

UDKRTSMobileHUD.uc
event PostRender()
{
  // Snip
          case ECM_BuildStructure:
            if (PlayerUDKRTSTeamInfo.Pawns[i] != None)
            {
              PlayerUDKRTSTeamInfo.Pawns[i].HasPendingCommand = false;
              // Playback the pawn confirmation effects and sounds
              PlayerUDKRTSTeamInfo.Pawns[i].ConfirmCommand();
              // Deproject the pending screen command location
              Canvas.Deproject(PlayerUDKRTSTeamInfo.Pawns[i].PendingScreenCommandLocation, CurrentWorldLocation, CurrentWorldDirection);
              // Find the world location for the pending move location
              ForEach TraceActors(class'UDKRTSCameraBlockingVolume', UDKRTSCameraBlockingVolume, HitCurrentWorldLocation, HitNormal, CurrentWorldLocation + CurrentWorldDirection * 65536.f, CurrentWorldLocation)
              {
                // Request the structure
                UDKRTSMobilePlayerController.RequestStructure(PlayerUDKRTSTeamInfo.Pawns[i].BuildableStructureArchetypes[PlayerUDKRTSTeamInfo.Pawns[i].CommandIndex], HitCurrentWorldLocation);
                // Move the pawn there
                UDKRTSMobilePlayerController.GiveMoveOrder(HitCurrentWorldLocation + Normal(PlayerUDKRTSTeamInfo.Pawns[i].Location - HitCurrentWorldLocation) * PlayerUDKRTSTeamInfo.Pawns[i].BuildableStructureArchetypes[PlayerUDKRTSTeamInfo.Pawns[i].CommandIndex].CollisionCylinder.CollisionRadius * 1.5f, PlayerUDKRTSTeamInfo.Pawns[i]);
                break;
              }
            }
            break;
  // Snip
}

最初にUDKRTSGameInfo.RequestStructure()によって、そこに建造物が作成できるかをチェックします。この処理は再度サーバー側で処理されますが、クライアント側で処理されたかのように見えます。しかし、クライアント側が権限を持つことはいかなる場合も良いアイデアとは言えないので、不正を防止するためにもサーバー側で処理されます。建造物がスポーンされて、建造物を作成した人にオーナーのレプリケーション情報が設定されます。

UDKRTSGameInfo.uc
function UDKRTSStructure RequestStructure(UDKRTSStructure RequstedStructureArchetype, UDKRTSPlayerReplicationInfo RequestingReplicationInfo, Vector SpawnLocation)
{
  local UDKRTSStructure UDKRTSStructure;
  local Actor Actor;
  local UDKRTSMobilePlayerController UDKRTSMobilePlayerController;

  // Check object variables
  if (RequstedStructureArchetype == None || RequestingReplicationInfo == None)
  {
    return None;
  }

  // Check that there are no nearby actors blocking construction
  ForEach VisibleCollidingActors(class'Actor', Actor, RequstedStructureArchetype.PlacementClearanceRadius, SpawnLocation, true,, true)
  {
    class'UDKRTSCommanderVoiceOver'.static.PlayCannotDeployHereSoundCue(RequestingReplicationInfo);

    UDKRTSMobilePlayerController = UDKRTSMobilePlayerController(RequestingReplicationInfo.Owner);
    if (UDKRTSMobilePlayerController != None)
    {
      UDKRTSMobilePlayerController.ReceiveMessage("Cannot deploy here.");
    }
    return None;
  }

  // Check that the player is able to build this structure
  if (!class'UDKRTSStructure'.static.CanBuildStructure(RequstedStructureArchetype, RequestingReplicationInfo, true))
  {
    return None;
  }

  // Spawn the structure
  UDKRTSStructure = Spawn(RequstedStructureArchetype.Class,,, SpawnLocation + Vect(0.f, 0.f, 1.f) * RequstedStructureArchetype.CollisionCylinder.CollisionHeight,, RequstedStructureArchetype, true);
  if (UDKRTSStructure != None)
  {
    RequestingReplicationInfo.Resources -= RequstedStructureArchetype.ResourcesCost;
    RequestingReplicationInfo.Power -= RequstedStructureArchetype.PowerCost;

    UDKRTSStructure.SetOwnerReplicationInfo(RequestingReplicationInfo);
  }

  return UDKRTSStructure;
}

サーバー側で建造物がスポーンされると、クライアント側にレプリケートされます。デフォルトの建造物は目に見えないアクタであるため、プレイヤーからは見ることができません。プレイヤーに属するユニット(部隊)が建造物範囲内に入ると、建造物が作成されます。これは建造物を建てたい場所へユニットが移動するシチュエーションをエミュレートしたものです。建設プロセスが開始すると、CompleteConstruction()と呼ばれるタイマーがスタートします。

UDKRTSStructure.uc
simulated function Tick(float DeltaTime)
{
  // Snip
    // Check if the building is waiting for a pawn to start construction
    else if (WaitingForPawnToStartConstruction)
    {
      // Scan for near by pawns
      ForEach VisibleCollidingActors(class'UDKRTSPawn', UDKRTSPawn, CollisionCylinder.CollisionRadius * 1.5f, Location, true,, true)
      {
        // Check that the pawn is on our team
        if (UDKRTSPawn != None && OwnerReplicationInfo != None && UDKRTSPawn.OwnerReplicationInfo != None && UDKRTSPawn.OwnerReplicationInfo.Team == OwnerReplicationInfo.Team)
        {
          // Start building the structure
          CreateNavMeshObstacle();
          SetHidden(false);
          WaitingForPawnToStartConstruction = false;
          SetDrawScale3D(Vect(1.f, 1.f, 0.01f));
          SetTimer(ConstructionTime, false, NameOf(CompleteConstruction));
          break;
        }
      }
    }
  // Snip
}

AIはどのように機能するか?

AIはループタイマーが設定されたAIControllerです。別の何かが発生した時に、AIに何かをさせるためにポーンや建造物が呼び出すことが出来る通知機能があります。例えば建造物が破損した場合、建造物がAIに破損した状況を伝えることによって、AIによって最善の「対処」がされることを可能にします。

UDKRTSStructure.uc
event TakeDamage(int DamageAmount, Controller EventInstigator, vector HitLocation, vector Momentum, class<DamageType> DamageType, optional TraceHitInfo HitInfo, optional Actor DamageCauser)
{
  // Snip
      // If the owner is an AI, then notify the AI that its base is under attack
      UDKRTSTeamAIController = UDKRTSTeamAIController(OwnerReplicationInfo.Owner);
      if (UDKRTSTeamAIController != None)
      {
        UDKRTSTeamAIController.NotifyStructureDamage(EventInstigator, Self);
      }
 // Snip
}

所有するTeamAIControllerへ通知が送られると、近くにユニットが存在する場合、TeamAIControllerが建造物を防御しようと試みます。

UDKRTSTeamAIController.uc
function NotifyStructureDamage(Controller EventInstigator, UDKRTSStructure Structure)
{
  local int i;
  local float Distance;
  local UDKRTSAIController UDKRTSAIController;
  local UDKRTSTargetInterface PotentialTarget;

  // Check parameters
  if (CachedUDKRTSTeamInfo == None || EventInstigator == None || EventInstigator.Pawn == None)
  {
    return;
  }

  if (CachedUDKRTSTeamInfo.Pawns.Length > 0)
  {
    // Find the potential target
    PotentialTarget = UDKRTSTargetInterface(EventInstigator.Pawn);
    if (PotentialTarget != None)
    {
      for (i = 0; i < CachedUDKRTSTeamInfo.Pawns.Length; ++i)
      {
        // For all healthy pawns under my control within a range of 1024 uu's away, engage the attacker!
        if (CachedUDKRTSTeamInfo.Pawns[i] != None && CachedUDKRTSTeamInfo.Pawns[i].Health > 0)
        {
          Distance = VSize(CachedUDKRTSTeamInfo.Pawns[i].Location - Structure.Location);
          if (Distance <= 1024.f)
          {
            UDKRTSAIController = UDKRTSAIController(CachedUDKRTSTeamInfo.Pawns[i].Controller);
            if (UDKRTSAIController != None && UDKRTSAIController.EnemyTargetInterface == None)
            {
              UDKRTSAIController.EngageTarget(EventInstigator.Pawn);
            }
          }
        }
      }
    }
  }
}

さもなければループタイマーによるAIの現状ステートの再割り当てがされ、ポーンと建造物の更なる作成、プレイヤーを攻撃するか否か等のコントロールがされます。RTSスターターキットは、ゲームデザイナーがAIに微調整を加えることを可能とした変数を含むアーキタイプオブジェクトを使用しています。アーキタイプオブジェクトは、順序通り建造物を作成可能か、また破壊された建造物と差し替えるか、ハーベスターをもっと増やすか、軍隊の規模を拡大するかといった、AIが判断を下すための建造物の作成順序情報等を格納しています。これはプレイヤーを油断させずにおくために、様々なことに優先度をつける色々なタイプのAIを作成する際に有益です。

アップグレードシステムはどのように機能するか?

アップグレードシステムは、サーバーとクライアントを終了するレプリケートされたアクタを持つことによって機能します。ベースのアップグレードクラスであるUDKRTSUpgradeは、特定の何かではなくアップグレードがもたらすブーストを単に格納します。武器の発砲、ポーン、建造物の破損、移動速度等が計算された時、サーバーが現存するアップグレードアクタをチェックして、エフェクトを適用します。例として、プレイヤーがアーマーのアップグレードを検索した時やプレイヤーのポーンが打撃を受けた時、何が起こるかを調べてみてください。

UDKRTSPawn.uc
function AdjustDamage(out int InDamage, out vector Momentum, Controller InstigatedBy, vector HitLocation, class<DamageType> DamageType, TraceHitInfo HitInfo, Actor DamageCauser)
{
  local UDKRTSTeamInfo UDKRTSTeamInfo;
  local int i;

  Super.AdjustDamage(InDamage, Momentum, InstigatedBy, HitLocation, DamageType, HitInfo, DamageCauser);

  // Check if the unit has any defensive bonuses
  if (DefensiveBonus > 0.f)
  {
    InDamage = FClamp(1.f - DefensiveBonus, 0.f, 1.f) * InDamage;
  }

  // Check if the owning team has any unit armor bonuses
  if (OwnerReplicationInfo != None)
  {
    UDKRTSTeamInfo = UDKRTSTeamInfo(OwnerReplicationInfo.Team);
    if (UDKRTSTeamInfo != None)
    {
      for (i = 0; i < UDKRTSTeamInfo.Upgrades.Length; ++i)
      {
        if (UDKRTSTeamInfo.Upgrades[i] != None && UDKRTSTeamInfo.Upgrades[i].UnitArmourBoost > 0.f)
        {
          InDamage = InDamage * (1.f - UDKRTSTeamInfo.Upgrades[i].UnitArmourBoost);
        }
      }
    }
  }
}

お分かりのように、アップグレードは与えられたダメージを修正して適用されます。当然のことながら、興味深いアップグレードを可能にするため、よりオブジェクト指向にすることで複雑化することは出来ますが、RTSスターターキットにはよりシンプルな方法が適用されました。

武器システムはどのように機能するか?

武器はファイヤーモードをもつ、簡易化されたアクタです。武器に含まれる多くの機能がほとんど必要とされないため、ここでは武器をベースクラスとして使用していません。

武器自体が発砲をコントロールすることはありませんが、いつ発砲されるべきか、発砲するべきかといったコントロールを担うゲートウェイの機能を持ちます。WeaponFireModeオブジェクトはUnreal Editorで作成され、どのように発砲されるかをコントロールします。WeaponFireMode内の既存のアーキタイプとパラメータを修正することによって、デザイナーによる新しい武器のカスタマイズを可能にします。また、WeaponFireModeをサブクラス化することによって、システム拡張の柔軟性をプログラマーに提供します。このスターターキットでは、二つのWeaponFireMode、アクタへのダメージ実行にトレースを使用するUDKRTSInstantHitWeaponFire 、発射物をスポーンして、アクタのコリジョンの際にダメージを与えるUDKRTSProjectileWeaponFire が存在します。

コマンダー ボイスオーバー システムはどのように機能するか?

コマンダー ボイスオーバー システムは、UDKRTSCommanderVoiceOver という名の静的オブジェクトです。コンテンツ パッケージ内に格納されているアーキタイプとリンクしているため、ゲームデザイナーが、アーキタイプを変更することができるとともに、変更を実行時に適用することができます。本スターターキットがコマンダーボイスを再生する場合は、Play*SoundSlot*SoundCue 関数のいずれかを呼び出します。すると、その関数が UDKRTSCommanderVoiceOver 内にある PlaySoundCue を呼び出します。PlaySoundCue 関数は、サウンドを再生すべきか否かをチェックし、最終的に PlayerReplicationInfo を所有するコントローラ上にある PlaySound を呼び出します。bool 型によって、サウンドが他のクライアントにレプリケートされないようにします。

UDKRTSCommanderVoiceOver.uc

/**
 * Plays the building sound
 *
 * @param      PlayerReplicationInfo      Who to play the sound for
 */
final static function PlayBuildingSoundCue(PlayerReplicationInfo PlayerReplicationInfo)
{
  PlaySoundCue(PlayerReplicationInfo, default.CommanderVoiceOverArchetype.Building);
}

/**
 * Plays the sound cue
 *
 * @param      PlayerReplicationInfo      Who to play the sound for
 * @param      SoundCue               Sound cue to play
 */
final static function PlaySoundCue(PlayerReplicationInfo PlayerReplicationInfo, SoundCue SoundCue)
{
  local AIController AIController;
  local WorldInfo WorldInfo;

  // Check if we're on the dedicated server
  WorldInfo = class'WorldInfo'.static.GetWorldInfo();
  if (WorldInfo != None && WorldInfo.NetMode == NM_DedicatedServer)
  {
    return;
  }

  // Check object references
  if (PlayerReplicationInfo == None || SoundCue == None || PlayerReplicationInfo.Owner == None)
  {
    return;
  }

  // If the player replication info belongs to an AI controller, then abort
  AIController = AIController(PlayerReplicationInfo.Owner);
  if (AIController != None)
  {
    return;
  }

  PlayerReplicationInfo.Owner.PlaySound(SoundCue, true, true, true,, true);
}

新たなサウンドを追加するには、UDKRTSCommanderVoiceOver 内に新たなフィールドを追加して、新たな静的関数を作成するだけです。クラス内の関数の数を「削減」するために、インデックス ベースのシステムを使用することは、もちろん可能ですが、コードの明確さを保つためにフル関数名が使用されています。

任意の場所から静的関数を呼び出すことによってサウンドを再生することができます。ただし、PlayerReplicationInfo の参照が必要となります。

UDKRTSStructure.uc

// Play the building sound
class'UDKRTSCommanderVoiceOver'.static.PlayBuildingSoundCue(OwnerReplicationInfo);

音楽はどのように再生されるか?

このスターターキットは、iPad2 などの iOS デバイスを対象としているため、音楽は、 MP3 を使用して再生されます。本スターターキットのサンプルマップでは PlayMusicTrack Kismet ノードが使用されて、音楽が開始されます。音楽は SoundCue (サウンドキュー) としても保存されているため PC 上で聴くことができます。

本スターターキットを利用してゲームをどのように作成するか?


このスターターキットにはゲームがすでに内蔵されているので、新規の建造物、ユニット、そして武器の作成をすぐに開始出来ます。しかし、ゲームプレイのロジックを新規に作成するには、スターターキットの修正が必要となります。建造物やユニット、武器のほとんどはデータ駆動型であるため、多くのプロパティ を定義するには、アーキタイプが使用されます。

新たな建造物の作成方法

[Content Browser] 内の[Actor Classes]タブから作成を開始します。アクタクラスツリー内でUDKRTSStructureを検索します。 その上を右クリックして[Create New Archetype]をクリックします。 https://udn.epicgames.com/pub/Three/DevelopmentKitGemsRTSStarterKit UDKRTS_CreateStructure_CreateArchetype_01.png

[Content Browser]から作成したい建造物のアーキタイプを選択して、その上をダブルクリック、プロパティを開きます。

UDKRTS_CreateStructure_FindArchetypeInContentBrowser_02.png

ここからゲームデザイナーが使用可能な変数を検索してみてください。大多数の変数はコメント付がされているので、機能に確証がない場合は変数名の上にカーソルを置いてみてください。例えば、他の建造物のアーキタイプはUDKRTSGameContent.Archetypesパッケージで見ることが出来ます。

UDKRTS_CreateStructure_StructureArchetypeProperties_03.png

様々な建造物のプロパティを試した後に、建造物をマップでインスタンス化、ユニットアーキタイプ内の[Buildable Structure](建築可能な建造物)の配列にアーキタイプを追加(ユニットが建物を構築できるように)、そしてAIの建築順序に追加する必要があります。

マップ上でのインスタンス化は、[Content Browser]で建造物のアーキタイプを選択し、ワールドビューポートで右クリック、[Add Archetype] <Your archetype name> をクリックするだけです。インスタンス化する建造物の[Starting Team Index]変数は、建造物の所有者を特定するために、マップ開始の際に変更することを忘れないでください。

UDKRTS_CreateStructure_InstanceArchetype_04.png

ユニットアーキタイプの[Buildable Structure]配列に追加するには、[Content Browser]からご自身のユニットアーキタイプを検索してください。アーキタイププロパティウィンドウを表示するため、その上をダブルクリックします。[Ability]カテゴリを展開して、緑のプラスシンボルを押して、[Buildable Structure Archetypes]に新規入力します。[Content Browser]から建造物のアーキタイプを選択して、[Buildable Structure Archetypes]配列にある緑の矢印シンボルをクリックして割り当てます。これで選択時には、ユニットのHUDアクションの際に新しい構造物が表示されます。 

UDKRTS_CreateStructure_AddAsBuildableArchetype_05.png

AIを使用して新しい建造物を構築したい場合は、[Structure Build Order]配列へ追加してください。この配列は、UDKRTSGameContent.Archetypes.パッケージ内のAIPropertiesアーキタイプに格納されます。 

UDKRTS_CreateStructure_AddInAIBuildOrder_06.png

新たなユニットの作成方法(ポーン)

[Content Browser]の[Actor Classes]タブから開始します。アクタクラスツリー内でUDKRTSPawnを検索します。その上を右クリック、[Create New Archetype]をクリックします。

UDKRTS_CreateUnit_CreateArchetype_01.png

新規作成したポーンのアーキタイプを[Content Browser]内で探して、その上をダブルクリック、プロパティを開きます。

UDKRTS_CreateUnit_FindArchetypeInContentBrowser_02.png

ここから、ゲームデザイナーが活用できる様々なカテゴリをご覧いただけます。ほとんどのカテゴリはコメント付がされていますので、機能に確証がない場合は、その上にカーソルを置いて確認していただけます。例えば、UDKRTSGameContent.Archetypes パッケージで、他のポーンに使用されているアーキタイプをご覧いただけます。

UDKRTS_CreateUnit_UnitArchetypeProperties_03.png

ご自身のユニットに武器を追加したい場合、既存の武器のアーキタイプを探すか、新規にアーキタイプを作成します。[Content Browser]から選択します。そして[Weapon Archetype]フィールドに設定します。

UDKRTS_CreateUnit_SetWeapon_04.png

AIがどのようなユニットを構築するべきかのヒントはありませんが、AIは作成されている建造物を全て 検索して、現在のニーズに合ったユニットタイプを探します。リソースポイントの獲得や、ランクの高い軍隊等、ユニットによる作成の可能性を高める様々なプロパティを設定してください。ユニットのアーキタイプを建造物の[Buildable Pawns Array]へ追加しておくとよいでしょう。さもなければプレイヤーやAIがユニットを構築する際にアクセス出来ません!

これを行うためには、適切な建造物のアーキタイプを探します。[Structure]カテゴリを展開し、緑のプラスシンボルを押して、[Buildable Pawn Archetypes]配列に新たに入力します。[Content Browser]で自身のポーンアーキタイプを選択し、[Buildable Pawn Archetypes]配列にある緑の矢印シンボルをクリックして割り当てます。これをすることによって、建造物のHUDアクションの選択時に、新しいユニットが表示されます。

UDKRTS_CreateUnit_AddAsBuildableArchetype_05.png

新たな武器の作成方法

[Content Browser]にある[Actor Classes]タブから開始します。アクタクラスツリー内でUDKRTSWeaponを検索します。その上を右クリック、[Create New Archetype]をクリックします。

UDKRTS_CreateWeapon_CreateArchetype_01.png

[Content Browser]で新しい武器のアーキタイプを検索して、その上をダブルクリック、プロパティを開きます。

UDKRTS_CreateWeapon_FindArchetypeInContentBrowser_02.png

新しい発射モードを作成するには[Weapon]カテゴリを展開して、青矢印を押して[Context]メニューを開きます。ヒットスキャン(即着弾)ベースの武器を作成したい場合はUDKRTSInstantHitWeaponFire、発射物ベースの武器を作成したい場合はUDKRTSProjectileWeaponFireを使用してください(この発射モードを使用するにはUDKRTSProjectileアーキタイプを作成するか、UDKRTSProjectileを検索してください。)

UDKRTS_CreateWeapon_CreateWeaponFireMode_03.png

ここから、ゲームデザイナーが活用できる様々なカテゴリをご覧いただけます。ほとんどのカテゴリはコメント付がされていますので、機能に確証がない場合は、その上にカーソルを置いて確認していただけます。例えば、UDKRTSGameContent.Archetypes パッケージで、他のポーンに使用されているアーキタイプをご覧いただけます。

武器がユニットに設定されている場合、ユニットが自動的に付与されている武器をスポーンして準備が整っている状態です。

このスターターキットの使用方法


  1. UDKをダウンロード します。
  2. UDKをインストールします。
  3. zipファイルをダウンロードします。
  4. コンテンツをUDKベースのディレクトリで解凍します。(  C:\Projects\UDK-2011-11\)Windowsが既存ファイル、もしくはフォルダの上書きとなることを通知するかもしれません。全てのメッセージに対してOKをクリックします。
      UDKRTS_Install_Unzipping_01.png
    UDKRTS_Install_OverrideAllConflicts_02.png
  5. Notepad(メモ帳)で、 UDKGame\Config ディレクトリにある DefaultEngine.ini を開きます。( C:\Projects\UDK-2011-10\UDKGame\Config\DefaultEngine.ini)
      UDKRTS_Install_FindDefaultEngine_03.png
  6. EditPackages を検索します。
    UDKRTS_Install_FindEditPackages_04.png
  7. +EditPackages=UDKRTSGame を追加します。
    UDKRTS_Install_AddEditPackage_05.png
  8. Binaries ディレクトリにある Unreal Frontend Application をロンチします( C:\Projects\UDK-2011-11\Binaries\UnrealFrontend.exe)
    UDKRTS_Install_FindUnrealFrontEnd_06.png
  9. Script をクリックして、その後 Full Recompile をクリックします。
      UDKRTS_Install_CompileScripts_07.png
  10. UDKRTSGameパッケージが最後にコンパイルされて表示されます。
      UDKRTS_Install_ScriptsCompiled_08.png
  11. UnrealEd をクリックして、Unreal Editorを開きます。
    UDKRTS_Install_RunUnrealEd_09.png
  12. Open ボタンをクリックして、 RTSExampleMap.udk を開きます。
    UDKRTS_Install_OpenMap_10.png
  13. トップダウン(上からの)カメラモードであることを確認してから、 Play In Editor ボタンをクリックしてRTSスターターキットを起動します。(必ずモバイルエミュレーションを有効にしてください。0から9のキーはHUDアクションに使用されます)
     
  14. iDeviceへエクスポートして、そこでRTSスターターキットを起動することも出来ます。(この時に、開発プロビジョンの設定を必ず行ってください)
    UDKRTS_Install_Run_11.png

RTSキット設定は、作成したいゲームに近づいた調整が可能となりました。UnrealscriptコードはDevelopment\Src\UDKRTSGame\Classes\*.ucに格納されていて、全てのコンテンツは UDKGame\Content\UDKRTS\Content\UDKRTSGameContent.upk に格納されています。 

ダウンロード


* UDK-2012-01 用 UnrealScript パッチの ダウンロード
    • enum EZoneTouchEvent から ETouchType への変更を修正する UDKRTSMobilePlayerController.uc の代わりになるパッチです。
* UDK-2012-05用 UnrealScriptパッチのダウンロード
    • 初回のタッチシミュレーションのみが処理されるバグを修正する「UDKRTSMobilePlayerController.uc」の代わりになるパッチです。