UDN
Search public documentation:

KismetOnlineSubsystemJP
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 > Kismet オンライン サブシステム

Kismet オンライン サブシステム


UDK での前回テスト 2012年1月
PC 対応
iOS 対応

概要


Kismet は、ノードを使用してゲームプレイ ロジックを構築することができるように設計されているビジュアル スクリプティング システムです。プロトタイプの段階から生産段階まで一貫してとても役に立つツールです。ゲームプレイ ロジックのうちいくつかが Kismet 内に存在するため、Kismet から Game Center (ゲームセンター) や Steam Works にアクセスできるようにしたくなるのは当然なことです。この開発キット文書では、新たな Kismet ノードを追加して、Game Center および Steam Works と接続できるようにします。

ALERT! 注意 : 提供されている UnrealScript では、Game Center および Steam Works のために同一の Kismet が定義されていますが、一度に使用できるのはそのうちどちらか一方だけです。その理由は、プリプロセッサが UnrealScript を特定のバージョンに変換するためです。このことは、Game Center および Steam Works が互いと異なる機能をもつために必要となります。したがって、マルチプラットフォームのゲームを作成する場合は、Kismet の異なる 2 つのバージョンを作成する必要があります。そのためには、Game Center Kismet ノードを含むストリーミングレベルと、Steam Works Kismet ノードを含む別のストリーミングレベルを作成するのが最善の方法となります。

UnrealScript のプリプロセシングについての説明


UnrealScript のプリプロセッサの機能は、C++ などの他の言語におけるプリプロセッサの機能と非常によく似ています。プリプロセッサは、if 文および else 文に基づいて UnrealScript に含める / から除外する部分および定数を定義する非常に簡単なスクリプトです。

次のコードのスニペットでは、コードが 2 つのセクションに分かれています。それらセクションは、定義されている定数に基づいてプリプロセッサに含まれることになります。 USE_STEAMWORKS が定義されている場合は、if 文内にあるすべてがコンパイルに含まれます。反対に、 USE_STEAMWORKS が定義されていない場合は、else 文内にあるすべてがコンパイルに含まれます。Game Center と Steam Works の機能が異なるため、コードが動くためには異なる経路が必要となりました。

SeqAct_UnlockAchievement.uc
/**
 * Called when this sequence action should do something attached with a local user num
 */
protected function InternalOnActivated()
{
  local OnlineSubsystem OnlineSubsystem;

  // Connect to the online subsystem and link up the achievement delegates
  OnlineSubsystem = class'GameEngine'.static.GetOnlineSubsystem();
  if (OnlineSubsystem != None && OnlineSubsystem.PlayerInterface != None)
  {
// ==========
// SteamWorks
// ==========
`if(`isdefined(USE_STEAMWORKS))
    // Begin unlocking the achievement
    BeginUnlockAchievement();

// ==========
// GameCenter
// ==========
`else
    // Game Center requires you to read the achievements list first, and then unlock the achievement.
    // Assign the read achievements delegate
    OnlineSubsystem.PlayerInterface.AddReadAchievementsCompleteDelegate(ProcessingPlayers[0].LocalUserNum, InternalOnReadAchievementsComplete);

    // Read all achievements
    OnlineSubsystem.PlayerInterface.ReadAchievements(ProcessingPlayers[0].LocalUserNum);
`endif
  }
}

USE_STEAMWORKS および USE_GAMECENTER は、Globals.uci というファイルで定義されます。使用する必要がない方をコメントアウトしてください。Globals.uci を改変すると、UnrealScript のコンパイラがこれを自動的に拾わないようになります。したがって、変更する場合は、UnrealScript を強制的に再コンパイルする必要があります。

ガーベジコレクションについての説明


オンライン サブシステムが実行する多数のタスクは非同期で実行されます。したがって、多数のタスクは、タスク終了時に呼び出されるデリゲートを使用します。Kismet ノード自体は、デリゲートにバインドされています。デリゲートが、適切にクリーンアップされなければ、レベルがガーベジコレクトされずにメモリから除去されなくなります。 GameKismetSceneClient というインタラクションが使用されることによって、ゲームのセッション終了が検知され、Kismet ノードすべてが自身をクリーンアップするように求められます。

GameKismetSceneClient.uc
/**
 * A GameKismetSceneClient is an interaction which watches when a game session ends. This is required to clean up all of the Kismet nodes that may have
 * bindings to the OnlineSubsystems
 *
 * Copyright 1998-2012 Epic Games, Inc. All Rights Reserved.
 */
class GameKismetSceneClient extends Interaction;

/**
 * Called when the current game session has ended. Cleans up all OnlineSubsystemBase Kismet nodes
 */
function NotifyGameSessionEnded()
{
  local Sequence GameSequence;
  local array<SequenceObject> SequenceObjects;
  local SeqAct_OnlineSubsystemBase SeqAct_OnlineSubsystemBase;
  local int i;
  local WorldInfo WorldInfo;

  // Get the world info
  WorldInfo = class'WorldInfo'.static.GetWorldInfo();
  if (WorldInfo == None)
  {
    return;
  }

  // Clean up all of the online subsystem base Kismet nodes
  GameSequence = WorldInfo.GetGameSequence();
  if (GameSequence != None)
  {
    GameSequence.FindSeqObjectsByClass(class'SeqAct_OnlineSubsystemBase', true, SequenceObjects);
    if (SequenceObjects.Length > 0)
    {
      for (i = 0; i < SequenceObjects.Length; ++i)
      {
        SeqAct_OnlineSubsystemBase = SeqAct_OnlineSubsystemBase(SequenceObjects[i]);
        if (SeqAct_OnlineSubsystemBase != None)
        {
          SeqAct_OnlineSubsystemBase.CleanUp(true);
        }
      }
    }
  }
}

defaultproperties
{
}

Steam Works および Game Center Kismet ノード


SeqAct_OnlineSubsystemBase

SeqAct_OnlineSubsystemBase は、他すべての Kismet Sequence Action のベースとなる抽象クラスです。この基本クラスは、オンラインサブシステムの命令のキュー、および、プレイヤー情報の整理、クリーンアップを扱います。また、出力リンクのコールのほとんども処理します。オンライン サブシステムのいずれかを利用する Kismet Sequence Action を新たに作成する必要がある場合は、このクラスをサブクラス化するのが最善です。Kismet Sequence Action がアクティベートされると、アタッチされている Kismet Variable Players ノードに基づいて、作用する必要があるプレイヤーの配列をコンパイルします。さらに、順次、プレイヤーの配列を処理します。この Kismet Sequence Action は、ただちに Out に出力します。Kismet Sequence Action がプレイヤーの処理にビジーである場合は、ただちに Busy (ビジー) に出力します。サブクラス化された Kismet Sequence Action の結果に応じて、その後、Succeeded (成功) あるいは Failed (失敗) に出力する可能性があります。

関数

  • Activated - Kismet Sequence Action が別の Kismet Sequence ノードによってアクティベートされる場合に、このイベントが呼び出されます。
  • FinishedProcessPlayerIndex - プレイヤーのためにすべきことを処理し終えた時に、この関数がサブクラスから呼び出されます。 bWasSuccessful が true の場合は、Succeeded 出力がアクティベートされます。true でない場合は、Failed 出力がアクティベートされます。サブクラス化された Kismet ノードによってすべてのプレイヤーが一気に処理された場合に、bProcessedAllPlayers をセットします。
  • InternalOnActivated - この関数は、Activated 内で呼び出されるスタブ関数です。この関数はサブクラスが拡張するためのものです。サブクラスは、Activated イベントを拡張してはならないためです。
  • CleanUp - この関数は、GameKismetSceneClient からと、FinishedProcessPlayerIndex 内から呼び出されます。この関数は、デリゲートにバインドしているサブクラス、または、他の種類のクリーンアップを必要とするサブクラスによって拡張されるべきです。IsExiting が true の場合は、あらゆるものをクリーンアップします。
  • NotifyGameKismetSceneClientCreated - この関数は、他の SeqAct_OnlineSubsystemBase に呼び出されて、GameKismetSceneClient がすでに作成されていることを示します。SeqAct_OnlineSubsystemBase が最初にアクティベートされると、ローカルのプレイヤーのビューポートに挿入すべき GameKismetSceneClient をまず作成しようとします。それに成功すると、他のすべての SeqAct_OnlineSubsystemBase に対して、GameKismetSceneClient をもう作成しないように命じます。

定義

  • SProcessingPlayer - この構造体は、処理されているプレイヤー、または、処理がペンディングされているプレイヤーを定義します。
  • IsProcessing - これが true の場合は、Kismet ノードがタスク処理にビジーであるため、この Kismet ノードへのリクエストがすべて直ちに拒否されます。
  • ProcessingPlayers - 現在処理されているプレイヤーの配列です。プレイヤーは順次処理されます。各プレイヤーの処理が完了すると、この配列から最初のエントリーを削除し、さらに、この配列が空になるまで次のエントリーを処理します。
  • HasCreatedSceneClient - これが false の場合は、この Kismet Sequence Action がアクティベートされると、GameKismetSceneClient を作成しようとするとともに、それをローカルのプレイヤーのビューポートに挿入しようとします。これは、ゲームのセッションが終了した時を捉えてクリーンアップが実行されるようにするためのものです。これが true の場合は、この Kismet Sequence Action は上記の実行を試みなくなります。

