实体快速入门
本实体快速入门演示如何使用实体对象和实体文件。
有关从旧版帐户和数据系统迁移到 PlayFab 实体的信息,请参阅 实体迁移信息
要求
- 一个 PlayFab 开发者帐户。
- Unity 编辑器的已安装副本。 有关安装 Unity 编辑器的信息,请参阅 Unity 文档中的 安装 Unity。 还可以使用 Visual Studio 功能安装程序安装某个版本的 Unity。
注意
PlayFab Unity3D SDK 支持 Unity 编辑器版本 5.3 及更高版本。
- 一个 Unity 项目。 有关创建 Unity 项目的信息,请参阅 快速入门指南。
注意
如果不熟悉 Unity,最近的安装包可让你选择安装游戏创建演练。 可以使用其中一个演练创建要在以下快速入门中使用的示例游戏。
- PlayFab Unity3D SDK。
本文中的 C# 示例是为 Unity SDK 编写的。 Unity SDK 使用事件驱动模型来处理非同步任务。 若要使用标准 C# 或 Xamarin C# SDK 运行示例代码,必须修改代码以使用异步任务模型。 必须修改的方法在签名中将异步附加到方法名称。 例如,Unity SDK 中的 SetObject 在标准 C# SDK 中变为 SetObjectAsync。 有关详细信息,请参阅 使用异步和等待进行异步编程。
术语
实体是可以包含数据的任何 PlayFab 概念。 内置实体类型为:
-
游戏 - 游戏包含可供所有玩家使用的全局信息。 这类似于 TitleData。 它由游戏/应用程序的游戏 ID (
TitleId
) 进行标识。 -
master_player_account - 通过此实体类型,可以在命名空间中的多个游戏间共享有关玩家的信息。 它由玩家 ID (
PlayFabId
) 进行标识,该 ID 作为任何登录或调用的一部分返回,用于对玩家帐户检索帐户信息(例如,PlayFab 客户端 API GetAccountInfo)。 -
title_player_account - 标识包含当前游戏的某些信息在内的玩家帐户。 这由任何登录上的 EntityKey 对象中返回的实体 ID(
EntityKey.Id
)进行标识。 -
字符 - 标识玩家拥有的包含可检索信息的字符。 它由角色 ID (
CharacterId
) 进行标识。
有关内置实体类型的详细信息,请参阅 可用的内置实体类型。
实体初始化
要调用任何实体 API,必须获取实体 ID
和实体 Type
。 使用 ID
和 Type
调用其他实体 API 方法。 这些是 EntityKey 对象的成员。
要执行此操作,请调用任何登录方法,如 LoginWithCustomID
。
void Login()
{
var request = new PlayFab.ClientModels.LoginWithCustomIDRequest
{
CustomId = SystemInfo.deviceUniqueIdentifier,
CreateAccount = true,
};
PlayFabClientAPI.LoginWithCustomID(request, OnLogin, OnSharedFailure);
}
void OnLogin(PlayFab.ClientModels.LoginResult result)
{
entityId = result.EntityToken.Entity.Id;
// The expected entity type is title_player_account.
entityType = result.EntityToken.Entity.Type;
}
也可以通过调用 GetEntityToken 方法来获取实体 ID
和实体 Type
。
PlayFabAuthenticationAPI.GetEntityToken(new GetEntityTokenRequest(),
(entityResult) =>
{
var entityId = entityResult.Entity.Id;
var entityType = entityResult.Entity.Type;
}, OnPlayFabError); // Define your own OnPlayFabError function to report errors
如果从客户端进行调用,则这通常代表已登录玩家。 从游戏服务器调用时,这代表你的游戏。
实体对象
通过实体对象,可以读取和写入附加到实体的小型 JSON 序列化对象。 所有实体类型都支持 GetObjects
和 SetObjects
方法。
以下代码片段演示如何设置和读取 Object
和 title_player_account
实体。
使用 SetObjects 方法设置玩家或游戏上的实体对象。
var data = new Dictionary<string, object>()
{
{"Health", 100},
{"Mana", 10000}
};
var dataList = new List<SetObject>()
{
new SetObject()
{
ObjectName = "PlayerData",
DataObject = data
},
// A free-tier customer may store up to 3 objects on each entity
};
PlayFabDataAPI.SetObjects(new SetObjectsRequest()
{
Entity = new EntityKey {Id = entityId, Type = entityType}, // Saved from GetEntityToken, or a specified key created from a titlePlayerId, CharacterId, etc
Objects = dataList,
}, (setResult) => {
Debug.Log(setResult.ProfileVersion);
}, OnPlayFabError);
使用 GetObjects 方法检索玩家或游戏上的实体对象。
var getRequest = new GetObjectsRequest {Entity = new EntityKey {Id = entityId, Type = entityType}};
PlayFabDataAPI.GetObjects(getRequest,
result => { var objs = result.Objects; },
OnPlayFabError
);
实体文件
通过实体文件,可以使用读取和写入任意格式的附加到实体的文件。
使用 GetFiles 方法检索实体文件。
void LoadAllFiles()
{
if (GlobalFileLock != 0)
throw new Exception("This example overly restricts file operations for safety. Careful consideration must be made when doing multiple file operations in parallel to avoid conflict.");
GlobalFileLock += 1; // Start GetFiles
var request = new PlayFab.DataModels.GetFilesRequest { Entity = new PlayFab.DataModels.EntityKey { Id = entityId, Type = entityType } };
PlayFabDataAPI.GetFiles(request, OnGetFileMeta, OnSharedFailure);
}
使用 initiatefileuploads 方法启动到实体配置文件的文件上传。
void UploadFile(string fileName)
{
if (GlobalFileLock != 0)
throw new Exception("This example overly restricts file operations for safety. Careful consideration must be made when doing multiple file operations in parallel to avoid conflict.");
ActiveUploadFileName = fileName;
GlobalFileLock += 1; // Start InitiateFileUploads
var request = new PlayFab.DataModels.InitiateFileUploadsRequest
{
Entity = new PlayFab.DataModels.EntityKey { Id = entityId, Type = entityType },
FileNames = new List<string> { ActiveUploadFileName },
};
PlayFabDataAPI.InitiateFileUploads(request, OnInitFileUpload, OnInitFailed);
}
使用 AbortFileUploads 方法中止到实体配置文件的挂起的文件上传。
void OnInitFailed(PlayFabError error)
{
if (error.Error == PlayFabErrorCode.EntityFileOperationPending)
{
// This is an error you should handle when calling InitiateFileUploads, but your resolution path may vary
GlobalFileLock += 1; // Start AbortFileUploads
var request = new PlayFab.DataModels.AbortFileUploadsRequest
{
Entity = new PlayFab.DataModels.EntityKey { Id = entityId, Type = entityType },
FileNames = new List<string> { ActiveUploadFileName },
};
PlayFabDataAPI.AbortFileUploads(request, (result) => { GlobalFileLock -= 1; UploadFile(ActiveUploadFileName); }, OnSharedFailure); GlobalFileLock -= 1; // Finish AbortFileUploads
GlobalFileLock -= 1; // Failed InitiateFileUploads
}
else
OnSharedFailure(error);
}
使用 FinalizeFileUploads 方法完成到实体配置文件的文件上传。 在上传操作成功完成之前,实体系统不会认为文件上传完成,也不会向其他调用方反映任何更改。
void FinalizeUpload(byte[] data)
{
GlobalFileLock += 1; // Start FinalizeFileUploads
var request = new PlayFab.DataModels.FinalizeFileUploadsRequest
{
Entity = new PlayFab.DataModels.EntityKey { Id = entityId, Type = entityType },
FileNames = new List<string> { ActiveUploadFileName },
};
PlayFabDataAPI.FinalizeFileUploads(request, OnUploadSuccess, OnSharedFailure);
GlobalFileLock -= 1; // Finish SimplePutCall
}
下面的示例展示了一个从登录到加载文件和上传新文件的完整实体文件循环。
步骤为:
- 登录并检索实体
ID
和实体Type
。 - 初始化原子上传操作。
- 上传所有文件。
- 完成原子上传操作。
为简单起见,此示例一次保存一个文件,但可以原子方式成批上传文件。
该示例假定你熟悉 Unity 3D 引擎的使用。
#if !DISABLE_PLAYFABENTITY_API && !DISABLE_PLAYFABCLIENT_API
using PlayFab;
using PlayFab.Internal;
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
public class EntityFileExample : MonoBehaviour
{
public string entityId; // Id representing the logged in player
public string entityType; // entityType representing the logged in player
private readonly Dictionary<string, string> _entityFileJson = new Dictionary<string, string>();
private readonly Dictionary<string, string> _tempUpdates = new Dictionary<string, string>();
public string ActiveUploadFileName;
public string NewFileName;
// GlobalFileLock provides is a simplistic way to avoid file collisions, specifically designed for this example.
public int GlobalFileLock = 0;
void OnSharedFailure(PlayFabError error)
{
Debug.LogError(error.GenerateErrorReport());
GlobalFileLock -= 1;
}
// OnGUI provides a way to build a Unity GUI entirely within script.
- // Your GUI will be game-specific.
void OnGUI()
{
if (!PlayFabClientAPI.IsClientLoggedIn() && GUI.Button(new Rect(0, 0, 100, 30), "Login"))
Login();
if (PlayFabClientAPI.IsClientLoggedIn() && GUI.Button(new Rect(0, 0, 100, 30), "LogOut"))
PlayFabClientAPI.ForgetAllCredentials();
if (PlayFabClientAPI.IsClientLoggedIn() && GUI.Button(new Rect(100, 0, 100, 30), "(re)Load Files"))
LoadAllFiles();
if (PlayFabClientAPI.IsClientLoggedIn())
{
// Display existing files
_tempUpdates.Clear();
var index = 0;
foreach (var each in _entityFileJson)
{
GUI.Label(new Rect(100 * index, 60, 100, 30), each.Key);
var tempInput = _entityFileJson[each.Key];
var tempOutput = GUI.TextField(new Rect(100 * index, 90, 100, 30), tempInput);
if (tempInput != tempOutput)
_tempUpdates[each.Key] = tempOutput;
if (GUI.Button(new Rect(100 * index, 120, 100, 30), "Save " + each.Key))
UploadFile(each.Key);
index++;
}
// Apply any changes
foreach (var each in _tempUpdates)
_entityFileJson[each.Key] = each.Value;
// Add a new file
NewFileName = GUI.TextField(new Rect(100 * index, 60, 100, 30), NewFileName);
if (GUI.Button(new Rect(100 * index, 90, 100, 60), "Create " + NewFileName))
UploadFile(NewFileName);
}
}
void Login()
{
var request = new PlayFab.ClientModels.LoginWithCustomIDRequest
{
CustomId = SystemInfo.deviceUniqueIdentifier,
CreateAccount = true,
};
PlayFabClientAPI.LoginWithCustomID(request, OnLogin, OnSharedFailure);
}
void OnLogin(PlayFab.ClientModels.LoginResult result)
{
entityId = result.EntityToken.Entity.Id;
entityType = result.EntityToken.Entity.Type;
}
void LoadAllFiles()
{
if (GlobalFileLock != 0)
throw new Exception("This example overly restricts file operations for safety. Careful consideration must be made when doing multiple file operations in parallel to avoid conflict.");
GlobalFileLock += 1; // Start GetFiles
var request = new PlayFab.DataModels.GetFilesRequest { Entity = new PlayFab.DataModels.EntityKey { Id = entityId, Type = entityType } };
PlayFabDataAPI.GetFiles(request, OnGetFileMeta, OnSharedFailure);
}
void OnGetFileMeta(PlayFab.DataModels.GetFilesResponse result)
{
Debug.Log("Loading " + result.Metadata.Count + " files");
_entityFileJson.Clear();
foreach (var eachFilePair in result.Metadata)
{
_entityFileJson.Add(eachFilePair.Key, null);
GetActualFile(eachFilePair.Value);
}
GlobalFileLock -= 1; // Finish GetFiles
}
void GetActualFile(PlayFab.DataModels.GetFileMetadata fileData)
{
GlobalFileLock += 1; // Start Each SimpleGetCall
PlayFabHttp.SimpleGetCall(fileData.DownloadUrl,
result => { _entityFileJson[fileData.FileName] = Encoding.UTF8.GetString(result); GlobalFileLock -= 1; }, // Finish Each SimpleGetCall
error => { Debug.Log(error); }
);
}
void UploadFile(string fileName)
{
if (GlobalFileLock != 0)
throw new Exception("This example overly restricts file operations for safety. Careful consideration must be made when doing multiple file operations in parallel to avoid conflict.");
ActiveUploadFileName = fileName;
GlobalFileLock += 1; // Start InitiateFileUploads
var request = new PlayFab.DataModels.InitiateFileUploadsRequest
{
Entity = new PlayFab.DataModels.EntityKey { Id = entityId, Type = entityType },
FileNames = new List<string> { ActiveUploadFileName },
};
PlayFabDataAPI.InitiateFileUploads(request, OnInitFileUpload, OnInitFailed);
}
void OnInitFailed(PlayFabError error)
{
if (error.Error == PlayFabErrorCode.EntityFileOperationPending)
{
// This is an error you should handle when calling InitiateFileUploads, but your resolution path may vary
GlobalFileLock += 1; // Start AbortFileUploads
var request = new PlayFab.DataModels.AbortFileUploadsRequest
{
Entity = new PlayFab.DataModels.EntityKey { Id = entityId, Type = entityType },
FileNames = new List<string> { ActiveUploadFileName },
};
PlayFabDataAPI.AbortFileUploads(request, (result) => { GlobalFileLock -= 1; UploadFile(ActiveUploadFileName); }, OnSharedFailure); GlobalFileLock -= 1; // Finish AbortFileUploads
GlobalFileLock -= 1; // Failed InitiateFileUploads
}
else
OnSharedFailure(error);
}
void OnInitFileUpload(PlayFab.DataModels.InitiateFileUploadsResponse response)
{
string payloadStr;
if (!_entityFileJson.TryGetValue(ActiveUploadFileName, out payloadStr))
payloadStr = "{}";
var payload = Encoding.UTF8.GetBytes(payloadStr);
GlobalFileLock += 1; // Start SimplePutCall
PlayFabHttp.SimplePutCall(response.UploadDetails[0].UploadUrl,
payload,
FinalizeUpload,
error => { Debug.Log(error); }
);
GlobalFileLock -= 1; // Finish InitiateFileUploads
}
void FinalizeUpload(byte[] data)
{
GlobalFileLock += 1; // Start FinalizeFileUploads
var request = new PlayFab.DataModels.FinalizeFileUploadsRequest
{
Entity = new PlayFab.DataModels.EntityKey { Id = entityId, Type = entityType },
FileNames = new List<string> { ActiveUploadFileName },
};
PlayFabDataAPI.FinalizeFileUploads(request, OnUploadSuccess, OnSharedFailure);
GlobalFileLock -= 1; // Finish SimplePutCall
}
void OnUploadSuccess(PlayFab.DataModels.FinalizeFileUploadsResponse result)
{
Debug.Log("File upload success: " + ActiveUploadFileName);
GlobalFileLock -= 1; // Finish FinalizeFileUploads
}
}
#endif
注意
每个文件操作都需要许多步骤和多个 API 调用,因此请勿尝试以多种方式同时访问同一文件。 如果非常小心,则可能不需要锁定机制。 如果要执行一些复杂操作,则锁定机制可能非常复杂。
Game Manager 和实体
Game Manager 允许你操作玩家的对象和文件。 玩家概述展示了游戏玩家和主玩家帐户信息。
此外,现在文件和对象在 Players 选项卡中有自己的部分。
另请参阅
PlayFab 博客上的 实体、对象和文件介绍。