UDN
Search public documentation:

KismetOnlineSubsystemCH
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 Home > 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 版本。 最好的方法是有一个动态加载关卡,其中包含 Game Center Kismet 节点,还有一个其中包含 Steam Kismet 节点。

Unrealscript 预处理概述


预处理器在 Unrealscript 中的工作方式与预处理器使用其他语言(如 C++)的工作方式非常相近。 预处理器是一种非常简单的脚本,它可以根据 if 和 else 声明语句来定义常量和 Unrealscript 的包含/非包含部分。

在这段代码中,预处理将会根据定义的常量包含其中的两段代码。 如果 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_STEAMWORKSUSE_GAMECENTER 在一个名为 Globals.uci 的文件中进行定义。将您不想使用的那一个注释掉。 当您修改 Globals.uci时,Unrealscript 编译器不会自动接收。 因此,您将需要在您更改它的时候强制对 Unrealscript 进行重新编译。

垃圾回收概述


A lot of the tasks that the Online Subsystem performs are done asynchronously. 因此,许多任务会在它们结束后调用代理。 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 Actions(序列操作)的基类。 这个基类会处理排队在线子系统指令,管理玩家信息并清理。 它还会处理大多数输出链接调用。 如果您需要创建一个采用任何在线子系统的新的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 输出被激活,否则会激活 Failed 输出。 如果子类的 Kismet 节点同时处理了所有玩家,那么设置 bProcessedAllPlayers。
  • InternalOnActivated - 这个函数是一个存根函数,在 Activated 中进行调用。 这是为了供子类扩展,因为子类不应扩展 Activated 事件。
  • CleanUp - 该函数可以通过 GameKismetSceneClient 以及在 FinishedProcessPlayerIndex 内部进行调用。 该函数应该通过绑定到代理或请求一些其他类型的清除处理的子类进行扩展。 如果 IsExiting 为 true,那么您应该清除所有内容。
  • NotifyGameKismetSceneClientCreated - 该函数被另一个 SeqAct_OnlineSubsystemBase 调用来表明 GameKismetSceneClient 已经创建完成。 当 SeqAct_OnlineSubsystemBase 第一个被激活的时候,它会首先尝试创建一个 GameKismetSceneClient 来插入到本地玩家的视图中。 当创建成功完成后,它会通知所有其他 SeqAct_OnlineSubsystemBase 不要尝试再次创建它。

定义

  • 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 这项操作完成,它们不需要再进行这项操作。 如果 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。

虚幻脚本

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 解锁成就。

KismetOnlineSubsystem_00.png

KismetOnlineSubsystem_07.jpg

函数

  • InternalOnActivated - 该函数由父类 SeqAct_OnlineSubsystemBase 进行调用。 Game Center 在解锁任何诸如 OnlineSubsystem.PlayerInterface.ReadAchievements() 前会首先要求您读取成就。 Steamworks 就调用 BeginUnlockAchievement()
  • InternalOnReadAchievementsComplete - 通过一个代理在 OnlineSubsystem.PlayerInterface 中调用这个函数。 调用它之后,它会检查是否已经在线解开了这个成就。 如果成就已经解开,并且 AlwaysUnlockAchievement 为 true,那么会调用 InternalOnUnlockAchievementComplete 。 否则,会调用 BeginUnlockAchievement()
  • BeginUnlockAchievement - 这个函数会将调用传递给 OnlineSubsystem.PlayerInterface.UnlockAchievement()
  • InternalOnReadAchievementsComplete - 通过一个代理在 OnlineSubsystem.PlayerInterface 中调用这个函数。 调用这个函数后,它会调用 FinishedProcessPlayerIndex()
  • CleanUp - 它会处理所有绑定代理的清理工作。

定义

  • AchievementId - 您希望解锁的成就的 Id。 它也可以通过将一个 Kismet Int Variable 附加给这个变量链接进行定义。
  • AlwaysUnlockAchievement - 如果该选项为 true,那么 Kismet Sequence Action 仍然会输出 "Succeeded",即使该成就已经被成功解锁。 Game Center 专用
  • DownloadedAchievements - 通过在线源下载的成就的数组。 Game Center 专用

Game Center 执行工作流

InternalOnActivated() 被调用后,会将一个代理添加给 OnlineSubsystem.PlayerInterface.ReadAchievements 完成时调用的 OnlineSubsystem.PlayerInterface 函数。

当在线子系统在线完成成就读取工作后,它会调用 InternalOnReadAchievementsComplete()InternalOnReadAchievementsComplete() 会检查成就是否已经被解锁。 如果还没有解锁,那么会调用 BeginUnlockAchievement() 。 否则,如果 AlwaysUnlockAchievement 为 true,那么会调用 InternalOnUnlockAchievementComplete()BeginUnlockAchievement() 会绑定到另一个解锁成就完成时调用的在线子系统代理。 然后 OnlineSubsystem.PlayerInterface.UnlockAchievement() 会被调用。

成就完成解锁后,会调用 InternalOnUnlockAchievementComplete() ,它会调用 FinishedProcessPlayerIndex()