実行フロー

この Kismet Sequence Action が他の Kismet Sequence ノードによってアクティベートされると、 Activated() がまず呼び出されます。 Activated() は、 HasCreatedSceneClient の値を調べることによって、 GameKismetSceneClient を作成する必要があるか否かをチェックします。その必要がある場合は、ローカルのプレイヤーのコントローラをグラブして、新たな GameKismetSceneClient をビューポートに挿入します。これに成功すると、他すべての SeqAct_OnlineSubsystemBase に対して、 GameKismetSceneClient の挿入が完了したため、これ以上その必要がないことを通知します。 IsProcessing が true の場合は、Busy の出力がアクティベートされて、関数が停止します。そうでない場合は、アタッチされている Kismet Player Variable (プレイヤー変数) に基づいて、プレイヤーのリストをコンパイルします。Kismet Player Variable にリストされている各プレイヤーについて、 ProcessingPlayers (処理プレイヤー) 配列を、プレイヤーインデックスと一意なプレイヤー ID で満たします。 ProcessingPlayers 配列がサイズをもっている場合は、 IsProcessing が true にセットされ、 InternalOnActivated() が呼び出されます。さらに、Out の出力がアクティベートされます。

サブクラスがプレイヤーの処理を終えると、 FinishedProcessPlayerIndex() が呼び出されます。 bProcessedAllPlayers が true の場合は、 ProcessingPlayers 配列が空になります。そうでない場合は、 ProcessingPlayers 配列の最初のエントリーのみが削除されます。 ProcessingPlayers 配列が空でない場合は、 InternalOnActivated() を再び呼び出すことによって、最初のエントリーが処理されます。そうでない場合で、 bWasSuccessful が true の場合は、"Succeeded" の出力がアクティベートされます。それ以外は、Failed の出力がアクティベートされます。

CleanUp() が呼び出され、 IsExiting が true の場合は、 ProcessingPlayers 配列が空になります。

NotifyGameKismetSceneClientCreated() が呼び出されると、 HasCreatedSceneClient が true にセットされます。

UnrealScript

SeqAct_OnlineSubsystemBase.uc
/**
 * An OnlineSubsystemBase action is an abstract Action which is used to call other online subsystem functions which require local user nums
 *
 * Copyright 1998-2012 Epic Games, Inc. All Rights Reserved.
 */
class SeqAct_OnlineSubsystemBase extends SequenceAction
  abstract
  HideDropDown;

struct SProcessingPlayer
{
  var byte LocalUserNum;
  var UniqueNetId LocalNetId;
};

var private bool IsProcessing;
var protected array<SProcessingPlayer> ProcessingPlayers;
var protected bool HasCreatedSceneClient;

/**
 * Called when this event is activated.
 */
event Activated()
{
  local SeqVar_Player SeqVar_Player;
  local bool AllPlayers;
  local array<int> PlayerIndices, ExistingPlayerIndices;
  local WorldInfo WorldInfo;
  local PlayerController PlayerController;
  local LocalPlayer LocalPlayer;
  local int i;
  local SProcessingPlayer ProcessingPlayer;
  local OnlineSubSystem OnlineSubSystem;
  local Sequence GameSequence;
  local array<SequenceObject> SequenceObjects;
  local SeqAct_OnlineSubsystemBase SeqAct_OnlineSubsystemBase;

  // Check if we need to insert the GameKismetSceneClient to watch for garbage collection events
  if (!HasCreatedSceneClient)
  {
    // Insert the game kismet scene client
    WorldInfo = GetWorldInfo();
    if (WorldInfo != None)
    {
      // Get the local player controller
      PlayerController = WorldInfo.GetALocalPlayerController();
      if (PlayerController != None)
      {
        LocalPlayer = LocalPlayer(PlayerController.Player);
        if (LocalPlayer != None && LocalPlayer.ViewportClient != None)
        {
          LocalPlayer.ViewportClient.InsertInteraction(new (LocalPlayer.ViewportClient) class'GameKismetSceneClient');
        }
      }
    }

    // Flag all SeqAct_OnlineSubsystemBase that the game kismet scene client has been created
    GameSequence = WorldInfo.GetGameSequence();
    if (GameSequence != None)
    {
      GameSequence.FindSeqObjectsByClass(class'SeqAct_OnlineSubsystemBase', true, SequenceObjects);
      if (SequenceObjects.Length > 0)
      {
        for (i = 0; i < SequenceObjects.Length; ++i)
        {
          SeqAct_OnlineSubsystemBase = SeqAct_OnlineSubsystemBase(SequenceObjects[i]);
          if (SeqAct_OnlineSubsystemBase != None)
          {
            SeqAct_OnlineSubsystemBase.NotifyGameKismetSceneClientCreated();
          }
        }
      }
    }
  }

  if (IsProcessing)
  {
    // Activate the Busy output
    ActivateOutputLink(1);
    return;
  }

  // Get the online subsystem
  OnlineSubSystem = class'GameEngine'.static.GetOnlineSubSystem();
  if (OnlineSubSystem == None || OnlineSubSystem.PlayerInterface == None)
  {
    return;
  }

  // Get the player indices
  ForEach LinkedVariables(class'SeqVar_Player', SeqVar_Player)
  {
    if (SeqVar_Player.bAllPlayers)
    {
      AllPlayers = true;
      break;
    }
    else
    {
      PlayerIndices.AddItem(SeqVar_Player.PlayerIdx);
    }
  }

  // Find the players that need to be processed
  WorldInfo = GetWorldInfo();
  if (WorldInfo != None)
  {
    // If all players is true, then iterate for each player
    if (AllPlayers)
    {
      ForEach WorldInfo.AllControllers(class'PlayerController', PlayerController)
      {
        LocalPlayer = LocalPlayer(PlayerController.Player);
        if (LocalPlayer != None)
        {
          // Create the processing player struct
          ProcessingPlayer.LocalUserNum = class'UIInteraction'.static.GetPlayerIndex(LocalPlayer.ControllerId);
          OnlineSubSystem.PlayerInterface.GetUniquePlayerId(ProcessingPlayer.LocalUserNum, ProcessingPlayer.LocalNetId);

          // Append the processing player struct to the processing players array
          ProcessingPlayers.AddItem(ProcessingPlayer);
        }
      }
    }
    // Otherwise iterate for each player index
    else if (PlayerIndices.Length > 0)
    {
      // Get all of the existing player indices first
      ForEach WorldInfo.AllControllers(class'PlayerController', PlayerController)
      {
        LocalPlayer = LocalPlayer(PlayerController.Player);
        if (LocalPlayer != None)
        {
          ExistingPlayerIndices.AddItem(class'UIInteraction'.static.GetPlayerIndex(LocalPlayer.ControllerId));
        }
      }

      for (i = 0; i < PlayerIndices.Length; ++i)
      {
        if (ExistingPlayerIndices.Find(PlayerIndices[i]) != INDEX_NONE)
        {
          // Create the processing player struct
          ProcessingPlayer.LocalUserNum = PlayerIndices[i];
          OnlineSubSystem.PlayerInterface.GetUniquePlayerId(ProcessingPlayer.LocalUserNum, ProcessingPlayer.LocalNetId);

          // Append the processing player struct to the processing players array
          ProcessingPlayers.AddItem(ProcessingPlayer);
        }
      }
    }

    // Process the first one
    if (ProcessingPlayers.Length > 0)
    {
      // Set processing to true
      IsProcessing = true;

      // Activate
      InternalOnActivated();
    }
  }

  // Activate the Out output
  ActivateOutputLink(0);
}

/**
 * Called when the Kismet node has finished processing for this player
 *
 * @param      bWasSuccessful        True if the Kismet node was successful in what it was doing
 * @param      bProcessedAllPlayers    True if this Kismet node processed all players in one go, rather then doing one player at a time
 */
protected function FinishedProcessPlayerIndex(bool bWasSuccessful, optional bool bProcessedAllPlayers)
{
  // Perform clean up of this Kismet node
  CleanUp();

  // Pop the first processing player index
  if (ProcessingPlayers.Length > 0)
  {
    // If processed all players, then remove all of them now
    if (bProcessedAllPlayers)
    {
      ProcessingPlayers.Remove(0, ProcessingPlayers.Length);
    }
    // Otherwise we've only processed one, so pop it off the top
    else
    {
      ProcessingPlayers.Remove(0, 1);
    }
  }

  // If there is still more player indices to process, process the next one
  if (ProcessingPlayers.Length > 0)
  {
    InternalOnActivated();
  }
  // Otherwise, this Kismet node has finished processing and should activate one of the outputs
  else
  {
    IsProcessing = false;
    ForceActivateOutput((bWasSuccessful) ? 2 : 3);
  }
}

/**
 * Called when this sequence action should do something attached with a local user num
 */
protected function InternalOnActivated();

