UDN
Search public documentation:
KismetOnlineSubsystem
日本語訳
中国翻译
한국어
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
中国翻译
한국어
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 Online Subsystem
Kismet Online Subsystem
Last tested against UDK Jan, 2012
PC compatible
iOS compatible
Overview
Kismet is visual scripting system designed to allow you to build game play logic using nodes. It's a very useful tool all the way from the prototype phase to the production phase. Because some of the game play logic exists within Kismet, it is only natural to also want to be able to access Game Center or Steam Works through Kismet as well. This development kit gems adds new Kismet nodes which allows you to interface with Game Center or Steam Works.
A word about Unrealscript preprocessing
The preprocessor in Unrealscript works very similar to how the preprocessor works in other languages such as C++. A preprocessor is a very simple script which is able to define constants and include/exclude portions of Unrealscript depending on if and else statements. In this snippet of code, there are two sections of code which the preprocessor will include depending on the constants defined. If USE_STEAMWORKS is defined, then everything inside the if statement is included for compilation. Otherwise if USE_STEAMWORKS is not defined, then everything inside the else statement is included for compilation. Due to the difference between the way Game Center and Steam Works work, it was required to have different pathways for code to run.
/**
* 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
}
}
A word about Garbage Collection
A lot of the tasks that the Online Subsystem performs are done asynchronously. Thus, a lot of tasks use delegates which are called when they've finished. The Kismet nodes themselves bind to the delegates, which if not cleaned up properly cause the level to not be garbage collected and removed from memory. An interaction called GameKismetSceneClient is used to detect when a game session has ended at which time it asks all of the Kismet nodes to clean themselves up.
/**
* 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 and Game Center Kismet Nodes
SeqAct_OnlineSubsystemBase
SeqAct_OnlineSubsystemBase is an abstract class where all of the other Kismet Sequence Actions are based of. This base class handles queuing online subsystem instructions, organizing player information and clean up. It also handles most of the output link calls. If you need to create a new Kismet Sequence Action which utilizes any of the online subsystem then it is best to subclass this class. When this Kismet Sequence Action is activated, it will compile an array of players that it needs to work on based on the attached Kismet Variable Players node attached. It then processes the players array sequentially. This Kismet Sequence Action will output to Out immediately. If the Kismet Sequence Action is busy processing players, it will output to Busy immediately. Depending on the results of the subclassed Kismet Sequence Action it may later output to Succeeded or Failed.Functions
- Activated - This event is called when the Kismet Sequence Action is activated by another Kismet Sequence node.
- FinishedProcessPlayerIndex - This function is call from subclasses when it has finished processing what it needs to do for a player. If bWasSuccessful is true, then the Succeeded output is activated, otherwise the Failed output is activated. Set bProcessedAllPlayers if the subclassed Kismet node has processed all players at once.
- InternalOnActivated - This function is a stub function which is called within Activated. This is for subclasses to extend, as subclasses should not extend the Activated event.
- CleanUp - This function is called from GameKismetSceneClient and from within FinishedProcessPlayerIndex. This function should be extended by subclasses that bind to delegates or require some other kind of clean up. If IsExiting is true, then you should clean everything up.
- NotifyGameKismetSceneClientCreated - This function is called by another SeqAct_OnlineSubsystemBase to indicate that the GameKismetSceneClient has already been created. When a SeqAct_OnlineSubsystemBase is first activated, it will first attempt to create a GameKismetSceneClient to insert into the local player's viewport. When that succeeds, it tells all other SeqAct_OnlineSubsystemBase's to not attempt to create it again.
Definitions
- SProcessingPlayer - This struct defines the player that is being processed or pending processing.
- IsProcessing - When this is true, then any more requests to this Kismet node are immediately rejected as this Kismet node is busy doing a task.
- ProcessingPlayers - The array of players currently being processed. Players are processing sequentially. After each player is processed, it then removes the first entry from the array and then deals with the next entry until this array is empty.
- HasCreatedSceneClient - When this is false, when this Kismet Sequence Action is activated it will attempt to create a GameKismetSceneClient and insert it into the local player's viewport. This is to catch when the game session has ended so that clean up can occur. If this is true, then this Kismet Sequence Action does not attempt to do this.
Execution flow
When this Kismet Sequence Action is activated by another Kismet Sequence Node, Activated() is first called. Activated() checks to see if it needs to create the GameKismetSceneClient, by checking the value of HasCreatedSceneClient. If it does, then it grabs the local player controller and inserts a new GameKismetSceneClient into the viewport. If that succeeds, it then informs all other SeqAct_OnlineSubsystemBase that this has been done and they don't need to do it. If IsProcessing is true, then the "Busy" output is activated and the function stops. Otherwise, it compiles the list of players based on the attached Kismet Player Variable. For each player listed in the Kismet Player Variable, it fills the ProcessingPlayers array with the player index and unique player id. If the ProcessingPlayers array has a size, IsProcessing is flagged true and InternalOnActivated() is called. The "Out" output is then activated. When the subclass has finished processing a player, FinishedProcessPlayerIndex() is called. If bProcessedAllPlayers is true, then the ProcessingPlayers array is emptied. Otherwise the first entry in ProcessingPlayers is removed only. If the ProcessingPlayers is not empty, then the first entry is then processed by calling InternalOnActivated() again. Else if bWasSuccessful is true, then the "Succeeded" output is activated; otherwise the "Failed" output is activated. When CleanUp() is called and IsExiting is true, then ProcessingPlayers array is emptied. When NotifyGameKismetSceneClientCreated() is called, HasCreatedSceneClient is set true.Unrealscript
/**
* 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
This Kismet Sequence Action unlocks an achievement based on the Id attached or defined in the properties panel.
Functions
- InternalOnActivated - This function is called by the parent class, SeqAct_OnlineSubsystemBase. Game Center first requires you to read the achievements before unlocking any thus OnlineSubsystem.PlayerInterface.ReadAchievements() is called. Steamworks just called BeginUnlockAchievement().
- InternalOnReadAchievementsComplete - This is called from OnlineSubsystem.PlayerInterface via a delegate. When this is called, it checks if the achievement has been achieved online. If it has been and AlwaysUnlockAchievement is true, then InternalOnUnlockAchievementComplete is called. Otherwise BeginUnlockAchievement() is called.
- BeginUnlockAchievement - This sends a call to OnlineSubsystem.PlayerInterface.UnlockAchievement().
- InternalOnUnlockAchievementComplete - This is called from OnlineSubsystem.PlayerInterface via a delegate. When this is called, it calls FinishedProcessPlayerIndex().
- CleanUp - This handles cleaning up all of the bound delegates.
Definitions
- AchievementId - The Id of the achievement you wish to unlock. This can also be defined by attaching a Kismet Int Variable to the variable links.
- AlwaysUnlockAchievement - If true, then the Kismet Sequence Action will still output "Succeeded" even if the achievement has already been unlocked. Game Center only
- DownloadedAchievements - An array of the achievements downloaded from an online source. Game Center only
Game Center Execution flow
When InternalOnActivated() is called, a delegate is added to OnlineSubsystem.PlayerInterface which is called when OnlineSubsystem.PlayerInterface.ReadAchievements has completed. When the online subsystem has finished reading the achievements online, it then calls InternalOnReadAchievementsComplete(). InternalOnReadAchievementsComplete() checks to see if the achievement has already been unlocked or not. If it has not been unlocked, then BeginUnlockAchievement() is called. Otherwise if AlwaysUnlockAchievement is true, then InternalOnUnlockAchievementComplete() is called. BeginUnlockAchievement() binds to another online subsystem delegate which is called when unlocking the achievement finishes. OnlineSubsystem.PlayerInterface.UnlockAchievement() is then called. When the achievement has finished unlocking, InternalOnUnlockAchievementComplete() is then called which calls FinishedProcessPlayerIndex(). When CleanUp is called, all of the bound delegates are removed.Steamworks Execution flow
When InternalOnActivated() is called BeginUnlockAchievement() is called. BeginUnlockAchievement() binds to another online subsystem delegate which is called when unlocking the achievement finishes. OnlineSubsystem.PlayerInterface.UnlockAchievement() is then called. When the achievement has finished unlocking, InternalOnUnlockAchievementComplete() is then called which calls FinishedProcessPlayerIndex(). When CleanUp is called, all of the bound delegates are removed.Unrealscript
/**
* 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)
}
Example Kismet
SeqAct_ModifyOnlineStat
Modify Online Stat is a Kismet Sequence Action which allows you to modify a stat online used by the online subsystem. For Game Center, this system allows you to upload new stats to use on leaderboards. For Steamworks this system allows you to add, subtract or set stats to use for achievements.
Functions
- InternalOnActivated - Called from the parent class, SeqAct_OnlineSubsystemBase. On Game Center this will always just call SetStatValue(). On Steamworks, depending on that value of ModifyMethod, this may call either ReadOnlineStats() (if you wanted to add or subtract for existing stats) or SetStatValue().
- ReadOnlineStats - This is only called on the Steamworks version when you want to add or subtract from an existing stat. This reads the online stat, and then calls InternalOnReadOnlineStatsComplete() when it is finished.
- InternalOnReadOnlineStatsComplete - After the stats have been read, the stat is either added to or subtracted from and then the result is sent back to Steamworks.
- SetStatValue - This sets the stats and sends it back to either Game Center or Steamworks.
- InternalOnFlushOnlineStatsComplete - This is only called on the Steamworks version when the stats have been written to the online database.
- CleanUp - This is called when the Kismet node should clean itself up.
Definitions
- SStat - Base struct definition. StatId refers the the stat id on Game Center or Steamworks. LinkedVariableName allows you to link this stat id to an attached Kismet Variable.
- SIntStat - Extension of SStat. Defines a value to add, subtract or set the stat.
- SFloatStat - Extension of SStat. Defines a value to add, subtract or set the stat.
- EModifyMethod - This sets how you want to modify the online stat.
- OnlineStatsWriteClass - This defines which OnlineStatsWrite to instance when writing to the online subsystem.
- OnlineStatsReadClass - This defines which OnlineStatsRead to instance when writing to the online subsystem.
- IntStats - Array of stat definitions to upload to the online subsystem.
- FloatStats - Array of stat definitions to upload to the online subsystem.
- ModifyMethod - Defines which method to modify the online stat. Only set is available on Game Center, Steamworks is able to add, subtract or set.
- SessionName - Defines the session name, this is defaulted to "Game".
Game Center Execution flow
On GameCenter, when InternalOnActivated() is called, SetStatValue() is called immediately. SetStatValue() instances an OnlineStatsWrite using the class defined in OnlineStatsWriteClass. It then iterates over IntStats and FloatStats and writes the values of those stats to the instanced OnlineStatsWrite class. It then writes these stats online and calls FinishedProcessPlayerIndex(). When CleanUp() is called, all delegates bound are freed. Any object references are also cleared.Steamworks Execution flow
On Steamworks, when InternalOnActivated() is called depending on the value of ModifyMethod two functions can be called. If ModifyMethod is MM_Add or MM_Subtract then ReadOnlineStats() is called, this is because the online stats value needs to be read so that it can be modified either by adding to it or subtracting from it. Otherwise SetStatValue() is called. When ReadOnlineStats() is called, a delegate is bound and an instance of OnlineStatsRead is created. OnlineSubsystem.StatsInterface.ReadOnlineStats() is then called. When reading the online stats is finished, InternalOnReadOnlineStatsComplete is then called. InternalOnReadOnlineStatsComplete then interates over the IntStats and FloatStats arrays and modifies the stats values. The modified stats are then written to an OnlineStatsWrite instance and are then sent to Steamworks by calling OnlineSubsystem.StatsInterface.WriteOnlineStats(). If OnlineSubsystem.StatsInterface.WriteOnlineStats() passed, then a delegate is bound and OnlineSubsystem.StatsInterface.FlushOnlineStats() is called. When the online stats has been flushed, InternalOnFlushOnlineStatsComplete() is called which then calls FinishedProcessPlayerIndex(). When SetStatValue() is called, it interates over the IntStats and FloatStats arrays and sets the stats values. The modified stats are then written to an OnlineStatsWrite instance and are then sent to Steamworks by calling OnlineSubsystem.StatsInterface.WriteOnlineStats(). If OnlineSubsystem.StatsInterface.WriteOnlineStats() passed, then a delegate is bound and OnlineSubsystem.StatsInterface.FlushOnlineStats() is called. When the online stats has been flushed, InternalOnFlushOnlineStatsComplete() is called which then calls FinishedProcessPlayerIndex(). When CleanUp() is called, all delegates bound are freed. Any object references are also cleared.Unrealscript
/**
* 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")
}
Example Kismet
Steam Works only Kismet Nodes
SeqAct_RefreshAchievements
This Kismet Sequence Action attempts to force the client to refresh it's achievements by reading them from Steamworks.
Functions
- InternalOnActivated - Which this function is called, it binds to a delegate and calls OnlineSubsystem.PlayerInterface.ReadAchievements().
- InternalOnReadAchievementsComplete - This function called when OnlineSubsystem.PlayerInterface.ReadAchievements() has finished reading achievements from Steamworks. It calls FinishedProcessPlayerIndex().
- CleanUp - This function cleans up any delegates that have been bound.
Execution flow
When InternalOnActivated() is called, it first binds to a delegate in OnlineSubsystem.PlayerInterface. This delegate is called when the online subsystem has finished reading the achievements from Steamworks. OnlineSubsystem.PlayerInterface.ReadAchievements() is then called. When reading achievements have finished, InternalOnReadAchievementsComplete() is then called, which then calls FinishedProcessPlayerIndex(). When CleanUp() is called, any delegates that have been bound are freed.Unrealscript
/**
* 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
This Kismet Sequence Action is used to reset achievements and or stats that has been saved for the player. This Kismet Sequence Action should only be used for debugging purposes as it binds directly to the Steamworks online subsystem.
Functions
- InternalOnActivated - When this is called this calls OnlineSubsystemSteamworks.ResetStats().
Definitions
- ResetAchievements - If this is true, then achievements are reset as well.
Execution flow
When InternalOnActivated() is called, it then calls OnlineSubsystemSteamworks.ResetStats(). It then calls FinishedProcessPlayerIndex().Unrealscript
/**
* 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"
}
Example Kismet
SeqAct_ReadOnlineStat
This Kismet Sequence Action allows you to read online stats to use within Kismet later on.
Functions
- InternalOnActivated - This is called by the parent class, SeqAct_OnlineSubsystemBase. This then starts the call to read the stats from Steamworks.
- InternalOnReadOnlineStatsComplete - This is called when reading the stats from Steamworks has finished. It finally calls FinishedProcessPlayerIndex().
- CleanUp - This function cleans up any delegates and object references that is used by this Kismet Sequence Action.
Definitions
- EReadStatsMethod - An enum which defines the different ways to read stats.
- SLinkedVariableName - A struct which holds the an id for the stat you want to read, and a name which links to an attached Kismet Variable.
- OnlineStatsReadClass - Online Read Stats class to use for reading online stats.
- ReadStatsMethod - Variable declaration of EReadStatsMethod.
- StartRankIndex - If use a read stats ranked method, which starting rank index to use.
- RowCount - How many rows to read if using read stats rank or rank around player methods.
- LinkedVariableNames - Linked variables to output to.
- Rank - Ranked variable that is mapped to a Kismet Variable.
Execution flow
When InternalOnActivated() is called, it first instances an OnlineStatsRead based on the class defined in OnlineStatsReadClass. OnlineSubsystem.StatsInterface.FreeStats() is then called to clear the OnlineStatsRead instance. A delegate is then bound and the request to read the online stats is done. The method to read the online stats is defined by the ReadStatsMethod variable. This may call OnlineSubsystem.StatsInterface.ReadOnlineStatsForFriends(), OnlineSubsystem.StatsInterface.ReadOnlineStatsByRank(), OnlineSubsystem.StatsInterface.ReadOnlineStatsByRankAroundPlayer() or OnlineSubsystem.StatsInterface.ReadOnlineStats(). InternalOnReadOnlineStatsComplete() is called when the online stats has finished, this function is responsible for getting the stats and outputting them into the attached Kismet Sequence Variables. Rank is also set here too. PopulateLinkedVariableValues() is called to copy the mapped stat properties across. FinishedProcessPlayerIndex() is then called. When CleanUp() is called, all delegates that have been bound and object instances are freed.Unrealscript
/**
* 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)
}
Example Kismet
Game Center only Kismet Nodes
SeqAct_Mobile_ShowAchievementsUI
When this Kismet Sequence Action is activated, it brings up Game Center's Achievements UI. This normally automatically pauses the game.
Functions
- InternalOnActivated - When this function is called, it calls OnlineSubSystem.PlayerInterfaceEx.ShowAchievementsUI().
Execution flow
When InternalOnActivated() is called by the parent class, SeqAct_OnlineSubsystemBase, it then calls OnlineSubSystem.PlayerInterfaceEx.ShowAchievementsUI(). It then calls FinishedProcessPlayerIndex().Unrealscript
/**
* 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"
}
Example Kismet
SeqAct_Mobile_ShowLeaderboardsUI
When this Kismet Sequence Action is activated, it brings up the Game Center's Leaderboards UI. This normal automatically pauses the game.
Functions
- InternalOnActivated - When this function is called, it creates the instance of OnlineStatsRead based on the OnlineStatsReadClass value. OnlineSuppliedUIInterface.ShowOnlineStatsUI() is then called to bring up Game Center's leaderboards UI. A delegate is also bound which calls InternalOnShowOnlineStatsUIComplete when the UI is closed.
- InternalOnShowOnlineStatsUIComplete - This function called when the Leaderboards UI is closed.
- CleanUp - This function is called to free up all bound delegates and object references.
Definitions
- OnlineStatsReadClass - The class of the OnlineStatsRead you want to use to read the online stats.
Execution flow
When InternalOnActivated() is called by the parent class, SeqAct_OnlineSubsystemBase, it instances the OnlineReadStats defined by OnlineStatsReadClass. A delegate is bound to detect when the leaderboards UI is closed. OnlineSuppliedUIInterface.ShowOnlineStatsUI() is then called to read and show the leaderboards UI. FinishedProcessPlayerIndex() is then called. When InternalOnShowOnlineStatsUIComplete() is called, the "Closed" output is activated. When CleanUp() is called, all bound delegates are freed.Unrealscript
/**
* 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")
}
Example Kismet
