存储和检索玩家的特定数据

Epic Games技术客户经理Rajen Kishna |
2021年12月7日
在Epic在线服务入门系列的前一篇文章中,我们介绍了使用作品存储服务从云端检索游戏的特定数据。在那篇文章中,我们介绍了作品存储玩家数据存储之间的区别,那么在今天的文章中,我们将使用后者将玩家数据保存到云端,或从云端检索该数据。你必须知道,玩家数据存储服务是由玩家授权的,因此在可以通过篡改实现作弊的情况下(例如库存管理),不应该使用它。在本文中,我们将介绍:
 

对比玩家数据存储与Epic游戏商城云端存档

在深入介绍玩家数据存储之前,值得一提的是,Epic游戏商城(EGS)有一个名为云端存档的相关功能。EGS云端存档无需API实现,因为该功能是直接为Epic游戏商城配置的,通过Epic Games启动器管理。

虽然这两种实现方式都可以将玩家的存档数据存储在云端,但玩家数据存储服务更加灵活,因为它可用于存储或检索任何平台上的任何文件,不受游戏的发行商店影响。你可以使用玩家数据存储服务为所有玩家实现跨平台存档功能。你可以在这篇文档中进一步了解两者之间的差异。

更改我们的客户端策略

与所有服务一样,我们必须在客户端策略中添加适当的操作:
  1. 访问https://dev.epicgames.com/portal/登录开发者门户。
  2. 在左侧菜单中导航到你的产品,进入“产品设置”,在“产品设置”界面中点击“客户端”选项卡。
  3. 在你使用的客户端策略旁边点击三点图标,然后点击“详情”。
  4. 向下滚动至“功能”并点击“玩家数据存储”旁的开关按钮。
  5. 勾选“访问”、“删除”和“复制”操作旁的复选框。我们将实现所有这些功能。
  6. 点击“保存并退出”。
Developer Portal Client Policy Player Data Storage
玩家数据存储客户端策略允许的功能和操作

查询文件和获取文件元数据

如果你没有读过之前关于作品存储的文章,请通读“在SDK初始化中添加EncryptionKey和CacheDirectory”部分,了解文件的加密和缓存方式。了解有关内容后,我们首先来实现文件查询:
 
  1. 在Views文件夹中创建一个名为“PlayerDataStorageView”的新用户控件:

<Grid>
    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*" />
        <ColumnDefinition Width="Auto" />
    </Grid.ColumnDefinitions>

    <StackPanel Grid.Column="1">
        <Button Width="100" Height="23" Margin="2" Content="Query files" Command="{Binding PlayerDataStorageQueryFileList}" />
        <Button Width="100" Height="23" Margin="2" Content="Upload file" Command="{Binding PlayerDataStorageWriteFile}" />
        <StackPanel Orientation="Horizontal">
            <Button Width="100" Height="23" Margin="2" Content="Download file" Command="{Binding PlayerDataStorageReadFile}" />
            <Button Width="100" Height="23" Margin="2" Content="Duplicate file" Command="{Binding PlayerDataStorageDuplicateFile}" />
        </StackPanel>
        <Button Width="100" Height="23" Margin="2" Content="Delete file" Command="{Binding PlayerDataStorageDeleteFile}" />
    </StackPanel>

    <ListView x:Name="PlayerDataStorageFilesListView" Grid.Column="0" Margin="2" ItemsSource="{Binding PlayerDataStorageFiles}" SelectedItem="{Binding SelectedPlayerDataStorageFile}" SelectionChanged="PlayerDataStorageFilesListView_SelectionChanged">
        <ListView.View>
            <GridView>
                <GridViewColumn Header="Filename" Width="150" DisplayMemberBinding="{Binding Filename}">
                    <GridViewColumn.HeaderContainerStyle>
                        <Style TargetType="{x:Type GridViewColumnHeader}">
                            <Setter Property="HorizontalContentAlignment" Value="Left" />
                        </Style>
                    </GridViewColumn.HeaderContainerStyle>
                </GridViewColumn>
                <GridViewColumn Header="LastModifiedTime" Width="175" DisplayMemberBinding="{Binding LastModifiedTime}">
                    <GridViewColumn.HeaderContainerStyle>
                        <Style TargetType="{x:Type GridViewColumnHeader}">
                            <Setter Property="HorizontalContentAlignment" Value="Left" />
                        </Style>
                    </GridViewColumn.HeaderContainerStyle>
                </GridViewColumn>
                <GridViewColumn Header="FileSizeBytes" Width="75" DisplayMemberBinding="{Binding FileSizeBytes}">
                    <GridViewColumn.HeaderContainerStyle>
                        <Style TargetType="{x:Type GridViewColumnHeader}">
                            <Setter Property="HorizontalContentAlignment" Value="Left" />
                        </Style>
                    </GridViewColumn.HeaderContainerStyle>
                </GridViewColumn>
                <GridViewColumn Header="UnencryptedDataSizeBytes" Width="150" DisplayMemberBinding="{Binding UnencryptedDataSizeBytes}">
                    <GridViewColumn.HeaderContainerStyle>
                        <Style TargetType="{x:Type GridViewColumnHeader}">
                            <Setter Property="HorizontalContentAlignment" Value="Left" />
                        </Style>
                    </GridViewColumn.HeaderContainerStyle>
                </GridViewColumn>
            </GridView>
        </ListView.View>
    </ListView>