/**
 * Called when this Kismet node should clean up all variable or delegate references so that garbage collection can occur
 *
 * @param    IsExiting      If exiting, then clean up everything
 */
function CleanUp(optional bool IsExiting)
{
  // Clear processing players array
  if (IsExiting)
  {
    ProcessingPlayers.Remove(0, ProcessingPlayers.Length);
  }
}

/**
 * Called when a Kismet node has created the GameKismetSceneClient which is used to catch when the game session has finished and when game clean up should occur
 */
function NotifyGameKismetSceneClientCreated()
{
  HasCreatedSceneClient = true;
}

defaultproperties
{
`if(`isdefined(USE_STEAMWORKS))
  ObjCategory="Steamworks"
`elseif(`isdefined(USE_GAMECENTER))
  ObjCategory="Game Center"
`endif
  ObjColor=(R=0,G=63,B=127,A=255)

  bAutoActivateOutputLinks=false

  VariableLinks.Empty
  VariableLinks(0)=(ExpectedType=class'SeqVar_Player',LinkDesc="Player")

  OutputLinks(1)=(LinkDesc="Busy")
  OutputLinks(2)=(LinkDesc="Succeeded")
  OutputLinks(3)=(LinkDesc="Failed")
}


SeqAct_UnlockAchievement

この Kismet Sequence Action は、アタッチされている ID またはプロパティパネルで定義されている ID に基づいて、Achievement (成績) のロックを解除します。

KismetOnlineSubsystem_00.png

KismetOnlineSubsystem_07.jpg

関数

  • InternalOnActivated - この関数は、親クラスの SeqAct_OnlineSubsystemBase によって呼び出されます。Game Center では、ロックを解除する前にまず、Achievements を読み取ることが必要となります。そのため、 OnlineSubsystem.PlayerInterface.ReadAchievements() が呼び出されます。Steamworks では、単に BeginUnlockAchievement() が呼び出されます。
  • InternalOnReadAchievementsComplete - この関数は、デリゲートを介して OnlineSubsystem.PlayerInterface から呼び出されます。呼び出されると、オンラインで Achievement が達成されているか否かをチェックします。達成されていて、 AlwaysUnlockAchievement が true の場合は、 InternalOnUnlockAchievementComplete が呼び出されます。そうでない場合は、 BeginUnlockAchievement() が呼び出されます。
  • BeginUnlockAchievement - この関数は、 OnlineSubsystem.PlayerInterface.UnlockAchievement() にコールを送ります。
  • InternalOnUnlockAchievementComplete - この関数は、デリゲートを介して、 OnlineSubsystem.PlayerInterface から呼び出されます。この関数は、呼び出されると、 FinishedProcessPlayerIndex() を呼び出します。
  • CleanUp - この関数は、バインドされているデリゲートすべてのクリーンアップを処理します。

定義

  • AchievementId - ロック解除したい Achievement の ID です。これは、Kismet Int Variable を変数リンクにアタッチさせることによっても定義することができます。
  • AlwaysUnlockAchievement - true の場合は、Achievement がすでにロック解除されていても、Kismet Sequence Action が Succeeded を出力します。 Game Center のみ
  • DownloadedAchievements - オンラインのソースからダウンロードされた Achievement の配列です。 Game Center のみ

Game Center の実行フロー

InternalOnActivated() が呼び出されると、デリゲートが OnlineSubsystem.PlayerInterface に追加されます。*OnlineSubsystem.PlayerInterface* は、 OnlineSubsystem.PlayerInterface.ReadAchievements が完了した時に呼び出されます。

オンラインサブシステムが、オンラインの Achievement の読み取りを終えると、 InternalOnReadAchievementsComplete() を呼び出します。 InternalOnReadAchievementsComplete() は、Achievement がすでにロック解除されているか否かを確認します。ロック解除されている場合は、 BeginUnlockAchievement() が呼び出されます。そうでないならば、 AlwaysUnlockAchievement が true の場合は、 InternalOnUnlockAchievementComplete() が呼び出されます。 *BeginUnlockAchievement() は、Achievement のロック解除が完了した時に呼び出される他のオンラインサブシステム デリゲートにバインドされます。さらに、 OnlineSubsystem.PlayerInterface.UnlockAchievement() が呼び出されます。

Achievement がロック解除を終えると、 InternalOnUnlockAchievementComplete() が呼び出されます。これは、 FinishedProcessPlayerIndex() を呼び出します。

CleanUp が呼び出されると、バインドされているデリゲートすべてが削除されます。

Steamworks の実行フロー

InternalOnActivated() が呼び出されると、 BeginUnlockAchievement() が呼び出されます。 BeginUnlockAchievement() は、Achievement のロック解除が完了した時に呼び出される他のオンラインサブシステム デリゲートにバインドされます。さらに、 OnlineSubsystem.PlayerInterface.UnlockAchievement() が呼び出されます。

Achievement がロック解除を終えると、 InternalOnUnlockAchievementComplete() が呼び出されます。これは、 FinishedProcessPlayerIndex() を呼び出します。

CleanUp が呼び出されると、バインドされているデリゲートすべてが削除されます。

UnrealScript

SeqAct_UnlockAchievement.uc
/**
 * An UnlockAchievement Action is used to unlock an achievement.
 *
 * Copyright 1998-2012 Epic Games, Inc. All Rights Reserved.
 */
class SeqAct_UnlockAchievement extends SeqAct_OnlineSubsystemBase;

// Achievement Id that you want to unlock
var() int AchievementId;

// ==========
// GameCenter
// ==========
`if(`isdefined(USE_GAMECENTER))
// If true, then achivements that have already been unlocked, will still output the success link
var() bool AlwaysUnlockAchievement;
// Array of all downloads achievements
var protected array<AchievementDetails> DownloadedAchievements;
`endif

/**
 * Called when this sequence action should do something attached with a local user num
 */
protected function InternalOnActivated()
{
  local OnlineSubsystem OnlineSubsystem;

  // Connect to the online subsystem and link up the achievement delegates
  OnlineSubsystem = class'GameEngine'.static.GetOnlineSubsystem();
  if (OnlineSubsystem != None && OnlineSubsystem.PlayerInterface != None)
  {
// ==========
// SteamWorks
// ==========
`if(`isdefined(USE_STEAMWORKS))
    // Begin unlocking the achievement
    BeginUnlockAchievement();

// ==========
// GameCenter
// ==========
`else
    // Game Center requires you to read the achievements list first, and then unlock the achievement.
    // Assign the read achievements delegate
    OnlineSubsystem.PlayerInterface.AddReadAchievementsCompleteDelegate(ProcessingPlayers[0].LocalUserNum, InternalOnReadAchievementsComplete);

    // Read all achievements
    OnlineSubsystem.PlayerInterface.ReadAchievements(ProcessingPlayers[0].LocalUserNum);
`endif
  }
}

// ==========
// GameCenter
// ==========
`if(`isdefined(USE_GAMECENTER))
/**
 * Called when the async achievements read has completed.
 *
 * @param    TitleId      The title id that the read was for (0 means current title)
 */
protected function InternalOnReadAchievementsComplete(int TitleId)
{
  local OnlineSubsystem OnlineSubsystem;
  local int AchievementIndex;

  // Ensure we have an online subsystem, and an associated player interface
  OnlineSubsystem = class'GameEngine'.static.GetOnlineSubsystem();
  if (OnlineSubsystem == None || OnlineSubsystem.PlayerInterface == None)
  {
    return;
  }

  // Read the achievements into the downloaded achievements array
  OnlineSubsystem.PlayerInterface.GetAchievements(ProcessingPlayers[0].LocalUserNum, DownloadedAchievements, TitleId);

  // Grab the achievement index
  AchievementIndex = DownloadedAchievements.Find('Id', AchievementId);

  // Unlock the achievement
  if (AchievementIndex != INDEX_NONE)
  {
    // We haven't unlocked it yet, so start the unlock process
    if (!DownloadedAchievements[AchievementIndex].bWasAchievedOnline)
    {
      BeginUnlockAchievement();
    }
    // We've already unlocked it, so finish
    else if (AlwaysUnlockAchievement)
    {
      InternalOnUnlockAchievementComplete(true);
    }
  }
}
`endif

/**
 * Called when unlocking the achievement should begin
 *
 * @param    AchievementId      Which achievement to unlock
 * @param    LocalUserNum      Local user index
 */
protected function BeginUnlockAchievement()
{
  local OnlineSubsystem OnlineSubsystem;

  // Grab the online subsystem
  OnlineSubsystem = class'GameEngine'.static.GetOnlineSubsystem();
  if (OnlineSubsystem == None || OnlineSubsystem.PlayerInterface == None)
  {
    return;
  }

  // Assign the unlock achievement complete delegate
  OnlineSubsystem.PlayerInterface.AddUnlockAchievementCompleteDelegate(ProcessingPlayers[0].LocalUserNum, InternalOnUnlockAchievementComplete);

  // Start the unlocking process
  OnlineSubsystem.PlayerInterface.UnlockAchievement(ProcessingPlayers[0].LocalUserNum, AchievementId);
}

/**
 * Called when the achievement unlocking has completed
 *
 * @param     bWasSuccessful      True if the async action completed without error, false if there was an error
 */
protected function InternalOnUnlockAchievementComplete(bool bWasSuccessful)
{
  // Finished unlocking this achievement
  FinishedProcessPlayerIndex(bWasSuccessful);
}

/**
 * Called when this Kismet node should clean up all variable or delegate references so that garbage collection can occur
 *
 * @param    IsExiting      If exiting, then clean up everything
 */
function CleanUp(optional bool IsExiting)
{
  local OnlineSubsystem OnlineSubsystem;
  local int i, InPlayerIndex;

  // Grab the online subsystem and remove all delegate binds
  OnlineSubsystem = class'GameEngine'.static.GetOnlineSubsystem();
  if (OnlineSubsystem != None && OnlineSubsystem.PlayerInterface != None)
  {
    // There can only be 4 maximum split screen local players
    for (i = 0; i < 4; ++i)
    {
      // Get the player index
      InPlayerIndex = class'UIInteraction'.static.GetPlayerIndex(i);
      if (InPlayerIndex >= 0)
      {
      // Clear the delegates
// ==========
// GameCenter
// ==========
`if(`isdefined(USE_GAMECENTER))
        OnlineSubsystem.PlayerInterface.ClearReadAchievementsCompleteDelegate(InPlayerIndex, InternalOnReadAchievementsComplete);
`endif
        OnlineSubsystem.PlayerInterface.ClearUnlockAchievementCompleteDelegate(InPlayerIndex, InternalOnUnlockAchievementComplete);
      }
    }
  }

  // Call the super just in case we are exiting
  Super.CleanUp(IsExiting);
}