在调用 CleanUp 后,所有绑定的代理都会被删除。

Steamworks 执行工作流

调用 InternalOnActivated() 的时候,会调用 BeginUnlockAchievement()BeginUnlockAchievement() 会绑定到另一个解锁成就完成时调用的在线子系统代理。 然后 OnlineSubsystem.PlayerInterface.UnlockAchievement() 会被调用。

成就完成解锁后,会调用 InternalOnUnlockAchievementComplete() ,它会调用 FinishedProcessPlayerIndex()

在调用 CleanUp 后,所有绑定的代理都会被删除。

虚幻脚本

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来说,此系统允许您上传新的统计在排行榜上使用。 对Steamworks来说,此系统允许您添加,减少或设置成就的统计以供使用。

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允许您链接此统计到附加的Kismet变量。
  • SIntStat - SStat延展。 定义一个增加,减少或设置统计的值。
  • SIntStat - SStat延展。 定义一个增加,减少或设置统计的值。
  • EModifyMethod - 此函数设置了您希望如何修改在线统计。
  • OnlineStatsWriteClass - 此函数定义了当写入在线子系统时哪个OnlineStatsWrite会归入实例。
  • OnlineStatsReadClass - 此函数定义了当写入在线子系统时哪个OnlineStatsRead会归入实例。
  • IntStats -要上传到在线子系统的统计定义数组。
  • FloatStats -要上传到在线子系统的统计定义数组。
  • ModifyMethod -定义采用何种方式来修改在线统计。 唯一的设置只能在Game Center进行,Steamworks能够增加,减少或设定。
  • SessionName - 定义了对话名称, 默认为"Game"。

Game Center 执行工作流

在GameCenter,调用 InternalOnActivated() 的时候,会立即调用 SetStatValue()SetStatValue() 使用 OnlineStatsWriteClass 定义的类来实例化OnlineStatsWrite。 然后迭代 IntStatsFloatStats 并把那些统计的值写入实例化的OnlineStatsWrite类。 然后它在线写入统计并调用 FinishedProcessPlayerIndex().

CleanUp() 被调用,所有绑定的代理都被释放。 任何对象参照也同样被释放。

Steamworks 执行工作流

在Steamworks中,何时调用 InternalOnActivated() 取决于 ModifyMethod 的2个函数值可被调用。 如果 ModifyMethod 为MM_Add 或 MM_Subtract, 则调用 ReadOnlineStats() ,这是因为在线统计值需要被读取来通过增加或减少其数值来修改。 否则,会调用 SetStatValue()

ReadOnlineStats() 被调用, 会绑定一个代理并创建一个OnlineStatsRead的实例。 然后 OnlineSubsystem.StatsInterface.ReadOnlineStats() 会被调用。 当读取在线统计完成后,会调用 InternalOnReadOnlineStatsComplete 。 然后 InternalOnReadOnlineStatsComplete 会与 IntStatsFloatStats 数组交互并修改统计数值。 修改的统计会写入OnlineStatsWrite实例然后通过调用 OnlineSubsystem.StatsInterface.WriteOnlineStats() 发给Steamworks。 如果 OnlineSubsystem.StatsInterface.WriteOnlineStats() 被传递,则代理被绑定并且 OnlineSubsystem.StatsInterface.FlushOnlineStats() 被调用。 当在线统计被刷新时, InternalOnFlushOnlineStatsComplete() 被调用然后调用 FinishedProcessPlayerIndex().

SetStatValue() 被调用,它会交互 IntStatsFloatStats 数组并设定统计数值。 修改的统计会写入OnlineStatsWrite实例然后通过调用 OnlineSubsystem.StatsInterface.WriteOnlineStats() 发给Steamworks。 如果 OnlineSubsystem.StatsInterface.WriteOnlineStats() 被传递,则代理被绑定并且 OnlineSubsystem.StatsInterface.FlushOnlineStats() 被调用。 当在线统计被刷新时, InternalOnFlushOnlineStatsComplete() 被调用然后调用 FinishedProcessPlayerIndex().

CleanUp() 被调用,所有绑定的代理都被释放。 任何对象参照也同样被释放。

虚幻脚本

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序列动作尝试通过Steamworks读取来强迫客户端刷新其成就。

KismetOnlineSubsystem_02.png

函数

  • InternalOnActivated -当此函数被调用,它会绑定代理并调用 OnlineSubsystem.PlayerInterface.ReadAchievements() .
  • InternalOnReadAchievementsComplete - 当 OnlineSubsystem.PlayerInterface.ReadAchievements() 完成从Steamworks读取成就时,此函数被调用。 它会调用 FinishedProcessPlayerIndex().
  • CleanUp - 此函数清理任何被绑定的代理。

执行工作流

InternalOnActivated() 被调用,它首先会绑定 OnlineSubsystem.PlayerInterface 的代理。 当在线子系统完成从Steamworks读取成就后,该代理会被调用。 然后 OnlineSubsystem.PlayerInterface.ReadAchievements() 会被调用。 成就完成解锁后,会调用 InternalOnReadAchievementsComplete() ,它会调用 FinishedProcessPlayerIndex()

