开发者可以利用 Epic在线服务 (EOS)的 玩家数据存储(PDS)接口 将加密、用户专属、游戏专属的数据保存到云服务器。用户可以在登录的任意设备上访问通过此接口保存的数据。玩家数据存储接口支持任意文件格式,常见用途为游戏存档和回放数据。
通过平台接口函数 EOS_Platform_GetPlayerDataStorageInterface
获取一个 EOS_HPlayerDataStorage
句柄,即可访问玩家数据存储接口。所有玩家数据存储接口函数均需将此句柄作为首个参数。必须确保 EOS_HPlatform
句柄可以更新(tick),以便在请求完成时触发回调。
要使用此接口,必须用以下额外参数对EOS平台进行初始化:
-
const char* EncryptionKey
:这个64个字符的十六进制字符串将编译一个256位密钥,EOS将使用此密钥来加密用户数据。Epic的后端服务器不保存此密钥。如不存在密钥,调用EOS_Platform_GetPlayerDataStorageInterface
会返回一个空引用。如果密钥长度或格式不对,从玩家数据存储接口调用文件管理函数将引起失败,并返回EOS_PlayerDataStorage_EncryptionKeyNotSet
错误代码。 -
const char* CacheDirectory
:这是接口在数据文件传输时用于临时存储的本地目录的完整路径。这可以是任何路径,通常为系统的临时目录或游戏数据的目录。如指定的目录不存在,EOS将尝试创建目录。多个用户和产品可以使用相同目录,EOS会确保其不会冲突。如果目录存在问题,调用文件管理函数将引起失败,并返回EOS_CacheDirectoryMissing
或EOS_CacheDirectoryInvalid
错误代码。请参阅EOS平台文档了解各个平台在文件夹的使用上有哪些限制。
文件管理
文件名格式
EOS中的文件名称采用以下格式:Directory0/Directory1/DirectoryN/Filename.Extension
“Directory”和“Extension”部分是选项项。“/”必须在每个目录名称后出现,但不能出现在其他位置,包括作为开头或末尾的字符。文件名中可以出现以下字符:
- 所有数字类型的ASCII字符
!
-
_
+
.
'
(
)
文件名不能超过 EOS_PLAYERDATASTORAGE_FILENAME_MAX_LENGTH_BYTES
(64)个字符。
查询文件
你可以通过查询EOS来获取保存在云端的一个或所有文件的信息。任意查询类型都将返回关于文件或其找到文件的元数据,包括文件的名称、数据大小(以字节为单位、未加密)及MD5散列。在访问或修改文件之前,你可以缓存此信息的副本。注意,由于文件以加密形式保存在后端,因此会为加密文件提供一些元数据(例如文件大小和哈希)。元数据不会完全匹配你传递给API的文件的对应值。
请务必在查询的回调函数中从缓存中复制可能需要的任何信息。无法保证此数据的生存周期会超过回调函数的持续时间。这点在运行多个查询时尤其重要,因为每次成功的查询都可能更改缓存的内容。
查询所有文件
要获取所有文件的相关信息,以 EOS_PlayerDataStorage_QueryFileListOptions
数据结构调用 EOS_PlayerDataStorage_QueryFileList
。此数据结构初始化如下:
属性 | 值 |
---|---|
ApiVersion | EOS_PLAYERDATASTORAGE_QUERYFILELISTOPTIONS_API_LATEST |
LocalUserId | 请求文件数据的本地用户的 EOS_ProductUserId |
完成时,EOS将用一个 EOS_PlayerDataStorage_QueryFileListCallbackInfo
数据接口运行你的回调函数(类型为 EOS_PlayerDataStorage_OnQueryFileListCompleteCallback
)。如调用成功,数据结构将包含找到文件的数量,以及EOS缓存中可用文件的相关信息。
查询单个文件
如果只需要单个文件的信息,且了解此文件的名称,可以调用 EOS_PlayerDataStorage_QueryFile
函数,传入一个初始化如下的 EOS_PlayerDataStorage_QueryFileOptions
结构:
属性 | 值 |
---|---|
ApiVersion | EOS_PLAYERDATASTORAGE_QUERYFILEOPTIONS_API_LATEST |
LocalUserId | 请求文件数据的本地用户的 EOS_ProductUserId |
Filename | 正在查询文件的文件名 |
操作完成时,EOS将用一个 EOS_PlayerDataStorage_QueryFileCallbackInfo
结构运行你的回调(类型为 EOS_PlayerDataStorage_OnQueryFileCompleteCallback
)。如调用成功,EOS将在缓存中保存你文件的相关数据。
检查缓存文件信息
EOS获取并缓存一个或多个文件的信息之后,可以用文件名调用 EOS_PlayerDataStorage_CopyFileMetadataByFilename
、或以缓存中文件的零基索引来调用 EOS_PlayerDataStorage_CopyFileMetadataAtIndex
,以获取特定文件上的信息。如果需要了解缓存中的文件数量,可以调用 EOS_PlayerDataStorage_GetFileMetadataCount
。不再需要此信息的副本之后,可调用 EOS_PlayerDataStorage_FileMetadata_Release
来释放其使用的内存。
查询EOS关于单个文件的信息后,EOS_PlayerDataStorage_GetFileMetadataCount
可能显示出缓存保存着多个文件。之前查询的结果仍留在缓存中时便可能出现此结果。在此类情况中,最佳方法是检查 EOS_PlayerDataStorage_QueryFileCallbackInfo
中的 ResultCode
为 EOS_Success
,并调用 EOS_PlayerDataStorage_CopyFileMetadataByFilename
访问其信息。
文件的元数据结构如下所示:
属性 | 值 |
---|---|
ApiVersion | EOS_PLAYERDATASTORAGE_FILEMETADATA_API_LATEST |
FileSizeBytes | 文件的总大小,以字节为单位(采用加密形式,可以稍大于预期) |
MD5Hash | 整个文件的MD5哈希,采用十六进制(加密文件的哈希) |
Filename | 文件名称 |
LastModifiedTime | 文件最后一次保存时的日期和时间。时间戳使用POSIX/Unix 格式。 |
UnencryptedDataSizeBytes | 数据(有效荷载)的总大小,以字节计算(未加密原始形态)。 |
访问并修改文件
EOS支持从云端异步读取、写入并删除文件。读取和写入为流送操作,EOS将提供柄,游戏可将其用于检查流送进程,或者进行节流,或者干预。
读取文件
要从云端(或本地缓存)调用已知名称的文件,调用 EOS_PlayerDataStorage_ReadFile
。需要传入一个 EOS_PlayerDataStorage_ReadFileOptions
,其初始化如下:
属性 | 值 |
---|---|
ApiVersion | EOS_PLAYERDATASTORAGE_READFILEOPTIONS_API_LATEST |
LocalUserId | 读取请求文件的本地用户的帐户ID |
Filename | 要读取的文件的名称 |
ReadChunkLengthBytes | 在单次 EOS_PlayerDataStorage_OnReadFileDataCallback 调用中药读取的最大数据量 |
ReadFileDataCallback | 在数据进入时对其进行处理、可提早停止传输的回调函数 |
FileTransferProgressCallback | 告知传输进度的可选回调函数;设置后将被调用至少一次 |
此函数返回一个 EOS_HPlayerDataStorageFileTransferRequest
,其可用于检查传输的进度、获取文件名、或取消传输。在传输期间,调用 EOS_PlayerDataStorageFileTransferRequest_GetFileRequestState
可轮询其当前状态、调用 EOS_PlayerDataStorageFileTransferRequest_GetFilename
可检查正在传输文件的命名、调用 EOS_PlayerDataStorageFileTransferRequest_CancelRequest
可将其取消(不会产生错误)。如果希望从EOS获得更新,而不是直接轮询传输,可在开始传输时提供一个有效的 EOS_PlayerDataStorage_OnFileTransferProgressCallback
函数(和 FileTransferProgressCallback
参数一样),EOS将定期对其进行调用(利用一个 EOS_PlayerDataStorage_FileTransferProgressCallbackInfo
参数),告知文件传输的进度。如果选择此方法,你必将接收到对回调函数的至少一个调用。使用回调函数并不会妨碍柄的使用,但这二者作用相差无几,多数情况下都不需要同时使用。
文件块进入时,你的 ReadFileDataCallback
(类型为 EOS_PlayerDataStorage_OnReadFileDataCallback
)将随类型为 EOS_PlayerDataStorage_ReadFileDataCallbackInfo
的参数运行。此结构包含来自文件的实际数据的一个块和一个变量(说明这是否为最后一个文件块)。其还拥有一个枚举返回类型 EOS_PlayerDataStorage_EReadResult
,可用来告知EOS继续传输、由于出错而终止、或是取消而不报错。
ReadFileDataCallback
只会从主SDK线程(用于执行SDK每帧更新逻辑的线程)调用,而且每帧只调用一次。这就是为何必须为 ReadChunkLengthBytes
设置正确的数值。如果把区块大小设置得过低,则会降低用户的读取效率。我们推荐使用4096的倍数,因为这通常符合系统的内存分页大小。你可以通过将 ReadChunkLengthBytes
与SDK的更新频率相乘,估计出最大读取速度。例如,4096 * 30相当于每秒120千字节的读取速度。另一方面,如果使用过大的区块大小,将导致SDK在内部分配更多内存。为了留下合理的数据足迹,你应该选择一个最符合你需要的数值。
文件传输结束后,EOS将以一个 EOS_PlayerDataStorage_ReadFileCallbackInfo
参数运行 EOS_PlayerDataStorage_OnReadFileCompleteCallback
回调函数。此参数将说明成功或失败,并包含文件名。
EOS可以将文件缓存到本地,加快之后的读取操作。这些文件将使用EOS平台初始化期间提供的密钥进行加密。发送到 ReadFileDataCallback
函数的数据块不会被加密。
以下时间线图表展示了假想的读取操作流程:
写入文件
要写入已知命名的文件,调用 EOS_PlayerDataStorage_WriteFile
。需要传入一个 EOS_PlayerDataStorage_WriteFileOptions
,其初始化如下:
属性 | 值 |
---|---|
ApiVersion | EOS_PLAYERDATASTORAGE_WRITEFILEOPTIONS_API_LATEST |
LocalUserId | 将文件写出至云端的本地用户的账户ID |
Filename | 要写入的文件的名称 |
ChunkLengthBytes | 单次tick中写入文件的最大数据量 |
WriteFileDataCallback | 将数据块提供到EOS的回调函数 |
FileTransferProgressCallback | 告知传输进度的可选回调函数(类型为 EOS_PlayerDataStorage_OnFileTransferProgressCallback );设置后将被调用至少一次 |
此函数返回一个 EOS_HPlayerDataStorageFileTransferRequest
,其可用于检查传输的进度、获取文件名、或取消传输。在传输期间,调用 EOS_PlayerDataStorageFileTransferRequest_GetFileRequestState
可轮询其当前状态、调用 EOS_PlayerDataStorageFileTransferRequest_GetFilename
可检查正在传输文件的命名、调用 EOS_PlayerDataStorageFileTransferRequest_CancelRequest
可将其取消(不会产生错误)。如果希望从EOS获得更新,而不是直接轮询传输,可在开始传输时提供一个有效的 EOS_PlayerDataStorage_OnFileTransferProgressCallback
函数(和 FileTransferProgressCallback
参数一样),EOS将定期对其进行调用(利用一个 EOS_PlayerDataStorage_FileTransferProgressCallbackInfo
参数),告知文件传输的进度。如果选择此方法,你必将接收到对回调函数的至少一个调用。使用回调函数并不会妨碍柄的使用,但这二者作用相差无几,多数情况下都不需要同时使用。
EOS要将文件的下个块发送至云端时,你的 WriteFileDataCallback
(类型为 EOS_PlayerDataStorage_OnWriteFileDataCallback
)将以一个类型为 EOS_PlayerDataStorage_WriteFileDataCallbackInfo
的参数运行,一个 空
指针指向外出数据的缓冲,一个 uint32_t
指针将说明数据的大小。EOS_PlayerDataStorage_WriteFileDataCallbackInfo
拥有一个名为 DataBufferLengthBytes
的变量,其说明可放入缓冲的最大字节数。此回调函数拥有一个枚举返回类型 EOS_PlayerDataStorage_EWriteResult
,可用来告知EOS继续传输、由于出错而终止、或是取消而不报错。
WriteFileDataCallback
只会从主SDK线程(用于执行SDK每帧更新逻辑的线程)调用,而且每帧只调用一次。这就是为何必须为 ReadChunkLengthBytes
设置正确的数值。如果把区块大小设置得过低,则会降低用户的读取效率。我们推荐使用4096的倍数,因为这通常符合系统的内存分页大小。你可以通过将 ReadChunkLengthBytes
与SDK的更新频率相乘,估计出最大读取速度。例如,4096 * 30相当于每秒120千字节的读取速度。另一方面,如果使用过大的区块大小,将导致SDK在内部分配更多内存。为了留下合理的数据足迹,你应该选择一个最符合你需要的数值。
文件传输结束后,EOS将以一个 EOS_PlayerDataStorage_WriteFileCallbackInfo
参数运行 EOS_PlayerDataStorage_OnWriteFileCompleteCallback
回调函数。此参数将说明成功或失败,并包含文件名。为避免数据丢失,必须运行此回调,才能允许用户退出游戏或关闭主机。
复制文件
如果要复制文件,无需手动读写文件的完整数据内容即可进行复制。用包含以下值的 EOS_PlayerDataStorage_DuplicateFileOptions
调用 EOS_PlayerDataStorage_DuplicateFile
:
属性 | 值 |
---|---|
ApiVersion | EOS_PLAYERDATASTORAGE_DUPLICATEFILEOPTIONS_API_LATEST |
LocalUserId | 授权操作的本地用户的账户ID;必须为原始文件的拥有者 |
SourceFilename | 要复制的现有文件的名称 |
DestinationFilename | 复制文件所需的名称 |
完成时,EOS将以一个 EOS_PlayerDataStorage_DuplicateFileCallbackInfo
参数运行 EOS_PlayerDataStorage_OnDuplicateFileCompleteCallback
函数。如用户未拥有原始文件、或存储空间不足,则文件复制将失败。如 DestinationFilename
识别到已存在的文件,则文件将被副本所替代。
- 原有文件不存在。
- 用户不持有原有文件。
- 用户储存空间不够。
如果 DestinationFilename
标识了一个已经存在的文件,则该文件会被副本替换。
删除文件
用一个初始化如下的 EOS_PlayerDataStorage_DeleteFileOptions
调用 EOS_PlayerDataStorage_DeleteFileOptions
即可删除文件:
属性 | 值 |
---|---|
ApiVersion | EOS_PLAYERDATASTORAGE_DELETEFILEOPTIONS_API_LATEST |
LocalUserId | 授权文件删除的本地用户的账户ID;必须为文件的拥有者 |
Filename | 要删除的文件的名称 |
完成时,EOS将运行回调(类型为 EOS_PlayerDataStorage_OnDeleteFileCompleteCallback
)并为其传递一个类型为 EOS_PlayerDataStorage_DeleteFileCallbackInfo
的参数。如用户并非文件的拥有者,文件删除操作可能失败。
数据缓存与加密
玩家数据存储接口会将文件数据和元数据缓存到客户端系统上的CacheDirectory文件夹中。EOS在进行读写操作时将尽量使用此数据,而非从云端进行流送,并使用MD5校验和测试来防止数据损坏,并确定本地缓存文件何时与云端版本完全一致。在此情况下读写操作可在单次回调循环中完成。任何成功的读写操作都将更新缓存,而删除则会清除缓存文件并移除其元数据。EOS在关闭时不会清理缓存,所以可以在之后的会话中重复使用缓存文件。
玩家数据储存文件会固定加密保存,使用EOS平台初始化时提供的密钥。加密密钥本身未保存在云端,因此我们无法使用密钥来访问你的数据。
文件解密工具
你可以使用文件解密工具(File Decryption Tool)来创建新文件或解密作品存储或 玩家数据存储 中的数据。
用法限制
玩家数据存储接口可以让使用EOS的开发者将加密的、用户特定的、游戏特定的数据保存到云服务器上。用户可以在任何可以登录的设备上访问通过该接口存储的数据。玩家数据存储接口支持任何文件格式;常用案例包括保存游戏数据和重播数据。
该服务对存储数量和使用率都有限制。节流限制(Throttling)会强制执行使用率限制,而服务可以删除文件以便强制执行存储空间限制。有关节流限制、使用配额和最佳实践的信息,请参阅服务使用限制。
| 使用类型|限制 | | --- | --- | | 读取或写入(Read or write)|每分钟1000次请求| | 单个文件的最大尺寸(见下文说明) | 200 MB | | 自动删除的文件大小(Auto-delete file size)|400 MB| | 单个用户的总存储空间(Total storage space per user)|400 MB | | 每个用户的最大文件数(Maximum files per user) | 1000 |
如果用户上传的内容超过任意一个存储空间限制,EOS_PlayerDataStorage_WriteFile
调用会失败,并返回 EOS_Result_PlayerDataStorageFileSizeTooLarge
。当用户存储空间满了后,再调用 EOS_PlayerDataStorage_WriteFile
就会失败,并返回 EOS_Result_PlayerDataStorageUserThrottled
,指示用户存储遇到了瓶颈。用户的存储空间一直会处于限制状态,直到删除足够多的文件,使得存储空间的使用量低于限制。所有超过一定大小的文件(由自动删除标准规定),会在文件上传后被删除。
玩家数据存储 vs EGS 云存储
Epic提供两种不同的云存储服务:启动器云存储和EOS玩家数据存储。前者使用范围最广,可以在Epic游戏商城中作为游戏配置的一部分进行设置。不过,这两种服务之间存在一些差异,而这会对使用哪种服务产生影响。
- 启动器云存储在Epic Games启动器中与游戏关联。所以假如玩家在启动器之外启动游戏,游戏就只能访问本地版本的存储数据。
- 启动器的云存储在配置完毕后完全由Epic负责处理
- EOS玩家数据存储要求玩家必须在线,并且完全由游戏端通过EOS接口来驱动。这就使得数据能在不同平台上共享,这一点是它和启动器云存储的区别。