defaultproperties
{
// ==========
// GameCenter
// ==========
`if(`isdefined(USE_GAMECENTER))
  AlwaysUnlockAchievement=true
`endif
  ObjName="Unlock Achievement"
  VariableLinks(1)=(ExpectedType=class'SeqVar_Int',LinkDesc="Achievement Id",PropertyName=AchievementId)
}

Kismet の見本

KismetOnlineSubsystem_10.png


SeqAct_ModifyOnlineStat

Modify Online Stat は、オンライン サブシステムによって使用される統計をオンラインで修正することを可能にする Kismet Sequence Action です。Game Center では、このシステムによって、Leaderboard (成績表) で使用する新たな統計をアップロードすることができます。Steamworks では、このシステムによって、Achievement (成績) のために使用する統計を、足したり、引いたり、セットしたりすることができます。

KismetOnlineSubsystem_01.png

KismetOnlineSubsystem_08.jpg

関数

  • InternalOnActivated - 親クラスの SeqAct_OnlineSubsystemBase から呼び出されます。Game Center では、常に、 SetStatValue() を呼び出すにすぎません。Steamworks では、 ModifyMethod の値によって、 ReadOnlineStats() (既存の統計を足したり引いたりする場合) か、 SetStatValue() のどちらかを呼び出すことができます。
  • ReadOnlineStats - 既存の統計を足したり引いたりする場合に、Steamworks のバージョンでのみ呼び出されます。この関数は、オンライン統計を読み込み、それが終わると、 InternalOnReadOnlineStatsComplete() を呼び出します。
  • InternalOnReadOnlineStatsComplete - 統計の読み込みが終わると、その統計に値が加わるか、引かれます。さらに、その結果が Steamworks に送信されます。
  • SetStatValue - この関数は、統計をセットし、Game Center または Steamworks のいずれかに送信します。
  • InternalOnFlushOnlineStatsComplete - この関数は、統計がオンライン データベースに書き込まれたときに、Steamworks バージョンのみで呼び出されます。
  • CleanUp - この関数は、Kismet ノードが自身をクリーンアップした場合に呼び出されます。

定義

  • SStat - ベースとなる構造体の定義です。StatId は、Game Center または Steamworks 上で stat (統計) id を参照します。 LinkedVariableName によって、この stat id を、 アタッチされている Kismet Variable (変数) にリンクさせることができます。
  • SIntStat - SStat の拡張です。統計を加える / 引く / セットする 値を定義します。
  • SFloatStat - SStat の拡張です。統計を加える / 引く / セットする 値を定義します。
  • EModifyMethod - オンライン統計を修正する方法を設定します。
  • OnlineStatsWriteClass - オンライン サブシステムに書きだす時にどの OnlineStatsWrite をインスタンス化するかを定義します。
  • OnlineStatsReadClass - オンライン サブシステムに書きだす時にどの OnlineStatsRead をインスタンス化するかを定義します。
  • IntStats - オンライン サブシステムにアップロードするための統計の定義の配列です。
  • FloatStats - オンライン サブシステムにアップロードするための統計の定義の配列です。
  • ModifyMethod - オンライン統計を修正する方法を定義します。Game Center ではセットだけが可能です。Steamworks では、足す / 引く / セットが可能です。
  • SessionName - セッション名を定義します。デフォルトでは Game となっています。

Game Center の実行フロー

Game Center では、 InternalOnActivated() が呼び出されると、ただちに SetStatValue() が呼び出されます。 SetStatValue() が、 OnlineStatsWriteClass で定義されているクラスを使用して、OnlineStatsWrite をインスタンス化します。さらに、 IntStatsFloatStats をイタレートして、それらの統計の値をインスタンス化された OnlineStatsWrite クラスに書き出します。さらに、これらの統計をオンラインで書き出し、 FinishedProcessPlayerIndex() を呼び出します。

CleanUp() が呼び出されると、バインドされているすべてのデリゲートが解放されます。あらゆるオブジェクトの参照も解放されます。

Steamworks の実行フロー

Steamworks では、 InternalOnActivated()ModifyMethod の値に応じて呼び出されると、2 つの関数が呼び出されます。 ModifyMethod が MM_Add または MM_Subtract の場合は、 ReadOnlineStats() が呼び出されます。なぜならば、オンライン統計値を読み取ることによって、足すか引くかして修正できるようにする必要があるためです。 ModifyMethod がそのような値でない場合は、 SetStatValue() が呼び出されます。

ReadOnlineStats() が呼び出されると、デリゲートがバインドされ、OnlineStatsRead のインスタンスが作成されます。さらに*OnlineSubsystem.StatsInterface.ReadOnlineStats()* が呼び出されます。オンライン統計の読み取りが終了すると、 InternalOnReadOnlineStatsComplete が呼び出されます。さらに、 InternalOnReadOnlineStatsCompleteIntStats 配列と FloatStats 配列をイタレートして、統計値を修正します。修正された統計は、OnlineStatsWrite インスタンスに書き出され、さらに、 OnlineSubsystem.StatsInterface.WriteOnlineStats() が呼び出されて、Steamworks に送信されます。 OnlineSubsystem.StatsInterface.WriteOnlineStats() が渡された場合、デリゲートがバインドされ、 OnlineSubsystem.StatsInterface.FlushOnlineStats() が呼び出されます。オンライン統計値がフラッシュされると、 InternalOnFlushOnlineStatsComplete() が呼び出されます。さらに、この関数は、 FinishedProcessPlayerIndex() を呼び出します。

SetStatValue() は、呼び出されると、 IntStats 配列と FloatStats 配列をイタレートして、統計値をセットします。修正された統計は、OnlineStatsWrite インスタンスに書き出され、さらに、 OnlineSubsystem.StatsInterface.WriteOnlineStats() が呼び出されて、Steamworks に送信されます。 OnlineSubsystem.StatsInterface.WriteOnlineStats() が渡された場合、デリゲートがバインドされ、 OnlineSubsystem.StatsInterface.FlushOnlineStats() が呼び出されます。オンライン統計値がフラッシュされると、 InternalOnFlushOnlineStatsComplete() が呼び出されます。さらに、この関数は、 FinishedProcessPlayerIndex() を呼び出します。

CleanUp() が呼び出されると、バインドされているすべてのデリゲートが解放されます。あらゆるオブジェクトの参照も解放されます。

Unrealscript

SeqAct_ModifyOnlineStat.uc
/**
 * An Modify Online Stat Action is used to modify and upload stats.
 *
 * Copyright 1998-2012 Epic Games, Inc. All Rights Reserved.
 */
class SeqAct_ModifyOnlineStat extends SeqAct_OnlineSubsystemBase;

// Stat base struct
struct SStat
{
  var() Name LinkedVariableName;
  var() int StatId;
};

// Integer based stat
struct SIntStat extends SStat
{
  var() int Value;
};

// Float based stat
struct SFloatStat extends SStat
{
  var() float Value;
};

// What method does the user want to modify the stat
enum EModifyMethod
{
  MM_Set<DisplayName=Set>,
// ==========
// Steamworks
// ==========
`if(`isdefined(USE_STEAMWORKS))
  MM_Add<DisplayName=Add>,
  MM_Subtract<DisplayName=Subtract>,
`endif
};

