获取与设置玩家状态信息

Epic Games技术客户经理Rajen Kishna |
2021年10月12日
欢迎回到我们的定期博文系列,探索在项目中使用Epic在线服务的各个方面。我们在之前的文章中分享了如何使用AuthInterface实现登录与登出功能,现在我们来看看如何获取与设置状态信息数据。以下是我们今天会讲解的内容:
 

如何使用“使用者状态接口”

用户状态接口有两个用途:
 
  • 为已认证的玩家设置(细节)状态
    • 除了设置状态之外,你还可以使用它向玩家的好友传递任意键值对,让你在游戏中显示额外的背景内容,例如为什么无法加入游戏(比如仅限邀请的队伍),或者玩家使用了什么角色皮肤。需要注意的是,这并非实时数据,因此最好通过这种方式传输时效性要求不高的数据。
  • 查询玩家好友的状态信息
    • 因为SDK只能返回同意游戏许可要求的玩家好友,所以只有登录并且获得游戏许可,或者通过Epic Games商城购买游戏并默认获得许可的玩家,才能够被查询。 

我希望在本文中介绍这两种情况,但我们会在下一篇文章中了解现实世界的状态查询例子,并将涵盖好友接口。

为应用添加状态许可

说到许可,我们还要为Epic账户服务(EAS)应用添加在线状态(状态)许可,使玩家必须同意使用状态API。
 
  1. 登录开发者门户https://dev.epicgames.com/portal/
  2. 找到你的产品,在左侧菜单中点击Epic账户服务。
  3. 点击你所使用的应用旁边的配置按钮。
  4. 点击右上角的许可标签页。
  5. 启用在线状态(状态)许可。
  6. 点击保存以确认。

Developer Portal Online Presence Permission

为EAS应用添加在线状态许可


最后,我们可以在Visual Studio解决方案中打开ApplicationSettings.c,并添加状态ScopeFlag:

public AuthScopeFlags ScopeFlags
{
    get
    {
        return AuthScopeFlags.BasicProfile | AuthScopeFlags.Presence;
    }
}


运行应用并触发授权登录时,你就会在浏览器中看到更新后的许可界面,并且依然能够成功完成认证。

查询玩家状态

我们之前提到,我们会以较为普遍的方式实现查询功能,以便在后续文章中查询好友的状态。这是我们(除认证之外)实现的第一个端口,我们会运用一些额外的步骤来构建用户界面,为添加的每个服务设置标签页。
 
  1. 首先在项目的根目录中创建一个文件夹“Views”。
  2. 为文件夹新建一个UserControl,命名为“PresenceView”
  3. 粘贴以下XAML,替换空白的网格节点:

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

    <StackPanel Grid.Column="1">
        <Button Width="100" Height="23" Margin="2" Content="Query presence" Command="{Binding PresenceQuery}" />
        <Button Width="100" Height="23" Margin="2" Content="Modify presence" Command="{Binding PresenceModifyStatus}" />
    </StackPanel>

    <StackPanel Grid.Column="0">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Status:" Margin="2" />
            <ComboBox Width="150" HorizontalAlignment="Left" SelectedItem="{Binding Status}" ItemsSource="{Binding StatusOptions}" IsEnabled="{Binding ProductIdText, Converter={StaticResource StringToBooleanConverter}}" />
        </StackPanel>

        <StackPanel Orientation="Horizontal">
            <TextBlock Text="ProductId:" Margin="2" />
            <TextBlock Text="{Binding ProductIdText}" Margin="2" />
        </StackPanel>

        <StackPanel Orientation="Horizontal">
            <TextBlock Text="ProductVersion:" Margin="2" />
            <TextBlock Text="{Binding ProductVersionText}" Margin="2" />
        </StackPanel>

        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Platform:" Margin="2" />
            <TextBlock Text="{Binding PlatformText}" Margin="2" />
        </StackPanel>

        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Rich Text:" Margin="2" />
            <TextBlock Text="{Binding RichText}" Margin="2" />
        </StackPanel>
    </StackPanel>
</Grid>

 
  • 你可以在设计视图中看到,这会添加一个简单的用户界面,显示我们查询的状态信息,并且可以在选项中使用对应的“修改”按钮来更改状态。
 
  1. 打开PresenceView.xaml.cs并定义DataContext:

public partial class PresenceView : UserControl
{
    public PresenceViewModel ViewModel { get { return ViewModelLocator.Presence; } }

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

 
  1. 在ViewModels文件夹中添加一个PresenceViewModel.cs类:

public class PresenceViewModel : BindableBase
{
    private Status _status;
    public Status Status
    {
        get { return _status; }
        set { SetProperty(ref _status, value); }
    }

    private string _productIdText;
    public string ProductIdText
    {
        get { return _productIdText; }
        set { SetProperty(ref _productIdText, value); }
    }

    private string _productVersionText;
    public string ProductVersionText
    {
        get { return _productVersionText; }
        set { SetProperty(ref _productVersionText, value); }
    }

