UDN
Search public documentation:
GameCenterCH
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
日本語訳
한국어
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
Game Center(游戏中心)
概述
Game Center是Apple的在线游戏网络。它使得游戏玩家通过iOS设备连接到其上进行成就排行计较及竞赛。使用虚幻引擎3开发的iOS设备的游戏支持Game Center的应用,使得这些游戏具有非常好的社交性游戏体验。 注意: 在以下部分将使用GC指代Game Center。
注意: 重点是 不要 直接在Unrealscript引用 OnlineSubsystemGameCenter,因为这样在您尝试在PC上启动UDK时将会出现问题。始终使用 OnlineSubsystem 引用,并且针对不同的平台,虚幻引擎 3 会自动使用正确的 OnlineSubsystem 类型。
配置设置
UE3通过OnlineSubsystemGameCenter 类来在iOS上支持GC。但是,因为它在启动过程中自动地弹出Game Center 登录/欢迎回来 屏幕。所以GC可以通过配置文件进行启用和禁用。允许开发人员在开发过程中不需要进行 GC 测试的时候关闭它,只需将它设置为 true 即可。否则,将它设置为 false。
[OnlineSubsystemGameCenter.OnlineSubsystemGameCenter] bDisableGameCenter=false
[OnlineSubsystemGameCenter.OnlineSubsystemGameCenter] bDisableGameCenter=false UniqueAchievementPrefix=com.epicgames.exploreue3.achievement_ UniqueCategoryPrefix=com.epicgames.exploreue3.leaderboard_ EpicUniqueAchievementPrefix=com.epicgames.exploreue3.achievement_ EpicUniqueCategoryPrefix=com.epicgames.exploreue3.leaderboard_
编译 OnlineSubsystemGameCenter
在对您的 iOS 游戏使用 Game Center 之前,您可能需要编译 OnlineSubsystemGameCenter。要编译 OnlineSubsystemGameCenter,您需要将其添加给 DefaultEngine.ini 中 EditPackages 数组。
[UnrealEd.EditorEngine] +EditPackages=UTGame +EditPackages=UTGameContent +EditPackages=OnlineSubsystemGameCenter
成就
成就的处理方式和其他平台类似,但当您第一次解除锁定一个成就时GC不会在屏幕上显示信息。当您的游戏代码试图解除锁定一个成就时,它应该首先检查确保它还没有被其他用户解锁。这是您可以使用的一个基本流程:
- 游戏启动 (它仅需要做一次,但是多次操作也可以)。
- 调用在完成阅读成就数据后会通知的 OnlineSub.PlayerInterface.AddReadAchievementsCompleteDelegate() 。
- 调用 OnlineSub.PlayerInterface.ReadAchievements() 来读取成绩。
- 注意,在您的代理中已经读取了成绩,所以现在您可以查询它们的状态。
- 玩家正在玩游戏,满足了成就标准。
- 调用 OnlineSub.PlayerInterface.GetAchievements() 来获得所有成就的状态。
- 在返回的数组中查找匹配的成就ID。
- 请 不要 使用您的成就ID作为成就数组的索引! 首先,成就 ID 会使用 1 作为它们的数组开始索引(Unrealscript及很多其他语言会使用 0 作为数组的开始索引)。
- 如果 bWasAchievedOnline 为 false;
- 那么显示您的UI、播放声音等。
- 调用 OnlineSub.PlayerInterface.UnlockAchievement() 告诉GC玩家已经解除了对该成就的锁定。
成就技术细节
当GC代码启动时,它将会立即开始下载成就,以便当您的游戏代码运行时,它们已经下载完成。但是,保险起见,请使用具有代理的 OnlineSub.PlayerInterface.ReadAchievements() 函数调用,以便确保成就下载已经完成,并且那个 OnlineSub.PlayerInterface.GetAchievements() 将返回真正的有效的结果。 在底层,针对成就有很多复杂的处理,因为如果用户在成就解锁时候下线,GC将不会通知服务器。所以,我们维持了一个本地的成就状态,它被保存到iOS闪存盘中。在这之后无论何时用户连接到GC时,引擎将会检查远程成就状态和本地成就状态,并将二者融合,通过在不显示UI的情况下解锁成就来更新远程服务器端的信息。 注意: 如果成就曾将将它们的 ID 设置为 -1,那么说明成就下载错误,UniqueAchievementPrefix 和/或 EpicUniqueAchievementPrefix 设置不正确。成就示例
在这个示例中,创建成就处理器 actor 类允许您将成就调用传递给它。当然您可以将它转换为其他类,例如您的自定义 GameInfo 类。成就控制器的工作方式是存储等待处理的成就列表(以防连续快速地获得某些成就)然后调用解锁所有等待处理的成就之前将会在它们之间循环的异步函数。 要解锁一个成就,请将这个成就 id 作为一个参数调用 YourAchievementHandler::UnlockAchievement()。这个成就 id 必须与成就 id 末尾的数值匹配。例如,id 为 com.epicgames.exploreue3.achievement_01 的成就将会有一个 id 为 1 的成就。记住成就 id 通常都是从 1 开始的,而不是 0。 第一个检查是为了确保成就 id 在等待处理的成就数组中。这样可以防止成就被多次解锁。如果成就处理器目前没有处理任何成就,那么会开始处理等待处理的成就,同时将 ProcessingAchievements 标记设置为 true。这是为了开始异步成就处理循环。 由此,已经完成分配在从服务器中读取结束的时候调用的代理,同时进行了可以读取成就的异步调用。class YourAchievementHandler extends Actor; // 等待处理的成就 var array<int> PendingAchievements; // 如果我们目前正在处理成就,那么为 True var bool ProcessingAchievements; /** * 为玩家解锁成就 * * @param AchievementId 要解锁的成就 * @param LocalUserNum 本地用户索引 */ function UnlockAchievement(int AchievementId) { local OnlineSubsystem OnlineSubsystem; local int PlayerControllerId; // 这个成就已经处于等待处理状态中,并且正在进行中,所以请等待 if (PendingAchievements.Find(AchievementId) != INDEX_NONE) { return; } // 将这个成就 id 添加到等待处理列表中 PendingAchievements.AddItem(AchievementId); // 如果我们现在没有处理成就,那么马上就开始处理 if (!ProcessingAchievements) { // 连接到 GameCenter 并链接成就代理 OnlineSubsystem = class'GameEngine'.static.GetOnlineSubsystem(); if (OnlineSubsystem != None && OnlineSubsystem.PlayerInterface != None) { // 获取本地玩家控制器 id PlayerControllerId = GetALocalPlayerControllerId(); // 分配读取成就代理 OnlineSubsystem.PlayerInterface.AddReadAchievementsCompleteDelegate(PlayerControllerId, InternalOnReadAchievementsComplete); // 读取所有成就 OnlineSubsystem.PlayerInterface.ReadAchievements(PlayerControllerId); // 设置为 true,防止它再次被执行 ProcessingAchievements = true; } } }
/** * 会返回本地玩家控制器 id。同样的规则适用于 Actor::GetALocalPlayerController()。 * * @return 会返回本地玩家控制器 id */ function int GetALocalPlayerControllerId() { local PlayerController PlayerController; local LocalPlayer LocalPlayer; // 获取本地玩家控制器 PlayerController = GetALocalPlayerController(); if (PlayerController == None) { return INDEX_NONE; } // 获取本地玩家信息 LocalPlayer = LocalPlayer(PlayerController.Player); if (LocalPlayer == None) { return INDEX_NONE; } return class'UIInteraction'.static.GetPlayerIndex(LocalPlayer.ControllerId); }
class YourAchievementHandler extends Actor; // 其中包含所有下载成就的数组 var array<AchievementDetails> DownloadedAchievements; /** * 在异步成就读取完成的时候进行调用 * * @param TitleId 这次读取的标题 id(0 表示当前标题) */ function InternalOnReadAchievementsComplete(int TitleId) { local OnlineSubsystem OnlineSubsystem; local int AchievementIndex, PlayerControllerId; // 确保我们有一个在线子系统和一个相关联的玩家界面 OnlineSubsystem = class'GameEngine'.static.GetOnlineSubsystem(); if (OnlineSubsystem == None || OnlineSubsystem.PlayerInterface == None) { return; } // 获取本地玩家控制器 id PlayerControllerId = GetALocalPlayerControllerId(); // 清除当前下载的成就数组,与此同时复制新数据的时候 DownloadedAchievements.Remove(0, DownloadedAchievements.Length); // 将成就读取到下载的成就数组中 OnlineSubsystem.PlayerInterface.GetAchievements(PlayerControllerId, DownloadedAchievements, TitleId); // 获取多有成就 if (DownloadedAchievements.Length > 0 && PendingAchievements.Length > 0) { // 获取成就索引 AchievementIndex = DownloadedAchievements.Find('Id', PendingAchievements[0]); // 解锁成就 if (AchievementIndex != INDEX_NONE && !DownloadedAchievements[AchievementIndex].bWasAchievedOnline) { // 分配解锁成就完整代理 OnlineSubsystem.PlayerInterface.AddUnlockAchievementCompleteDelegate(PlayerControllerId, InternalOnUnlockAchievementComplete); // 开始解锁处理 OnlineSubsystem.PlayerInterface.UnlockAchievement(PlayerControllerId, PendingAchievements[0]); } } // 删除这个代理引用,这样垃圾回收才可以进行 OnlineSubsystem.PlayerInterface.ClearReadAchievementsCompleteDelegate(PlayerControllerId, InternalOnReadAchievementsComplete); }
class YourAchievementHandler extends Actor; /** * 在成就解锁完成的时候进行调用 * * @param bWasSuccessful 如果一部操作完成,没有发生错误,那么为 true,如果有错误为 false */ function InternalOnUnlockAchievementComplete(bool bWasSuccessful) { local OnlineSubsystem OnlineSubsystem; local PlayerController PlayerController; local int AchievementIndex, PlayerControllerId; // 获取本地玩家控制器 id PlayerControllerId = GetALocalPlayerControllerId(); if (bWasSuccessful && PendingAchievements.Length > 0) { // 获取本地玩家控制器 PlayerController = GetALocalPlayerController(); if (PlayerController != None) { // 获取成就索引 AchievementIndex = DownloadedAchievements.Find('Id', PendingAchievements[0]); // 将成就显示在用户界面播放器上 } } // 弹出处理好的成就,不管它是否成功处理完成 PendingAchievements.Remove(0, 1); // 确保我们有一个在线子系统和一个相关联的玩家界面 OnlineSubsystem = class'GameEngine'.static.GetOnlineSubsystem(); if (OnlineSubsystem == None || OnlineSubsystem.PlayerInterface == None) { return; } // 如果我们还有等待处理的成就,那么处理下一个 if (PendingAchievements.Length > 0) { // 连接到 GameCenter 并链接成就代理 // 分配读取成就代理 OnlineSubsystem.PlayerInterface.AddReadAchievementsCompleteDelegate(PlayerControllerId, InternalOnReadAchievementsComplete); // 读取所有成就 OnlineSubsystem.PlayerInterface.ReadAchievements(PlayerControllerId); } else // 否则,我们就完蛋了,所以清除 { // 清除代理绑定 OnlineSubsystem.PlayerInterface.ClearUnlockAchievementCompleteDelegate(PlayerControllerId, InternalOnUnlockAchievementComplete); // 设置这个标记说明我们不再处理成就 ProcessingAchievements = false; } }
class YourAchievementHandler extends Actor; /** * 当 actor 被销毁的时候进行调用 */ event Destroyed() { local OnlineSubsystem OnlineSubsystem; local int PlayerControllerId; Super.Destroyed(); // 确保我们有一个在线子系统和一个相关联的玩家界面 OnlineSubsystem = class'GameEngine'.static.GetOnlineSubsystem(); if (OnlineSubsystem == None || OnlineSubsystem.PlayerInterface == None) { return; } // 如果我们还在处理成就,那么必须清楚分配的代理,这样才可以正常进行垃圾回收 if (ProcessingAchievements) { // 获取本地玩家控制器 id PlayerControllerId = GetALocalPlayerControllerId(); // 删除这个代理引用,这样垃圾回收才可以进行 OnlineSubsystem.PlayerInterface.ClearReadAchievementsCompleteDelegate(PlayerControllerId, InternalOnReadAchievementsComplete); // 清除代理绑定 OnlineSubsystem.PlayerInterface.ClearUnlockAchievementCompleteDelegate(PlayerControllerId, InternalOnUnlockAchievementComplete); } }
排行榜
GC的另一个主要的非-多玩家功能是排行榜。再次说明,排行榜的工作方式和其他平台上的该功能类似,但是GC排行榜有一些限制。GC实际上仅支持具有多个“类别”的一个“排行榜”。我们把这些类别当做一个排行榜表格中的一个列栏来对待。但是,所以类别都是使用相同的格式和标签来显示的(具有它们的UI),所以这些类别可以是时间、或整数点、或整数技能等。另外,它们或者是升序排列或者是降序排列。这将会影响您设置您的分数的方式。 要报告分数,请创建 OnlineStatsWrite 的一个子类,然后在默认属性块中设置它的属性(将 1 作为基础 ID)。在这里,您可以创建这个对象的一个实例,设置这些值,然后通过 OnlineSub.StatsInterface.WriteOnlineStats() 进行报告。
class YourOnlineStatsWrite extends OnlineStatsWrite; const PROPERTY_KILLS = 1; const PROPERTY_LEVEL = 2; const PROPERTY_GOLD = 3; defaultproperties { Properties=((PropertyId=PROPERTY_KILLS,Data=(Type=SDT_Int32,Value1=0)),(PropertyId=PROPERTY_LEVEL,Data=(Type=SDT_Int32,Value1=0)),(PropertyId=PROPERTY_GOLD,Data=(Type=SDT_Int32,Value1=0))) }
- 读取玩家所杀的人的保存值(您可以使用一个配置值或 BasicSaveObject/BasicLoadObject)
- 相应地增加这个值
- 将这个分数写到 Game Center
local OnlineSubsystem OnlineSubsystem; local OnlineSuppliedUIInterface OnlineSuppliedUIInterface; local array<UniqueNetId> PlayerIds; local UniqueNetId PlayerId; local YourOnlineStatsRead YourOnlineStatsRead; local PlayerController PlayerController; local LocalPlayer LocalPlayer; local byte LocalUserNum; // 获得在线子系统 OnlineSubSystem = class'GameEngine'.static.GetOnlineSubsystem(); // 检查在线子系统是否可以访问 if (OnlineSubSystem != None) { // 通过本地玩家控制器创建 PlayerId 数组 PlayerController = GetALocalPlayerController(); if (PlayerController != None) { // 获取本地玩家 LocalPlayer = LocalPlayer(PlayerController.Player); if (LocalPlayer != None) { // 获取本地用户数量 LocalUserNum = class'UIInteraction'.static.GetPlayerIndex(LocalPlayer.ControllerId); // 从本地用户数获取唯一的玩家 id OnlineSubSystem.PlayerInterface.GetUniquePlayerId(LocalUserNum, PlayerId); // 将唯一的玩家 id 添加到玩家 id 数组 PlayerIds.AddItem(PlayerId); // 获取在线提供的 UI 界面 OnlineSuppliedUIInterface = OnlineSuppliedUIInterface(OnlineSubSystem.GetNamedInterface('SuppliedUI')); if (OnlineSuppliedUIInterface != None) { // 实例化在线统计数据并读取类 YourOnlineStatsRead = new () class'YourOnlineStatsRead'; if (YourOnlineStatsRead!= None) { // 现在在线统计数据 UI OnlineSuppliedUIInterface.ShowOnlineStatsUI(PlayerIds, YourOnlineStatsRead); } } } } }
class YourOnlineStatsRead extends OnlineStatsRead; const PROPERTY_KILLS = 1; const PROPERTY_LEVEL = 2; const PROPERTY_GOLD = 3; defaultproperties { ColumnIds=(PROPERTY_KILLS,PROPERTY_LEVEL,PROPERTY_GOLD) }
排行榜技术细节
和成就类似,如果在用户离线时报告了分数,那么GC将不能自动地重新提交它。所以,我们在底层做了很多处理来保存报告的最高和最低分数到磁盘中,然后我们在用户下次登录时把它们报告到服务器。我们不知道排行榜是升序的还是降序的,这也是为什么提交最高分和最低分的原因。 在读取统计数据的时候,所有分数都以 QWORDs / Int64s 形式进行报告(64 位整数,范围是 − 9,223,372,036,854,775,808 到 9,223,372,036,854,775,807),这种形式也就是 GC 存储排行榜值的方式。多玩家
支持多玩家 - 目前仅是基于大厅的匹配比赛,最多4人。由于GC是通过 GKMatch 对象来处理网络流量的,所以通过GC匹配竞赛的玩家可以通过游戏网络进行通信。这4台iOS设备中的其中一个台将作为服务器。
Matchmaking(玩家匹配)
匹配玩家是通过GC的内置界面完成的:OnlineSuppliedUIInterface(OnlineSub.GetNamedInterface('SuppliedUI')).ShowMatchmakingUI();
邀请
GC支持从玩家的匹配UI中邀请玩家。如果邀请的玩家正在玩另一个游戏,那么该玩家将会看到一个邀请信息,如果它们接受了邀请,那么它将会启动您的游戏,并且当游戏启动时,GC匹配UI将会滑出来向玩家显示他们正在加入这场比赛。 如果玩家已经在您的游戏中了,那么在 PlayerController (参照 OnGameInviteAccepted() )中的代码将会毁掉当前的任何在线游戏,并弹出GC匹配UI来向玩家显示它们正在加入的比赛。注意,被邀请的玩家实际上可能变为服务器。语音聊天
GC有一个非常基本的、容易使用的语音聊天系统。当您进入到多人游戏后,您将会调用 OnlineSub.VoiceInterface.StartNetworkedVoice() 来让玩家彼此之间进行交谈。也可以通过使用标准的函数( MuteRemoteTalker() , IsRemotePlayerTalking() , 等)来静音和查询当前正在说话的人。垃圾回收
每当您绑定到任何 Online Subsystem(在线子系统)代理时,确保它们始终会在关卡预计要进行垃圾回收的时候被清除,这一点很重要。它很重要,因为不这样做的话,垃圾回收将会失败,内存块将永远不会被释放! Kismet 在线子系统开发工具包精华文章会在以后深入地对它进行说明。
相关主题
- Kismet 在线子系统 - 该开发工具包精华文章将会演示如何添加与 Game Center 相连接的 Kismet 节点。
下载
- YourAchievementHandler.uc - 下载这个 Achievement(成就)示例的源代码。