// Online stats write class associated with the stats
var() class<OnlineStatsWrite> OnlineStatsWriteClass;
// ==========
// Steamworks
// ==========
`if(`isdefined(USE_STEAMWORKS))
// Online stats read class associated with the stats
var() class<OnlineStatsRead> OnlineStatsReadClass;
`endif
// Array of integer based stats
var() array<SIntStat> IntStats;
// Array of float based stats
var() array<SFloatStat> FloatStats;
// Method to modify the stat
var() EModifyMethod ModifyMethod;
// Session name
var() Name SessionName;

// Online stats write object instance
var protected OnlineStatsWrite OnlineStatsWrite;
// Online stats read object instance
var protected OnlineStatsRead OnlineStatsRead;

/**
 * Called when this sequence action should do something attached with a local user num
 */
protected function InternalOnActivated()
{
  // Abort if the online stats write class is none
  // Abort if there are no column ids
  if (OnlineStatsWriteClass == None || IntStats.Length <= 0)
  {
    return;
  }

// ==========
// Steamworks
// ==========
`if(`isdefined(USE_STEAMWORKS))
  // User wants to modify the stats using add or subtract
  if (ModifyMethod == MM_Add || ModifyMethod == MM_Subtract)
  {
    ReadOnlineStats();
  }
  // User wants to modify the stats by setting the value
  else
  {
`endif
    SetStatValue();
// ==========
// Steamworks
// ==========
`if(`isdefined(USE_STEAMWORKS))
  }
`endif
}

// ==========
// Steamworks
// ==========
`if(`isdefined(USE_STEAMWORKS))
/**
 * Called when the user wants to modify the stats using increment or decrement
 */
protected function ReadOnlineStats()
{
  local OnlineSubsystem OnlineSubsystem;
  local array<UniqueNetId> PlayerIds;

  // Attempt to read the online stats
  OnlineSubsystem = class'GameEngine'.static.GetOnlineSubsystem();
  if (OnlineSubsystem != None && OnlineSubsystem.StatsInterface != None)
  {
    OnlineSubsystem.StatsInterface.AddReadOnlineStatsCompleteDelegate(InternalOnReadOnlineStatsComplete);

    PlayerIds.AddItem(ProcessingPlayers[0].LocalNetId);
    OnlineStatsRead = new () OnlineStatsReadClass;
    if (OnlineStatsRead != None)
    {
      OnlineSubsystem.StatsInterface.ReadOnlineStats(PlayerIds, OnlineStatsRead);
    }
  }
}

/**
 * Called when reading the online stats has finished
 *
 * @param    bWasSuccessful      True if reading the online stats succeeded
 */
function InternalOnReadOnlineStatsComplete(bool bWasSuccessful)
{
  local OnlineSubsystem OnlineSubsystem;
  local int i, ReadIntStatValue;
  local float ReadFloatStatValue;
  local SeqVar_Int SeqVar_Int;
  local SeqVar_Float SeqVar_Float;

  // If reading the stats is successful
  if (bWasSuccessful && OnlineStatsRead != None)
  {
    // Write stats
    OnlineSubsystem = class'GameEngine'.static.GetOnlineSubsystem();
    if (OnlineSubsystem != None && OnlineSubsystem.StatsInterface != None)
    {
      // Instance the stats write object
      OnlineStatsWrite = new () OnlineStatsWriteClass;
      if (OnlineStatsWrite != None)
      {
        // Modify the int stats write values
        for (i = 0; i < IntStats.Length; ++i)
        {
          OnlineStatsRead.GetIntStatValueForPlayer(ProcessingPlayers[0].LocalNetId, IntStats[i].StatId, ReadIntStatValue);

          // If this state has a variable name, then perform a look up
          if (IntStats[i].LinkedVariableName != '' && IntStats[i].LinkedVariableName != 'None')
          {
            ForEach LinkedVariables(class'SeqVar_Int', SeqVar_Int)
            {
              if (SeqVar_Int.VarName == IntStats[i].LinkedVariableName)
              {
                switch (ModifyMethod)
                {
                case MM_Add:
                  OnlineStatsWrite.SetIntStat(IntStats[i].StatId, ReadIntStatValue + SeqVar_Int.IntValue);
                  break;

                case MM_Subtract:
                  OnlineStatsWrite.SetIntStat(IntStats[i].StatId, ReadIntStatValue - SeqVar_Int.IntValue);
                  break;

                default:
                  break;
                }
              }
            }
          }
          // Otherwise use the value defined in the struct
          else
          {
            switch (ModifyMethod)
            {
            case MM_Add:
              OnlineStatsWrite.SetIntStat(IntStats[i].StatId, ReadIntStatValue + IntStats[i].Value);
              break;

            case MM_Subtract:
              OnlineStatsWrite.SetIntStat(IntStats[i].StatId, ReadIntStatValue - IntStats[i].Value);
              break;

            default:
              break;
            }
          }
        }

        // Modify the float stats write values
        for (i = 0; i < FloatStats.Length; ++i)
        {
          OnlineStatsRead.GetFloatStatValueForPlayer(ProcessingPlayers[0].LocalNetId, FloatStats[i].StatId, ReadFloatStatValue);

          // If this state has a variable name, then perform a look up
          if (FloatStats[i].LinkedVariableName != '' && FloatStats[i].LinkedVariableName != 'None')
          {
            ForEach LinkedVariables(class'SeqVar_Float', SeqVar_Float)
            {
              if (SeqVar_Float.VarName == FloatStats[i].LinkedVariableName)
              {
                switch (ModifyMethod)
                {
                case MM_Add:
                  OnlineStatsWrite.SetFloatStat(FloatStats[i].StatId, ReadFloatStatValue + SeqVar_Float.FloatValue);
                  break;

                case MM_Subtract:
                  OnlineStatsWrite.SetFloatStat(FloatStats[i].StatId, ReadFloatStatValue - SeqVar_Float.FloatValue);
                  break;

                default:
                  break;
                }
              }
            }
          }
          // Otherwise use the value defined in the struct
          else
          {
            switch (ModifyMethod)
            {
            case MM_Add:
              OnlineStatsWrite.SetFloatStat(FloatStats[i].StatId, ReadFloatStatValue + FloatStats[i].Value);
              break;

            case MM_Subtract:
              OnlineStatsWrite.SetFloatStat(FloatStats[i].StatId, ReadFloatStatValue - FloatStats[i].Value);
              break;

            default:
              break;
            }
          }
        }

        // Send the write request to the stat handler
        if (OnlineSubsystem.StatsInterface.WriteOnlineStats(SessionName, ProcessingPlayers[0].LocalNetId, OnlineStatsWrite))
        {
          // Add the flush delegate
          OnlineSubsystem.StatsInterface.AddFlushOnlineStatsCompleteDelegate(InternalOnFlushOnlineStatsComplete);
          // Flush the online stats
          OnlineSubsystem.StatsInterface.FlushOnlineStats(SessionName);
        }
        // Activate the failed output link
        else
        {
          ForceActivateOutput(3);
        }
      }
    }
  }
  else
  {
    FinishedProcessPlayerIndex(bWasSuccessful);
  }
}
`endif

/**
 * Called when this Kismet node should just set the stats value
 */
protected function SetStatValue()
{
  local OnlineSubsystem OnlineSubsystem;
  local int i;
  local SeqVar_Int SeqVar_Int;
  local SeqVar_Float SeqVar_Float;

  // Write stats
  OnlineSubsystem = class'GameEngine'.static.GetOnlineSubsystem();
  if (OnlineSubsystem != None && OnlineSubsystem.StatsInterface != None)
  {
    // Instance the stats write object
    OnlineStatsWrite = new () OnlineStatsWriteClass;
    if (OnlineStatsWrite != None)
    {
      // Set the int stats write values
      for (i = 0; i < IntStats.Length; ++i)
      {
        // If this state has a variable name, then perform a look up
        if (IntStats[i].LinkedVariableName != '' && IntStats[i].LinkedVariableName != 'None')
        {
          ForEach LinkedVariables(class'SeqVar_Int', SeqVar_Int)
          {
            if (SeqVar_Int.VarName == IntStats[i].LinkedVariableName)
            {
              OnlineStatsWrite.SetIntStat(IntStats[i].StatId, SeqVar_Int.IntValue);
              break;
            }
          }
        }
        // Otherwise use the value defined in the struct
        else
        {
          OnlineStatsWrite.SetIntStat(IntStats[i].StatId, IntStats[i].Value);
          break;
        }
      }

      // Modify the float stats write values
      for (i = 0; i < FloatStats.Length; ++i)
      {
        // If this state has a variable name, then perform a look up
        if (FloatStats[i].LinkedVariableName != '' && FloatStats[i].LinkedVariableName != 'None')
        {
          ForEach LinkedVariables(class'SeqVar_Float', SeqVar_Float)
          {
            if (SeqVar_Float.VarName == FloatStats[i].LinkedVariableName)
            {
              OnlineStatsWrite.SetFloatStat(FloatStats[i].StatId, SeqVar_Float.FloatValue);
            }
          }
        }
        // Otherwise use the value defined in the struct
        else
        {
          OnlineStatsWrite.SetFloatStat(FloatStats[i].StatId, FloatStats[i].Value);
        }
      }

      // Send the write request to the stat handler
      if (OnlineSubsystem.StatsInterface.WriteOnlineStats(SessionName, ProcessingPlayers[0].LocalNetId, OnlineStatsWrite))
      {
// ==========
// SteamWorks
// ==========
`if(`isdefined(USE_STEAMWORKS))
        // Add the flush delegates
        OnlineSubsystem.StatsInterface.AddFlushOnlineStatsCompleteDelegate(InternalOnFlushOnlineStatsComplete);
`endif
        // Flush the online stats
        OnlineSubsystem.StatsInterface.FlushOnlineStats(SessionName);
// ==========
// GameCenter
// ==========
`if(`isdefined(USE_GAMECENTER))
        // Clear all object refs
        OnlineStatsWrite = None;
        OnlineStatsRead = None;

        // Handle process player index
        FinishedProcessPlayerIndex(true);
`endif
      }
      // Activate the failed output link
      else
      {
        ForceActivateOutput(3);
      }
    }
  }
}

`if(`isdefined(USE_STEAMWORKS))
/**
 * Called when the stats flush operation has completed
 *
 * @param SessionName the name of the session having stats flushed for
 * @param bWasSuccessful true if the async action completed without error, false if there was an error
 */