CleanUp() 被调用,任何绑定的代理都被释放。

虚幻脚本

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序列动作被用来重置成就和/或为玩家保存的统计。 Kismet序列动作只能被用来做调试之用,因为它直接与Steamworks在线子系统绑定。

KismetOnlineSubsystem_03.png

函数

  • InternalOnActivated -如果此函数被调用,它会调用 OnlineSubsystemSteamworks.ResetStats() .

定义

  • ResetAchievements - 如果此函数为true,则成就也同样会被重置。

执行工作流

InternalOnActivated() 被调用,它会调用 OnlineSubsystemSteamworks.ResetStats() . 然后它会调用 FinishedProcessPlayerIndex() .

虚幻脚本

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序列动作允许您稍后读取Kismet内的在线统计。

KismetOnlineSubsystem_04.png

KismetOnlineSubsystem_09.jpg

函数

  • InternalOnActivated - 该函数由父类 SeqAct_OnlineSubsystemBase 进行调用。 然后开始读取Steamworks的统计的调用。
  • InternalOnReadOnlineStatsComplete -当从Steamworks的统计读取完成时,这个函数被调用。 它最终会调用 FinishedProcessPlayerIndex().
  • CleanUp -此函数清理任何被Kismet序列动作使用的代理和对象参照。

定义

  • EReadStatsMethod -该枚举定义了读取统计的不同方法。
  • SLinkedVariableName -存放您需要读取的统计的ID的结构,以及链接到附属的Kismet变量的名称。
  • OnlineStatsReadClass -在线读取统计类,用来读取在线统计。
  • ReadStatsMethod - EReadStatsMethod 的变量声明。
  • StartRankIndex -如果使用读取统计排名方法,将开始使用rank index(排名索引).
  • RowCount -如使用read stats rank(读取统计排名)或rank around player方式所需行数。
  • LinkedVariableNames - 输出的链接变量。
  • Rank 映射为Kismet变量的排名变量。

执行工作流

InternalOnActivated() 被调用,它首先基于 OnlineStatsReadClass 中定义的类实例化 OnlineStatsReadOnlineSubsystem.StatsInterface.FreeStats() 随后被调用来清理 OnlineStatsRead 实例。 随后代理被绑定,读取在线统计的请求完成。 读取在线统计的方法由 ReadStatsMethod 变量来定义。 这可能调用 OnlineSubsystem.StatsInterface.ReadOnlineStatsForFriends() , OnlineSubsystem.StatsInterface.ReadOnlineStatsByRank() , OnlineSubsystem.StatsInterface.ReadOnlineStatsByRankAroundPlayer()OnlineSubsystem.StatsInterface.ReadOnlineStats(). 当在线统计完成时, InternalOnReadOnlineStatsComplete() 被调用,此函数负责得到统计并将它们输出到附加的Kismet顺序变量。 排名也在此设置。 PopulateLinkedVariableValues() 被调用来复制映射的属性.然后调用 FinishedProcessPlayerIndex()

CleanUp() 被调用,所有绑定的代理和对象实例都被释放。

虚幻脚本

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序列动作被激活,它会弹出Game Center的成就界面。 一般这个动作会自动暂停游戏。

KismetOnlineSubsystem_05.png

函数

  • InternalOnActivated -当此函数被调用,它会调用 OnlineSubSystem.PlayerInterfaceEx.ShowAchievementsUI() .

执行工作流

InternalOnActivated() 被父类调用, SeqAct_OnlineSubsystemBase 随后会调用 OnlineSubSystem.PlayerInterfaceEx.ShowAchievementsUI() . 然后它会调用 FinishedProcessPlayerIndex() .

虚幻脚本

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序列动作被激活,它会弹出Game Center的排行榜界面。 一般这个动作会自动暂停游戏。

KismetOnlineSubsystem_06.png

函数

  • InternalOnActivated -当此函数被调用,它会创建基于 OnlineStatsReadClass 值的 OnlineStatsRead 实例。 随后 OnlineSuppliedUIInterface.ShowOnlineStatsUI() 被调用来弹出Game Center的排行榜界面。 一个代理也被绑定,当界面关闭后,会调用 InternalOnShowOnlineStatsUIComplete
  • InternalOnShowOnlineStatsUIComplete - 当排行榜界面关闭后,此函数被调用。
  • CleanUp -此函数被调用来清空所有绑定的代理和对象参照。

定义

  • OnlineStatsReadClass -您想用来读取在线统计的OnlineStatsRead类

执行工作流

InternalOnActivated() 被父类 SeqAct_OnlineSubsystemBase 调用,它从 OnlineStatsReadClass 定义的OnlineReadStats获得实例。 一个代理被绑定来侦测排行榜界面何时被关闭。 OnlineSuppliedUIInterface.ShowOnlineStatsUI() 随后被调用来读取和显示排行榜界面。 FinishedProcessPlayerIndex() 随后被调用。

InternalOnShowOnlineStatsUIComplete() 被调用,"Closed"输出被激活。

CleanUp() 被调用,所有绑定的代理都被释放。

虚幻脚本

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

下载