</Grid>

 
  • 现在我们有了一个简单的UI,它带有用于显示文件的ListView,以及用于查询、上传、下载、复制和删除文件的按钮。
 
  1. 打开PlayerDataStorageView.xaml.cs并设置DataContext和占位符事件句柄:

public partial class PlayerDataStorageView : UserControl
{
    public PlayerDataStorageViewModel ViewModel { get { return ViewModelLocator.PlayerDataStorage; } }

    public PlayerDataStorageView()
    {
        InitializeComponent();
        DataContext = ViewModel;
    }

    private void PlayerDataStorageFilesListView_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
    }
}

 
  1. 在ViewModels文件夹中添加PlayerDataStorageViewModel.cs类:

public class PlayerDataStorageViewModel : BindableBase
{
    private ObservableCollection<FileMetadata> _playerDataStorageFiles;
    public ObservableCollection<FileMetadata> PlayerDataStorageFiles
    {
        get { return _playerDataStorageFiles; }
        set { SetProperty(ref _playerDataStorageFiles, value); }
    }

    private FileMetadata _selectedPlayerDataStorageFile;
    public FileMetadata SelectedPlayerDataStorageFile
    {
        get { return _selectedPlayerDataStorageFile; }
        set { SetProperty(ref _selectedPlayerDataStorageFile, value); }
    }
}

 
  1. 在ViewModelLocator.cs中添加对PlayerDataStorageViewModel的引用:

private static PlayerDataStorageViewModel _playerDataStorage;
public static PlayerDataStorageViewModel PlayerDataStorage
{
    get { return _playerDataStorage ??= new PlayerDataStorageViewModel(); }
}

 
  1. 在Services文件夹中添加PlayerDataStorageService.cs类来存储查询逻辑:

public static class PlayerDataStorageService
{
    public static void QueryFileList()
    {
        var queryFileListOptions = new QueryFileListOptions()
        {
            LocalUserId = ProductUserId.FromString(ViewModelLocator.Main.ProductUserId)
        };

        ViewModelLocator.Main.StatusBarText = "Querying player data storage file list...";

        App.Settings.PlatformInterface.GetPlayerDataStorageInterface()
.QueryFileList(queryFileListOptions, null, (QueryFileListCallbackInfo queryFileListCallbackInfo) =>
        {
            Debug.WriteLine($"QueryFileList {queryFileListCallbackInfo.ResultCode}");

            if (queryFileListCallbackInfo.ResultCode == Result.Success)
            {
                for (uint i = 0; i < queryFileListCallbackInfo.FileCount; i++)
                {
                    var copyFileMetadataAtIndexOptions = new CopyFileMetadataAtIndexOptions()
                    {
                        Index = i,
                        LocalUserId = ProductUserId.FromString(ViewModelLocator.Main.ProductUserId)
                    };
                    var result = App.Settings.PlatformInterface.GetPlayerDataStorageInterface()
.CopyFileMetadataAtIndex(copyFileMetadataAtIndexOptions, out var metadata);

                    if (result == Result.Success)
                    {
                        ViewModelLocator.PlayerDataStorage.PlayerDataStorageFiles
.Add(metadata);
                    }
                }
            }

            ViewModelLocator.Main.StatusBarText = string.Empty;
        });
    }
}

   
  1. 在Commands文件夹中添加PlayerDataStorageQueryFileListCommand.cs类:

public class PlayerDataStorageQueryFileListCommand : CommandBase
{
    public override bool CanExecute(object parameter)
    {
        return !string.IsNullOrWhiteSpace(ViewModelLocator.Main.ProductUserId);
    }

    public override void Execute(object parameter)
    {
        ViewModelLocator.PlayerDataStorage.PlayerDataStorageFiles = new ObservableCollection<FileMetadata>();
        PlayerDataStorageService.QueryFileList();
    }
}

 
  1. 打开PlayerDataStorageViewModel.cs,声明并实例化命令:

public PlayerDataStorageQueryFileListCommand PlayerDataStorageQueryFileList { get; set; }

public PlayerDataStorageViewModel()
{
    PlayerDataStorageQueryFileList = new PlayerDataStorageQueryFileListCommand();
}

 
  1. 在ViewModelLocator.cs中向RaiseConnectCanExecuteChanged()方法添加以下代码行,确保我们只有在通过连接接口成功登录后才能进行查询:

PlayerDataStorage.PlayerDataStorageQueryFileList.RaiseCanExecuteChanged();
 
  1. 最后,将PlayerDataStorageView添加到MainWindow.xaml中的TabControl:

<TabItem x:Name="PlayerDataStorage" Header="Player Data Storage">
    <views:PlayerDataStorageView />
</TabItem>


现在,如果我们运行应用程序,并在验证身份后查询玩家数据存储文件,会在日志中看到以下内容:

[Warning] LogEOSPlayerDataStorage - Querying file failed, got 0 results.
QueryFileList NotFound


这是意料之中的,因为我们尚未上传任何文件,我们接下来就上传文件。

上传文件

你的文件上传逻辑可能会有差异,但下面的内容应该能让你大致了解如何实现这个API。
 
  1. 在PlayerDataStorageService.cs中添加以下方法:

public static void WriteFile(OpenFileDialog openFileDialog)
{
    var bytesWritten = 0;

    var writeFileOptions = new WriteFileOptions()
    {
        LocalUserId = ProductUserId.FromString(ViewModelLocator.Main.ProductUserId),
        Filename = openFileDialog.SafeFileName,
        ChunkLengthBytes = 10485760,
        WriteFileDataCallback = (WriteFileDataCallbackInfo writeFileDataCallbackInfo, out byte[] buffer) =>
        {
            using var fs = new FileStream($"{openFileDialog.FileName}", FileMode.Open, FileAccess.Read);
            if (fs.Length > bytesWritten)
            {
                var readBytes = new byte[System.Math.Min(writeFileDataCallbackInfo.DataBufferLengthBytes, fs.Length)];
                fs.Seek(bytesWritten, SeekOrigin.Begin);
                bytesWritten += fs.Read(readBytes, 0, (int)System.Math.Min(writeFileDataCallbackInfo
.DataBufferLengthBytes, fs.Length));
                buffer = readBytes;
            }
            else
            {
                buffer = new byte[0];
                return WriteResult.CompleteRequest;
            }
            return WriteResult.ContinueWriting;
        },
        FileTransferProgressCallback = (FileTransferProgressCallbackInfo fileTransferProgressCallbackInfo) =>
        {
            var percentComplete = (double)fileTransferProgressCallbackInfo.BytesTransferred / (double)fileTransferProgressCallbackInfo.TotalFileSizeBytes * 100;
            ViewModelLocator.Main.StatusBarText = $"Downloading file <{fileTransferProgressCallbackInfo.Filename}> ({System.Math.Ceiling(percentComplete)}%)...";
        }
    };

    ViewModelLocator.Main.StatusBarText = $"Uploading file <{writeFileOptions.Filename}> (creating buffer)...";

    var fileTransferRequest = App.Settings.PlatformInterface
.GetPlayerDataStorageInterface().WriteFile(writeFileOptions, null, (WriteFileCallbackInfo writeFileCallbackInfo) =>
    {
        Debug.WriteLine($"WriteFile {writeFileCallbackInfo.ResultCode}");

        if (writeFileCallbackInfo.ResultCode == Result.Success)
        {
            ViewModelLocator.PlayerDataStorage
.PlayerDataStorageQueryFileList.Execute(null);
            Debug.WriteLine($"Successfully uploaded {writeFileCallbackInfo.Filename}.");
            ViewModelLocator.Main.StatusBarText = string.Empty;
        }
        else
        {
            Debug.WriteLine($"Error uploading {writeFileCallbackInfo.Filename}: {writeFileCallbackInfo.ResultCode}.");
            ViewModelLocator.Main.StatusBarText = string.Empty;
        }    
    });

    if (fileTransferRequest == null)
    {
        Debug.WriteLine("Error uploading file: bad handle");
        ViewModelLocator.Main.StatusBarText = string.Empty;
    }
}

 
  • 这个模式与我们之前实现的通过作品存储读取文件的模式类似:初始化WriteFileOptions,它将持有我们的逻辑,即分块上传文件并报告进度。
  • 我们使用PlayerDataStorage.WriteFile来发起实际的上传,监测除了“Success”之外的任何结果,因为这可能表明达到了下文所述的使用限制
 
  1. 在Commands文件夹中添加PlayerDataStorageWriteFileCommand.cs类:

public class PlayerDataStorageWriteFileCommand : CommandBase
{
    public override bool CanExecute(object parameter)
    {
        return !string.IsNullOrWhiteSpace(ViewModelLocator.Main.ProductUserId);
    }

    public override void Execute(object parameter)
    {
        OpenFileDialog openFileDialog = new OpenFileDialog();
        if (openFileDialog.ShowDialog() == true)
        {
            PlayerDataStorageService.WriteFile(openFileDialog);
        }
    }
}

 
  1. 打开PlayerDataStorageViewModel.cs,声明和实例化新命令:

public PlayerDataStorageQueryFileListCommand PlayerDataStorageQueryFileList { get; set; }
public PlayerDataStorageWriteFileCommand PlayerDataStorageWriteFile { get; set; }

public PlayerDataStorageViewModel()
{
    PlayerDataStorageQueryFileList = new PlayerDataStorageQueryFileListCommand();
    PlayerDataStorageWriteFile = new PlayerDataStorageWriteFileCommand();
}

 
  1. 最后,打开ViewModelLocator.cs并将以下内容添加到RaiseConnectCanExecuteChanged()方法中:

PlayerDataStorage.PlayerDataStorageWriteFile.RaiseCanExecuteChanged();

现在我们可以运行应用程序并上传文件了(需要考虑最下面列出的使用限制)。上传后,我们的代码会触发一个查询,ListView中将显示新文件。
 
App Player Data Storage Added Files
玩家数据存储中的已上传文件

我们也可以进入开发者门户,导航至我们的产品,然后依次点击“游戏服务”和“玩家数据存储”,通过从应用程序UI中复制的PUID查询玩家。我们在这里可以看到这名用户的文件列表,以及当前文件的占用空间和最大文件的占用空间。
 
Developer Portal Player Data Storage Added Files
在Epic Games开发者门户中上传的文件

读取文件

下一步是从云端读取文件,与我们通过作品存储服务所做的操作类似。这里的逻辑基本相同:
 
  1. 在PlayerDataStorageService.cs中添加以下方法:

public static void ReadFile(FileMetadata fileMetadata)
{
    var readFileOptions = new ReadFileOptions()
    {
        LocalUserId = ProductUserId.FromString(ViewModelLocator.Main.ProductUserId),
        Filename = fileMetadata.Filename,
        ReadChunkLengthBytes = 1048576,
        ReadFileDataCallback = (ReadFileDataCallbackInfo readFileDataCallbackInfo) =>
        {
            using var fs = new FileStream($"{App.Settings.CacheDirectory}{readFileDataCallbackInfo.Filename}", FileMode.Append, FileAccess.Write);
            fs.Write(readFileDataCallbackInfo.DataChunk, 0, readFileDataCallbackInfo.DataChunk.Length);
            return ReadResult.ContinueReading;
        },
        FileTransferProgressCallback = (FileTransferProgressCallbackInfo fileTransferProgressCallbackInfo) =>
        {
            var percentComplete = (double)fileTransferProgressCallbackInfo.BytesTransferred / (double)fileTransferProgressCallbackInfo.TotalFileSizeBytes * 100;
            ViewModelLocator.Main.StatusBarText = $"Downloading file <{fileTransferProgressCallbackInfo.Filename}> ({System.Math.Ceiling(percentComplete)}%)...";
        }
    };

    ViewModelLocator.Main.StatusBarText = $"Downloading file <{readFileOptions.Filename}> (creating buffer)...";

    var fileTransferRequest = App.Settings.PlatformInterface
.GetPlayerDataStorageInterface().ReadFile(readFileOptions, null, (ReadFileCallbackInfo readFileCallbackInfo) =>
    {
        Debug.WriteLine($"ReadFile {readFileCallbackInfo.ResultCode}");

        if (readFileCallbackInfo.ResultCode == Result.Success)
        {
            Debug.WriteLine($"Successfully downloaded {readFileCallbackInfo.Filename} to {App.Settings.CacheDirectory}.");
            ViewModelLocator.Main.StatusBarText = string.Empty;
        }
    });

    if (fileTransferRequest == null)
    {
        Debug.WriteLine("Error downloading file: bad handle");
        ViewModelLocator.Main.StatusBarText = string.Empty;
    }
}

   
  1. 在Commands文件夹中添加PlayerDataStorageReadFileCommand.cs类:

public class PlayerDataStorageReadFileCommand : CommandBase
{
    public override bool CanExecute(object parameter)
    {
        return ViewModelLocator.PlayerDataStorage.SelectedPlayerDataStorageFile != null;
    }

    public override void Execute(object parameter)
    {
        PlayerDataStorageService.ReadFile(ViewModelLocator
.PlayerDataStorage.SelectedPlayerDataStorageFile);
    }
}

 
  1. 打开PlayerDataStorageViewModel.cs,声明和实例化新命令:

public PlayerDataStorageQueryFileListCommand PlayerDataStorageQueryFileList { get; set; }
public PlayerDataStorageWriteFileCommand PlayerDataStorageWriteFile { get; set; }
public PlayerDataStorageReadFileCommand PlayerDataStorageReadFile { get; set; }

public PlayerDataStorageViewModel()
{
    PlayerDataStorageQueryFileList = new PlayerDataStorageQueryFileListCommand();
    PlayerDataStorageWriteFile = new PlayerDataStorageWriteFileCommand();
    PlayerDataStorageReadFile = new PlayerDataStorageReadFileCommand();
}

 
  1. 最后,打开PlayerDataStorageView.xaml.cs并将以下内容添加到PlayerDataStorageFilesListView_SelectionChanged()方法中:

ViewModel.PlayerDataStorageReadFile.RaiseCanExecuteChanged();

现在,查询文件之后,你就能够在ListView中选择一个文件,并点击“Download file”按钮从云端获取文件。你应该能在调试窗口中看到如下表明下载完成的输出:

ReadFile Success
Successfully downloaded test_file.txt to C:\Users\<User>\AppData\Local\Temp\.

复制文件

有时,你可能想在玩家的存储空间中复制文件,而不经过下载和上传。这时复制API就能派上用场:
 
  1. 在PlayerDataStorageService.cs中添加以下方法:

public static void DuplicateFile(FileMetadata fileMetadata)
{
    var duplicateFileOptions = new DuplicateFileOptions()
    {
        LocalUserId = ProductUserId.FromString(ViewModelLocator.Main.ProductUserId),
        SourceFilename = fileMetadata.Filename,
        DestinationFilename = $"{fileMetadata.Filename}_(copy)"
    };

    ViewModelLocator.Main.StatusBarText = $"Copying <{duplicateFileOptions.SourceFilename}> as <{duplicateFileOptions.DestinationFilename}>...";

    App.Settings.PlatformInterface.GetPlayerDataStorageInterface()
.DuplicateFile(duplicateFileOptions, null, (DuplicateFileCallbackInfo duplicateFileCallbackInfo) =>
    {
        Debug.WriteLine($"DuplicateFile {duplicateFileCallbackInfo.ResultCode}");

        if (duplicateFileCallbackInfo.ResultCode == Result.Success)
        {
            ViewModelLocator.PlayerDataStorage
.PlayerDataStorageQueryFileList.Execute(null);
            ViewModelLocator.Main.StatusBarText = "Successfully copied file.";
        }
        else
        {
            Debug.WriteLine("Copying file failed: " + duplicateFileCallbackInfo.ResultCode);
            ViewModelLocator.Main.StatusBarText = string.Empty;
        }
    });
}

   
  1. 在Commands文件夹中添加PlayerDataStorageDuplicateFileCommand.cs类:

public class PlayerDataStorageDuplicateFileCommand : CommandBase
{
    public override bool CanExecute(object parameter)
    {
        return ViewModelLocator.PlayerDataStorage.SelectedPlayerDataStorageFile != null;
    }

    public override void Execute(object parameter)
    {
        PlayerDataStorageService.DuplicateFile(ViewModelLocator
.PlayerDataStorage.SelectedPlayerDataStorageFile);
    }
}

 
  1. 打开PlayerDataStorageViewModel.cs,声明和实例化新命令:

public PlayerDataStorageQueryFileListCommand PlayerDataStorageQueryFileList { get; set; }
public PlayerDataStorageWriteFileCommand PlayerDataStorageWriteFile { get; set; }
public PlayerDataStorageReadFileCommand PlayerDataStorageReadFile { get; set; }
public PlayerDataStorageDuplicateFileCommand PlayerDataStorageDuplicateFile { get; set; }

public PlayerDataStorageViewModel()
{
    PlayerDataStorageQueryFileList = new PlayerDataStorageQueryFileListCommand();
    PlayerDataStorageWriteFile = new PlayerDataStorageWriteFileCommand();
    PlayerDataStorageReadFile = new PlayerDataStorageReadFileCommand();
    PlayerDataStorageDuplicateFile = new PlayerDataStorageDuplicateFileCommand();
}

 
  1. 最后,打开PlayerDataStorageView.xaml.cs并将以下内容添加到PlayerDataStorageFilesListView_SelectionChanged()方法中:

ViewModel.PlayerDataStorageDuplicateFile.RaiseCanExecuteChanged();

当我们在ListView中选择文件并点击“Duplicate file”按钮后,我们将看到一个同名但附加了“_(copy)”的新文件。
 
App Player Data Storage Duplicated File
在玩家数据存储服务的云存储空间中复制的文件

删除文件

我们最后要实现的是删除文件功能:
 
  1. 在PlayerDataStorageService.cs中添加以下方法:

public static void DeleteFile(FileMetadata fileMetadata)
{
    var deleteFileOptions = new DeleteFileOptions()
    {
        LocalUserId = ProductUserId.FromString(ViewModelLocator.Main.ProductUserId),
        Filename = fileMetadata.Filename
    };

    ViewModelLocator.Main.StatusBarText = $"Deleting <{deleteFileOptions.Filename}>...";

    App.Settings.PlatformInterface.GetPlayerDataStorageInterface()
.DeleteFile(deleteFileOptions, null, (DeleteFileCallbackInfo deleteFileCallbackInfo) =>
    {
        Debug.WriteLine($"DeleteFile {deleteFileCallbackInfo.ResultCode}");

        if (deleteFileCallbackInfo.ResultCode == Result.Success)
        {
            ViewModelLocator.PlayerDataStorage
.PlayerDataStorageQueryFileList.Execute(null);
            ViewModelLocator.Main.StatusBarText = "Successfully deleted file.";
        }
        else
        {
            Debug.WriteLine("Deleting file failed: " + deleteFileCallbackInfo.ResultCode);
            ViewModelLocator.Main.StatusBarText = string.Empty;
        }
    });
}

   
  1. 在Commands文件夹中添加PlayerDataStorageDeleteFileCommand.cs类:

public class PlayerDataStorageDeleteFileCommand : CommandBase
{
    public override bool CanExecute(object parameter)
    {
        return ViewModelLocator.PlayerDataStorage.SelectedPlayerDataStorageFile != null;
    }

    public override void Execute(object parameter)
    {
        PlayerDataStorageService.DeleteFile(ViewModelLocator
.PlayerDataStorage.SelectedPlayerDataStorageFile);
    }
}

 
  1. 打开PlayerDataStorageViewModel.cs,声明并实例化新命令:

public PlayerDataStorageQueryFileListCommand PlayerDataStorageQueryFileList { get; set; }
public PlayerDataStorageWriteFileCommand PlayerDataStorageWriteFile { get; set; }
public PlayerDataStorageReadFileCommand PlayerDataStorageReadFile { get; set; }
public PlayerDataStorageDuplicateFileCommand PlayerDataStorageDuplicateFile { get; set; }
public PlayerDataStorageDeleteFileCommand PlayerDataStorageDeleteFile { get; set; }

public PlayerDataStorageViewModel()
{
    PlayerDataStorageQueryFileList = new PlayerDataStorageQueryFileListCommand();
    PlayerDataStorageWriteFile = new PlayerDataStorageWriteFileCommand();
    PlayerDataStorageReadFile = new PlayerDataStorageReadFileCommand();
    PlayerDataStorageDuplicateFile = new PlayerDataStorageDuplicateFileCommand();
    PlayerDataStorageDeleteFile = new PlayerDataStorageDeleteFileCommand();
}

 
  1. 最后,打开PlayerDataStorageView.xaml.cs并将以下内容添加到PlayerDataStorageFilesListView_SelectionChanged()方法中:

ViewModel.PlayerDataStorageDeleteFile.RaiseCanExecuteChanged();

现在,我们可以使用“Delete file”按钮删除在ListView中选择的文件。

使用限制

玩家数据存储存在一些使用限制,为所有玩家确保可靠性和可用性。撰写本文时,存在下列限制(请务必参阅这篇文档了解最新信息):
  • 每分钟读写请求次数不能超过1000
  • 单个文件的尺寸不能超过200 MB
  • 每个用户的存储空间总计不能超过400 MB
  • 每个用户的文件个数不能超过1000

如果试图上传的文件超过单个文件200 MB的尺寸上限,对PlayerDataStorage.WriteFile的调用将失败,返回PlayerDataStorageFileSizeTooLarge。一旦超过用户400 MB的总计存储空间,对PlayerDataStorage.WriteFile的额外调用将失败,返回PlayerDataStorageUserThrottled,指示该用户处在节流状态。在删除足够多的文件,使占用的存储空间小于400 MB之前,该用户的存储空间将保持在节流状态。所以,节流状态下仍允许查询文件、检索文件元数据和删除文件。

获取代码

在下方可以获取本文的代码。请按照GitHub仓库中的使用说明设置下载的代码。
我们讨论了作品存储和玩家数据存储之间的区别,在下一篇文章中,我们将使用玩家数据存储实现存储和检索特定于玩家的数据。

你可以在系列目录中找到包含本系列所有文章的完整列表,如果你对本文有任何疑问或反馈,请前往社区论坛告知我们。

    你的成功就是我们的成功

    Epic秉承开放、整合的游戏社区理念。通过免费向所有人提供在线服务,我们致力于帮助更多开发者服务好他们自己的玩家社区。