function InternalOnFlushOnlineStatsComplete(Name InSessionName, bool bWasSuccessful)
{
  // Clear all object refs
  OnlineStatsWrite = None;
  OnlineStatsRead = None;

  // Handle process player index
  FinishedProcessPlayerIndex(bWasSuccessful);
}
`endif

/**
 * Called when this Kismet node should clean up all variable or delegate references so that garbage collection can occur
 *
 * @param    IsExiting      If exiting, then clean up everything
 */
function CleanUp(optional bool IsExiting)
{
// ==========
// SteamWorks
// ==========
`if(`isdefined(USE_STEAMWORKS))
  local OnlineSubsystem OnlineSubsystem;
  local int i, InPlayerIndex;

  // Grab the online subsystem and remove all delegate binds
  OnlineSubsystem = class'GameEngine'.static.GetOnlineSubsystem();
  if (OnlineSubsystem != None && OnlineSubsystem.StatsInterface != None)
  {
    // There can only be 4 maximum split screen local players
    for (i = 0; i < 4; ++i)
    {
      // Get the player index
      InPlayerIndex = class'UIInteraction'.static.GetPlayerIndex(i);
      if (InPlayerIndex >= 0)
      {
        // Clear the delegates
        OnlineSubsystem.StatsInterface.ClearFlushOnlineStatsCompleteDelegate(InternalOnFlushOnlineStatsComplete);
        OnlineSubsystem.StatsInterface.ClearReadOnlineStatsCompleteDelegate(InternalOnReadOnlineStatsComplete);
      }
    }
  }
`endif

  // Clear the online stats write object
  OnlineStatsWrite = None;
  // Clear the online stats read object
  OnlineStatsRead = None;

  // Call the super just in case we are exiting
  Super.CleanUp(IsExiting);
}

defaultproperties
{
  SessionName="Game"
  ObjName="Modify Online Stat"

  VariableLinks(1)=(ExpectedType=class'SeqVar_Int',LinkDesc="Stat Values")
}

Kismet の見本

KismetOnlineSubsystem_11.png


Steam Works 専用 Kismet ノード


SeqAct_RefreshAchievements

この Kismet Sequence Action は、Steam Works から読み取ることによって Achievement をリフレッシュするように、クライアントに強制しようとします。

KismetOnlineSubsystem_02.png

関数

  • InternalOnActivated - この関数が呼び出されると、デリゲートにバインドするとともに、 OnlineSubsystem.PlayerInterface.ReadAchievements() を呼び出します。
  • InternalOnReadAchievementsComplete - OnlineSubsystem.PlayerInterface.ReadAchievements() が Steam Works から Achievement の読み取りを終了した時に、この関数が呼び出されます。この関数は FinishedProcessPlayerIndex() を呼び出します。
  • CleanUp - この関数は、バインドされていたあらゆるデリゲートをクリーンアップします。

実行フロー

InternalOnActivated() は、呼び出されると、まず、 OnlineSubsystem.PlayerInterface 内のデリゲートにバインドします。このデリケートは、オンラインサブシステムが Steam Works から Achievement の読み取りを終了した時に、呼び出されます。さらに、 OnlineSubsystem.PlayerInterface.ReadAchievements() が呼び出されます。Achievement の読み取りが終了すると、 InternalOnReadAchievementsComplete() が呼び出されます。この関数は、さらに、 FinishedProcessPlayerIndex() を呼び出します。

CleanUp() が呼び出されると、バインドされていたあらゆるデリゲートが開放されます。

UnrealScript

SeqAct_RefreshAchievements.uc
/**
 * A Refresh Achievements Action is used to refresh all achievements.
 *
 * Copyright 1998-2012 Epic Games, Inc. All Rights Reserved.
 */
class SeqAct_RefreshAchievements extends SeqAct_OnlineSubsystemBase
`if(`notdefined(USE_STEAMWORKS))
  abstract
  HideDropDown;
`else
  ;

/**
 * Called when this sequence action should do something attached with a local user num
 */
protected function InternalOnActivated()
{
  local OnlineSubSystem OnlineSubSystem;

  OnlineSubSystem = class'GameEngine'.static.GetOnlineSubSystem();
  if (OnlineSubSystem != None && OnlineSubSystem.PlayerInterface != None)
  {
    // Bind the delegate
    OnlineSubSystem.PlayerInterface.AddReadAchievementsCompleteDelegate(ProcessingPlayers[0].LocalUserNum, InternalOnReadAchievementsComplete);

    // Read all achievements
    OnlineSubsystem.PlayerInterface.ReadAchievements(ProcessingPlayers[0].LocalUserNum);
  }
}

/**
 * Called when the async achievements read has completed
 *
 * @param TitleId the title id that the read was for (0 means current title)
 */
function InternalOnReadAchievementsComplete(int TitleId)
{
  FinishedProcessPlayerIndex(true);
}

/**
 * Called when this Kismet node should clean up all variable or delegate references so that garbage collection can occur
 *
 * @param    IsExiting      If exiting, then clean up everything
 */
function CleanUp(optional bool IsExiting)
{
  local OnlineSubsystem OnlineSubsystem;
  local int i, InPlayerIndex;

  // Grab the online subsystem and remove all delegate binds
  OnlineSubsystem = class'GameEngine'.static.GetOnlineSubsystem();
  if (OnlineSubsystem != None && OnlineSubsystem.PlayerInterface != None)
  {
    // There can only be 4 maximum split screen local players
    for (i = 0; i < 4; ++i)
    {
      // Get the player index
      InPlayerIndex = class'UIInteraction'.static.GetPlayerIndex(i);
      if (InPlayerIndex >= 0)
      {
        // Clear the delegates
        OnlineSubsystem.PlayerInterface.ClearReadAchievementsCompleteDelegate(InPlayerIndex, InternalOnReadAchievementsComplete);
      }
    }
  }

  // Call the super just in case we are exiting
  Super.CleanUp(IsExiting);
}
`endif

defaultproperties
{
  ObjName="Refresh Achievements"
}


SeqAct_ResetAchievements

この Kismet Sequence Action は、プレイヤーのために保存されている統計または Achievement (またはその両方) をリセットするために使用されます。この Kismet Sequence Action は、Steam Works オンライン サブシステムに直接バインドされているため、デバッグ用にのみ使用されるべきです。

KismetOnlineSubsystem_03.png

関数

  • InternalOnActivated - この関数は、呼び出されると、 OnlineSubsystemSteamworks.ResetStats() を呼び出します。

定義

  • ResetAchievements - これが true の場合は、Achievement もリセットされます。

実行フロー

InternalOnActivated() が呼び出されると、 OnlineSubsystemSteamworks.ResetStats() を呼び出します。さらに、 OnlineSubsystemSteamworks.ResetStats()FinishedProcessPlayerIndex() を呼び出します。

UnrealScript

SeqAct_ResetAchievements.uc
/**
 * A Reset Achievements Action is used to reset all of the achievements earnt by the player.
 *
 * Copyright 1998-2012 Epic Games, Inc. All Rights Reserved.
 */
class SeqAct_ResetAchievements extends SeqAct_OnlineSubsystemBase
`if(`notdefined(USE_STEAMWORKS))
  abstract
  HideDropDown;
`else
  ;

// If true, then achievements are also reset. Otherwise only stats are reset.
var() const bool ResetAchievements;

/**
 * Called when this sequence action should do something attached with a local user num
 */
protected function InternalOnActivated()
{
  local OnlineSubsystemSteamworks OnlineSubsystemSteamworks;
  local bool bWasSuccessful;

  OnlineSubsystemSteamworks = OnlineSubsystemSteamworks(class'GameEngine'.static.GetOnlineSubsystem());
  if (OnlineSubsystemSteamworks != None)
  {
    bWasSuccessful = OnlineSubsystemSteamworks.ResetStats(ResetAchievements);
  }
  else
  {
    bWasSuccessful = false;
  }

  FinishedProcessPlayerIndex(bWasSuccessful);
}
`endif