    private string _platformText;
    public string PlatformText
    {
        get { return _platformText; }
        set { SetProperty(ref _platformText, value); }
    }

    private string _richText;
    public string RichText
    {
        get { return _richText; }
        set { SetProperty(ref _richText, value); }
    }

    private List<Enum> _statusOptions;
    public List<Enum> StatusOptions
    {
        get { return _statusOptions; }
        set { SetProperty(ref _statusOptions, value); }
    }
}

 
  1. 为ViewModelLocator添加一个PresenceViewModel的静态引用:

private static PresenceViewModel _presence;
public static PresenceViewModel Presence
{
    get { return _presence ??= new PresenceViewModel(); }
}

 
  1. 为Services文件夹添加一个PresenceService.cs类:

public static void Query(EpicAccountId targetUserId)
{
    var queryPresenceOptions = new QueryPresenceOptions()
    {
        LocalUserId = EpicAccountId.FromString(ViewModelLocator.Main.AccountId),
        TargetUserId = targetUserId
    };

    ViewModelLocator.Main.StatusBarText = "Querying user presence...";

   App.Settings.PlatformInterface.GetPresenceInterface()
.QueryPresence(queryPresenceOptions, null, (QueryPresenceCallbackInfo queryPresenceCallbackInfo) =>
    {
        Debug.WriteLine($"QueryPresence {queryPresenceCallbackInfo.ResultCode}");

        if (queryPresenceCallbackInfo.ResultCode == Result.Success)
        {
            Copy(queryPresenceCallbackInfo.LocalUserId, queryPresenceCallbackInfo.TargetUserId);
        }
        else if (Common.IsOperationComplete(queryPresenceCallbackInfo.ResultCode))
        {
            Debug.WriteLine("Query presence failed: " + queryPresenceCallbackInfo.ResultCode);
            ViewModelLocator.Main.StatusBarText = string.Empty;
        }
    });
}

public static void Copy(EpicAccountId localUserId, EpicAccountId targetUserId)
{
    var copyPresenceOptions = new CopyPresenceOptions()
    {
        LocalUserId = localUserId,
        TargetUserId = targetUserId
    };

    var result = App.Settings.PlatformInterface.GetPresenceInterface()
.CopyPresence(copyPresenceOptions, out var info);
    if (result == Result.Success)
    {
        ViewModelLocator.Main.StatusBarText = "User presence retrieved.";

        if (localUserId == targetUserId)
        {
            ViewModelLocator.Presence.Status = info.Status;
            ViewModelLocator.Presence.ProductIdText = info.ProductId;
            ViewModelLocator.Presence.ProductVersionText = info.ProductVersion;
            ViewModelLocator.Presence.PlatformText = info.Platform;
            ViewModelLocator.Presence.RichText = info.RichText;
        }

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

 
  • 这里我们能运用两种方法: 
    • Query()用于获取非认证玩家用户的状态。我们使用的模式和Auth Interface相同:将一个可选类实例化,在使用者状态端口中调用QueryPresence,通过回调法来验证结果。
    • Copy(),因为QueryPresence会将结果缓存在本地,能够在已认证的玩家通过Auth Interface Login调用后自动获取状态。我们使用CopyPresence从缓存中获取实际的状态信息。
  • 在这个示例中,本地用户和目标用户是同一个,所以我们使用状态信息填充PresenceViewModel,在用户界面上显示信息。
 
  1. 为Commands文件夹添加一个PresenceQueryCommand.cs类:

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

    public override void Execute(object parameter)
    {
        if (parameter == null)
        {
            PresenceService.Copy(EpicAccountId
.FromString(ViewModelLocator.Main.AccountId), EpicAccountId.FromString(ViewModelLocator.Main.AccountId));
        }
    }
}

 
  • 我们可以搜索空参数来跳过这个查询方法,但我们也会在下篇关于好友的文章中实施查询。
 
  1. 再次打开PresenceViewModel.cs,将PresenceQueryCommand实例化:

public PresenceQueryCommand PresenceQuery { get; set; }

public PresenceViewModel()
{
    StatusOptions = Enum.GetValues(Status.GetType()).Cast<Enum>().ToList();

    PresenceQuery = new PresenceQueryCommand();
}

 
  • 要注意的是,我们还要将通过Epic.OnlineServices.Presence.Status枚举包含的选项,将StatusOptions实例化。
 
  1. 在ViewModelLocator RaiseAuthCanExecuteChanged()方法中加入以下命令行,确保查询状态按钮仅在用户登陆时启用。

Presence.PresenceQuery.RaiseCanExecuteChanged();
 
  1. 最后向MainWindow TabControl中加入PresenceView:

<TabItem x:Name="Presence" Header="Presence">
    <views:PresenceView />
</TabItem>


现在我们就可以运行应用、授权认证、在浏览器中同意更新后的许可,随后就能使用查询状态按钮,从缓存中获取当前的状态及相关信息。
 
App MainWindow Queried Presence UI
已认证玩家的状态信息

你还必须在状态能够发生改变的游戏的制作中实施AddNotifyOnPresenceChanged,从而不必再次调用QueryPresence。

设置状态信息

现在我们可以根据下拉菜单中的选项来设置状态信息。 
 
  1. 打开PresenceService.cs并添加以下方法:

public static void ModifyStatus()
{
    var createPresenceModificationOptions = new CreatePresenceModificationOptions()
    {
        LocalUserId = EpicAccountId.FromString(ViewModelLocator.Main.AccountId)
    };

    ViewModelLocator.Main.StatusBarText = "Creating presence modification...";

    var result = App.Settings.PlatformInterface.GetPresenceInterface()
.CreatePresenceModification(createPresenceModificationOptions, out var presenceModification);

    Debug.WriteLine($"CreatePresenceModification {result}");

    if (result == Result.Success)
    {
        var setStatusOptions = new PresenceModificationSetStatusOptions()
        {
            Status = ViewModelLocator.Presence.Status
        };

        result = presenceModification.SetStatus(setStatusOptions);
        Debug.WriteLine($"SetStatus {result}");

        var setPresenceOptions = new SetPresenceOptions()
        {
            LocalUserId = EpicAccountId.FromString(ViewModelLocator.Main.AccountId),
            PresenceModificationHandle = presenceModification
        };

        ViewModelLocator.Main.StatusBarText = "Setting presence status...";

        App.Settings.PlatformInterface.GetPresenceInterface()
.SetPresence(setPresenceOptions, null, (SetPresenceCallbackInfo setPresenceCallbackInfo) =>
        {
            Debug.WriteLine($"SetPresence {setPresenceCallbackInfo.ResultCode}");
            if (Common.IsOperationComplete(setPresenceCallbackInfo.ResultCode))
            {
                if (presenceModification != null)
                {
                    presenceModification.Release();
                    presenceModification = null;
                }

                ViewModelLocator.Main.StatusBarText = string.Empty;
            }
        });
    }
    else if (Common.IsOperationComplete(result))
    {
        Debug.WriteLine("Create presence modification failed: " + result);
        ViewModelLocator.Main.StatusBarText = string.Empty;
    }
}

 
  • 首先我们会创建一个PresenceModification对象,随后会调整对象并将它传递给SetPresence方法,从而改变状态信息。
  • 我们这里只使用SetStatus来更改状态,但你还可以更改与状态相关的富文本(例如表明玩家正在游玩的关卡)你还可以添加、替换删除自定义状态数据,通过上述方式传输任意键值对数据。
  • 最后你还可以设置状态的JoinInfo,将信息从EOS覆层中传输到游戏里,让好友能够根据你的游戏的逻辑来加入游戏。此时,你可以订阅覆层通知,在玩家接受加入申请等情况下获得通知。
 
  1. 我们还要为状态修改命令的CanExecuteChanged行为添加一个触发器,只有在我们首次查询过玩家的状态后才能触发:

Add: 

ViewModelLocator.Presence.PresenceModifyStatus.
RaiseCanExecuteChanged();


对于PresenceService.Copy()中的以下代码:

if (result == Result.Success)
{
    ViewModelLocator.Main.StatusBarText = "User presence retrieved.";

    ViewModelLocator.Presence.Status = info.Status;
    ViewModelLocator.Presence.ProductIdText = info.ProductId;
    ViewModelLocator.Presence.ProductVersionText = info.ProductVersion;
    ViewModelLocator.Presence.PlatformText = info.Platform;
    ViewModelLocator.Presence.RichText = info.RichText;

    ViewModelLocator.Main.StatusBarText = string.Empty;

ViewModelLocator.Presence.PresenceModifyStatus.
RaiseCanExecuteChanged();
}

 
  1. 为Commands文件夹添加一个PresenceModifyStatusCommand.cs类:

public class PresenceModifyStatusCommand : CommandBase
{
    public override bool CanExecute(object parameter)
    {
        return !string.IsNullOrWhiteSpace(ViewModelLocator.Presence.ProductIdText);
    }

    public override void Execute(object parameter)
    {
        PresenceService.ModifyStatus();
    }
}

 
  1. 在PresenceViewModel.cs中声明并初始化新的命令:

public PresenceModifyStatusCommand PresenceModifyStatus { get; set; }
PresenceModifyStatus = new PresenceModifyStatusCommand();


现在当我们运行应用程序,就可以查询初始状态、使用下拉菜单更改状态,还能通过修改来设置新状态。

获取代码

和往常一样,一定要获取代码并参照《在Visual Studio 2019中为EOS设置C#解决方案》文章的设置C#解决方案部分的第五步和第九步,将SDK添加到解决方案中,并编辑ApplicationSettings.cs以包含你的SDK凭证。
如果你有任何疑问或者反馈,敬请联系我们,我们将在下篇文章中深度解析如何查询Epic好友和及其状态。 请查看系列目录,了解该系列的其他文章。

    你的成功就是我们的成功

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