defaultproperties
{
`if(`isdefined(USE_STEAMWORKS))
  ResetAchievements=true
`endif
  ObjName="Reset Achievements/Stats"
}

Kismet の見本

KismetOnlineSubsystem_12.png


SeqAct_ReadOnlineStat

この Kismet Sequence Action によって、後に Kismet 内で使用することになるオンライン統計を読み取ることができます。

KismetOnlineSubsystem_04.png

KismetOnlineSubsystem_09.jpg

関数

  • InternalOnActivated - この関数は、親クラスの SeqAct_OnlineSubsystemBase によって呼び出されます。この関数は、さらに、Steam Works から統計を読み取るためのコールを開始します。
  • InternalOnReadOnlineStatsComplete - この関数は、Steam Woks から統計を読み取り終わると呼び出されます。この関数は最終的に FinishedProcessPlayerIndex() を呼び出します。
  • CleanUp - この関数は、このKismet Sequence Action によって使用されるあらゆるデリゲートとオブジェクト参照をクリーンアップします。

定義

  • EReadStatsMethod - 統計を読み取るための各種方法を定義する列挙型変数です。
  • SLinkedVariableName - 読み取りたい統計のための ID、および、アタッチされている Kismet Variable (変数) へリンクする名前を保持する構造体です。
  • OnlineStatsReadClass - オンライン統計を読み取るために使用する Online Read Stats クラスです。
  • ReadStatsMethod - EReadStatsMethod の変数宣言です。
  • StartRankIndex - read stats rank メソッド を使用する場合に、どの開始ランクインデックスを使用するかを保持します。
  • RowCount - read stats rank メソッド または rank around player メソッドを使用する場合に、読み取る行数を保持します。
  • LinkedVariableNames - 出力先のリンクされている変数です。
  • Rank - Kismet Variable (変数) にマッピングされているランク化された変数です。

実行フロー

InternalOnActivated() は、呼び出されると、まず、 *OnlineStatsReadClass で定義されているクラスに基づいて OnlineStatsRead のインスタンス化を行います。さらに、 OnlineSubsystem.StatsInterface.FreeStats() が呼び出され、 OnlineStatsRead インスタンスがクリアされます。さらに、デリゲートがバインドされ、オンライン統計を読み取るためのリクエストが実行されます。オンライン統計を読み取るためのメソッドは、 ReadStatsMethod 変数によって定義されています。これによって、 OnlineSubsystem.StatsInterface.ReadOnlineStatsForFriends()OnlineSubsystem.StatsInterface.ReadOnlineStatsByRank()OnlineSubsystem.StatsInterface.ReadOnlineStatsByRankAroundPlayer()OnlineSubsystem.StatsInterface.ReadOnlineStats() が呼び出される可能性があります。 InternalOnReadOnlineStatsComplete() は、オンライン統計が完了した時に呼び出されます。この関数の役割は、統計を取得し、それらをアタッチされている Kismet Sequence Variables に出力することです。ランクもここにセットされます。 PopulateLinkedVariableValues() が呼び出されて、マッピングされた統計プロパティをコピーします。さらに、 FinishedProcessPlayerIndex() が呼び出されます。

CleanUp() が呼び出されると、バインドされていたすべてのデリゲートとオブジェクト インスタンスが解放されます。

UnrealScript

SeqAct_ReadOnlineStat.uc
/**
 * A Read Online Stat Action is used to read online stats.
 *
 * Copyright 1998-2012 Epic Games, Inc. All Rights Reserved.
 */
class SeqAct_ReadOnlineStat extends SeqAct_OnlineSubsystemBase
// ==========
// GameCenter
// ==========
`if(`isdefined(USE_GAMECENTER))
  abstract
  HideDropdown;
`else
  ;
//
enum EReadStatsMethod
{
  RST_ReadAll<DisplayName=Read all stats>,
  RST_ReadFriendsOnly<DisplayName=Read stats of friends>,
  RST_ReadByRank<DisplayName=Read stats by rank>,
  RST_ReadByRankAroundPlayer<DisplayName=Read stats around player>
};

//
struct SLinkedVariableName
{
  var() int StatId;
  var() Name LinkedVariableName;
};

// Class to use which reads the stats
var() class<OnlineStatsRead> OnlineStatsReadClass;
// Method to read stats
var() EReadStatsMethod ReadStatsMethod;
// If use a read stats ranked method, which starting rank index to use
var() int StartRankIndex;
// How many rows to read if using read stats rank or rank around player methods
var() int RowCount;
// Linked variables to output to
var() array<SLinkedVariableName> LinkedVariableNames;

// Online stats read object instance
var protected OnlineStatsRead OnlineStatsRead;
// Rank variable mapped to a Kismet variable
var int Rank;

/**
 * Called when this sequence action should do something attached with a local user num
 */
protected function InternalOnActivated()
{
  local OnlineSubSystem OnlineSubSystem;
  local array<UniqueNetId> PlayerIds;

  // Abort if the online stats read class is none
  // Abort if there are no column ids
  if (OnlineStatsReadClass == None || LinkedVariableNames.Length <= 0)
  {
    return;
  }

  // Request stats
  OnlineSubSystem = class'GameEngine'.static.GetOnlineSubSystem();
  if (OnlineSubSystem != None && OnlineSubSystem.StatsInterface != None)
  {
    // Instance the online stats
    OnlineStatsRead = new () OnlineStatsReadClass;
    if (OnlineStatsRead != None)
    {
      // Free the stats
      OnlineSubsystem.StatsInterface.FreeStats(OnlineStatsRead);

      // Bind the read online stats delegate
      OnlineSubsystem.StatsInterface.AddReadOnlineStatsCompleteDelegate(InternalOnReadOnlineStatsComplete);

      switch (ReadStatsMethod)
      {
      // Read stats for friends only
      case RST_ReadFriendsOnly:
        OnlineSubsystem.StatsInterface.ReadOnlineStatsForFriends(ProcessingPlayers[0].LocalUserNum, OnlineStatsRead);
        break;

      // Read stats by rank only
      case RST_ReadByRank:
        OnlineSubsystem.StatsInterface.ReadOnlineStatsByRank(OnlineStatsRead, StartRankIndex, RowCount);
        break;

      // Read stats around player
      case RST_ReadByRankAroundPlayer:
        OnlineSubsystem.StatsInterface.ReadOnlineStatsByRankAroundPlayer(ProcessingPlayers[0].LocalUserNum, OnlineStatsRead, RowCount);
        break;

        // Read all stats
      case RST_ReadAll:
      default:
        PlayerIds.AddItem(ProcessingPlayers[0].LocalNetId);
        OnlineSubsystem.StatsInterface.ReadOnlineStats(PlayerIds, OnlineStatsRead);
        break;
      }
    }
  }
}

/**
 * Notifies the interested party that the last stats read has completed
 *
 * @param bWasSuccessful true if the async action completed without error, false if there was an error
 */
function InternalOnReadOnlineStatsComplete(bool bWasSuccessful)
{
  local int i;
  local SeqVar_Int SeqVar_Int;
  local SeqVar_Float SeqVar_Float;

  if (OnlineStatsRead != None)
  {
    // Output the stat id for the processing player
    if (LinkedVariableNames.Length > 0)
    {
      for (i = 0; i < LinkedVariableNames.Length; ++i)
      {
        if (LinkedVariableNames[i].LinkedVariableName != '' && LinkedVariableNames[i].LinkedVariableName != 'None')
        {
          // Copy the int stats over to the sequence variable
          ForEach LinkedVariables(class'SeqVar_Int', SeqVar_Int)
          {
            if (SeqVar_Int.VarName == LinkedVariableNames[i].LinkedVariableName)
            {
              OnlineStatsRead.GetIntStatValueForPlayer(ProcessingPlayers[0].LocalNetId, LinkedVariableNames[i].StatId, SeqVar_Int.IntValue);
            }
          }

          // Copy the float stats over to the sequence variable
          ForEach LinkedVariables(class'SeqVar_Float', SeqVar_Float)
          {
            if (SeqVar_Float.VarName == LinkedVariableNames[i].LinkedVariableName)
            {
              OnlineStatsRead.GetFloatStatValueForPlayer(ProcessingPlayers[0].LocalNetId, LinkedVariableNames[i].StatId, SeqVar_Float.FloatValue);
            }
          }
        }
      }
    }

    // Output rank
    Rank = OnlineStatsRead.GetRankForPlayer(ProcessingPlayers[0].LocalNetId);
    // Populate the linked variables
    PopulateLinkedVariableValues();
  }

  // Finished processing players
  FinishedProcessPlayerIndex(bWasSuccessful);
  // Remove reference to online stats read
  OnlineStatsRead = None;
}

/**
 * Called when this Kismet node should clean up all variable or delegate references so that garbage collection can occur
 *
 * @param    IsExiting      If exiting, then clean up everything
 */
function CleanUp(optional bool IsExiting)
{
  local OnlineSubsystem OnlineSubsystem;
  local int i, InPlayerIndex;

  // Grab the online subsystem and remove all delegate binds
  OnlineSubsystem = class'GameEngine'.static.GetOnlineSubsystem();
  if (OnlineSubsystem != None && OnlineSubsystem.StatsInterface != None)
  {
    // There can only be 4 maximum split screen local players
    for (i = 0; i < 4; ++i)
    {
      // Get the player index
      InPlayerIndex = class'UIInteraction'.static.GetPlayerIndex(i);
      if (InPlayerIndex >= 0)
      {
        // Clear the delegates
        OnlineSubsystem.StatsInterface.ClearReadOnlineStatsCompleteDelegate(InternalOnReadOnlineStatsComplete);
      }
    }
  }

  // Clear the online stats read object
  OnlineStatsRead = None;

  // Call the super just in case we are exiting
  Super.CleanUp(IsExiting);
}
`endif

defaultproperties
{
  ObjName="Read Online Stat"

// ==========
// SteamWorks
// ==========
`if(`isdefined(USE_STEAMWORKS))
  StartRankIndex=1
  RowCount=50
`endif

  VariableLinks(1)=(ExpectedType=class'SeqVar_Int',LinkDesc="Stat Values",bWriteable=true)
  VariableLinks(2)=(ExpectedType=class'SeqVar_Int',LinkDesc="Rank",bWriteable=true,PropertyName=Rank)
}

Kismet の見本

KismetOnlineSubsystem_13.png


Game Center 専用 Kismet ノード


SeqAct_Mobile_ShowAchievementsUI

この Kismet Sequence Action がアクティベートされると、Game Center の Achievement の UI が表示されます。通常、これによってゲームが自動的にポーズされます。

KismetOnlineSubsystem_05.png

関数

  • InternalOnActivated - この関数は、呼び出されると、 OnlineSubSystem.PlayerInterfaceEx.ShowAchievementsUI() を呼び出します。

実行フロー

InternalOnActivated() が親クラスの SeqAct_OnlineSubsystemBase によって呼び出されると、これはさらに、 OnlineSubSystem.PlayerInterfaceEx.ShowAchievementsUI() を呼び出します。さらに、 OnlineSubsystemSteamworks.ResetStats()FinishedProcessPlayerIndex() を呼び出します。

UnrealScript

SeqAct_Mobile_ShowAchievementsUI.uc
/**
 * A Mobile Show Achievements UI Action is used to display the achievements UI.
 *
 * Copyright 1998-2012 Epic Games, Inc. All Rights Reserved.
 */
class SeqAct_Mobile_ShowAchievementsUI extends SeqAct_OnlineSubsystemBase
`if(`notdefined(USE_GAMECENTER))
  abstract
  HideDropDown;
`else
  ;

/**
 * Called when this sequence action should do something attached with a local user num
 */
protected function InternalOnActivated()
{
  local OnlineSubsystem OnlineSubsystem;

  // Get the online sub system
  OnlineSubSystem = class'GameEngine'.static.GetOnlineSubsystem();
  // Check that the online subsystem and player interface ex is accessible
  if (OnlineSubSystem != None && OnlineSubSystem.PlayerInterfaceEx != None)
  {
    OnlineSubSystem.PlayerInterfaceEx.ShowAchievementsUI(ProcessingPlayers[0].LocalUserNum);
    FinishedProcessPlayerIndex(true);
  }
  else
  {
    FinishedProcessPlayerIndex(false);
  }
}
`endif

defaultproperties
{
  ObjName="Show Achievements UI"
}

Kismet の見本

KismetOnlineSubsystem_14.png


SeqAct_Mobile_ShowLeaderboardsUI

この Kismet Sequence Action がアクティベートされると、Game Center の Leaderboards の UI が表示されます。通常、これによって、ゲームが自動的にポーズされます。

KismetOnlineSubsystem_06.png

関数

  • InternalOnActivated - この関数は、呼び出されると、 OnlineStatsReadClass の値に基づいて OnlineStatsRead のインスタンスを作成します。さらに、 OnlineSuppliedUIInterface.ShowOnlineStatsUI() が呼び出されて、Game Center の Leaderboards の UI が表示されます。UI が閉じられる際に InternalOnShowOnlineStatsUIComplete を呼び出すデリゲートもバインドされます。
  • InternalOnShowOnlineStatsUIComplete - この関数は、Leaderboards の UI が閉じられる際に呼び出されます。
  • CleanUp - この関数は、すべてのバインドされているデリゲートとオブジェクト参照を解放するために呼び出されます。

定義

  • OnlineStatsReadClass - オンライン統計を読み取るために使用する OnlineStatsRead のクラスです。

実行フロー

InternalOnActivated() が、親クラスの SeqAct_OnlineSubsystemBase に呼び出されると、 OnlineStatsReadClass によって定義されている OnlineReadStats をインスタンス化します。デリゲートがバインドされて、Leaderboards のUI が閉じられる時を検知します。さらに、 OnlineSuppliedUIInterface.ShowOnlineStatsUI() が呼び出され、Leaderboards の UI を読み取り表示します。さらに、 FinishedProcessPlayerIndex() が呼び出されます。

InternalOnShowOnlineStatsUIComplete() が呼び出されると、Closed の出力がアクティベートされます。

CleanUp() が呼び出されると、バインドされているすべてのデリゲートが解放されます。

UnrealScript

SeqAct_Mobile_ShowLeaderboardsUI.uc
/**
 * A Mobile Show Leaderboards UI Action is used to display the leaderboards UI.
 *
 * Copyright 1998-2012 Epic Games, Inc. All Rights Reserved.
 */
class SeqAct_Mobile_ShowLeaderboardsUI extends SeqAct_OnlineSubsystemBase
`if(`notdefined(USE_GAMECENTER))
  abstract
  HideDropDown;
`else
  ;

var() const class<OnlineStatsRead> OnlineStatsReadClass;

/**
 * Called when this sequence action should do something attached with a local user num
 */
protected function InternalOnActivated()
{
  local OnlineSubsystem OnlineSubsystem;
  local OnlineSuppliedUIInterface OnlineSuppliedUIInterface;
  local array<UniqueNetId> PlayerIds;
  local OnlineStatsRead OnlineStatsRead;
  local int i;

  // If this class is none, then abort
  if (OnlineStatsReadClass == None)
  {
    return;
  }

  // Get the online sub system
  OnlineSubSystem = class'GameEngine'.static.GetOnlineSubsystem();
  // Check that the online subsystem is accessible
  if (OnlineSubSystem != None)
  {
    // Create the PlayerIds array from all the processing players array
    for (i = 0; i < ProcessingPlayers.Length; ++i)
    {
      PlayerIds.AddItem(ProcessingPlayers[i].LocalNetId);
    }

    // Get the online supplied UI interface
    OnlineSuppliedUIInterface = OnlineSuppliedUIInterface(OnlineSubSystem.GetNamedInterface('SuppliedUI'));
    if (OnlineSuppliedUIInterface != None)
    {
      // Instance the online stats read class
      OnlineStatsRead = new () OnlineStatsReadClass;
      if (OnlineStatsRead != None)
      {
        // Bind to the online stats UI delegate
        OnlineSuppliedUIInterface.AddShowOnlineStatsUICompleteDelegate(InternalOnShowOnlineStatsUIComplete);

        // Show the online stats UI
        OnlineSuppliedUIInterface.ShowOnlineStatsUI(PlayerIds, OnlineStatsRead);
        FinishedProcessPlayerIndex(true, true);
      }
    }
  }
  else
  {
    FinishedProcessPlayerIndex(false);
  }
}

/**
 * Delegate fired when the supplied stats UI is closed
 */
function InternalOnShowOnlineStatsUIComplete()
{
  // Activate the fourth output
  ForceActivateOutput(4);
}

/**
 * Called when this Kismet node should clean up all variable or delegate references so that garbage collection can occur
 *
 * @param    IsExiting      If exiting, then clean up everything
 */
function CleanUp(optional bool IsExiting)
{
  local OnlineSubsystem OnlineSubsystem;
  local OnlineSuppliedUIInterface OnlineSuppliedUIInterface;

  Super.CleanUp(IsExiting);

  if (IsExiting)
  {
    // Get the online sub system
    OnlineSubSystem = class'GameEngine'.static.GetOnlineSubsystem();
    // Check that the online subsystem is accessible
    if (OnlineSubSystem != None)
    {
      // Get the online supplied UI interface
      OnlineSuppliedUIInterface = OnlineSuppliedUIInterface(OnlineSubSystem.GetNamedInterface('SuppliedUI'));
      if (OnlineSuppliedUIInterface != None)
      {
        OnlineSuppliedUIInterface.ClearShowOnlineStatsUICompleteDelegate(InternalOnShowOnlineStatsUIComplete);
      }
    }
  }
}
`endif

defaultproperties
{
  ObjName="Show Leaderboards UI"

  OutputLinks(4)=(LinkDesc="Closed")
}

Kismet の見本

KismetOnlineSubsystem_15.png

ダウンロード


  • Kismet Online Subsystem のソースのみは、 ここ からダウンロードすることができます。
  • サンプルのマップが付いている Kismet Online Subsystem は、 ここ からダウンロードすることができます。
  • Jazz Jackrabbit Example を以下からダウンロードすることができます。