查询Epic好友及其状态

Epic Games技术客户经理Rajen Kishna |
2021年10月19日
在介绍了身份验证状态接口之后,为了完善关于Epic账户服务的Epic在线服务入门文章,我们将继续介绍如何使用好友接口查询Epic好友及其状态。在本文中,我们将介绍:
 

好友API作用域

在深入研究查询API之前,了解这些API的作用域非常重要。如前一篇文章所述,默认情况下,好友API不会返回玩家在Epic Games启动器等地方所能够看到的完整好友列表。我们看到,当我们的EAS应用程序访问我们指定的特定信息(基本资料、状态和好友)时,玩家将收到准许提示。只有在玩家的好友提供准许后,好友接口才会在查询时返回该特定好友。如果玩家通过Epic Games商城购买游戏,作为购买流程的一部分,后台将自动提供准许。举个例子:
 
  • 玩家A是玩家B和玩家C的好友,并通过Epic Games商城购买了游戏
  • 玩家B未拥有该游戏
  • 玩家C通过Steam购买了该游戏

在这种情况下,当我们调用查询API时,玩家A不会在游戏中看到任何好友,除非玩家C启动该游戏,并在身份验证流程中提供准许。查询API永远不会返回玩家B,因为他没有该款游戏。

请注意,根据文档,目前没有API能够管理好友请求或为玩家添加/删除好友。我们将在今年晚些时候扩展社交覆层,以帮助实现这一功能。

为应用程序添加好友权限

与处理状态时类似,我们必须为EAS应用程序添加好友权限,才能正确取得玩家们的准许:
 
  1. 访问https://dev.epicgames.com/portal/登录开发者门户
  2. 导航到你的产品,然后单击左侧菜单中的“Epic账户服务”
  3. 单击你正在使用的应用程序旁的“配置”按钮
  4. 单击右上方的“权限”选项卡
  5. 启用“好友(friends_list)”权限。请注意,你将在左侧看到预览更新,它现在包括关于好友准许的信息
  6. 单击“保存”以确认
Developer Portal Friends Permission
为EAS应用程序添加好友权限

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

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


现在,当你运行应用程序并触发身份验证登录时,应该可以在浏览器中看到更新后的准许屏幕,并且仍然能够成功地完成身份验证。

查询好友

添加完成之后,我们就可以在解决方案中实现查询功能:
 
  1. 在Views文件夹中添加一个名为FriendsView的新UserControl
  2. 粘贴以下XAML,替换空的Grid节点:
 

<Grid>

    <Grid.ColumnDefinitions>

        <ColumnDefinition Width="*" />

        <ColumnDefinition Width="Auto" />

    </Grid.ColumnDefinitions>


    <StackPanel Grid.Column="1">

        <Button Width="100" Height="23" Margin="2" Content="Query friends" Command="{Binding FriendsQuery}" />

        <Button Width="100" Height="23" Margin="2" Content="Subscribe" Command="{Binding FriendsSubscribeUpdates}" />

        <Button Width="100" Height="23" Margin="2" Content="Unsubscribe" Command="{Binding FriendsUnsubscribeUpdates}" />

        <Button Width="100" Height="23" Margin="2" Content="Query status" Command="{Binding PresenceQuery}" CommandParameter="{Binding SelectedFriend}" />

    </StackPanel>


    <ListView x:Name="FriendsListView" Grid.Column="0" Margin="2" ItemsSource="{Binding Friends}" SelectedItem="{Binding SelectedFriend}" SelectionChanged="FriendsListView_SelectionChanged">

        <ListView.View>

            <GridView>

                <GridViewColumn Header="Friend" Width="300" DisplayMemberBinding="{Binding EpicAccountId}">

                    <GridViewColumn.HeaderContainerStyle>

                        <Style TargetType="{x:Type GridViewColumnHeader}">

                            <Setter Property="HorizontalContentAlignment" Value="Left" />

                        </Style>

                    </GridViewColumn.HeaderContainerStyle>

                </GridViewColumn>

                <GridViewColumn Header="Friend Status" Width="150" DisplayMemberBinding="{Binding FriendsStatus}">

                    <GridViewColumn.HeaderContainerStyle>

                        <Style TargetType="{x:Type GridViewColumnHeader}">

                            <Setter Property="HorizontalContentAlignment" Value="Left" />

                        </Style>

                    </GridViewColumn.HeaderContainerStyle>

                </GridViewColumn>

                <GridViewColumn Header="Status" Width="150" DisplayMemberBinding="{Binding Status}">

                    <GridViewColumn.HeaderContainerStyle>

                        <Style TargetType="{x:Type GridViewColumnHeader}">

                            <Setter Property="HorizontalContentAlignment" Value="Left" />

                        </Style>

                    </GridViewColumn.HeaderContainerStyle>

                </GridViewColumn>

            </GridView>

        </ListView.View>

    </ListView>

</Grid>

 
  • 我们将使用一个ListView来显示API返回给我们的好友,并添加额外的按钮来订阅更新或取消订阅更新,以及查询好友的状态。
 
  1. 打开FriendsView.xaml.cs并定义DataContext:

public partial class FriendsView : UserControl
{
    public FriendsViewModel ViewModel { get { return ViewModelLocator.Friends; } }

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

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

 
  • 注意空白的FriendsListView_SelectionChanged事件句柄,我们将在稍后实现好友状态查询时使用它。
 
  1. 我们将在UI中结合一些属性(Epic账户Id、好友状态、在线状态)来显示好友,因此,我们在项目根目录下创建一个名为Models的文件夹,然后在其中添加一个Friend.cs类:

public class Friend : BindableBase
{
    private EpicAccountId _epicAccountId;
    public EpicAccountId EpicAccountId
    {
        get { return _epicAccountId; }
        set { SetProperty(ref _epicAccountId, value); }
    }

    private FriendsStatus _friendsStatus;
    public FriendsStatus FriendsStatus
    {
        get { return _friendsStatus; }
        set { SetProperty(ref _friendsStatus, value); }
    }

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

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

public class FriendsViewModel : BindableBase
{
    private ObservableCollection<Friend> _friends;
    public ObservableCollection<Friend> Friends
    {
        get { return _friends; }
        set { SetProperty(ref _friends, value); }
    }

    private Friend _selectedFriend;
    public Friend SelectedFriend
    {
        get { return _selectedFriend; }
        set { SetProperty(ref _selectedFriend, value); }
    }

    private ulong _notificationId;
    public ulong NotificationId
    {
        get { return _notificationId; }
        set { SetProperty(ref _notificationId, value); }
    }
}

 
  1. 在ViewModelLocator中添加一个对FriendsViewModel的静态引用:

private static FriendsViewModel _friends;
public static FriendsViewModel Friends
{
    get { return _friends ??= new FriendsViewModel(); }
}

 
  1. 在Services文件夹中添加一个FriendsService.cs类:

public static class FriendsService
{
    public static void QueryFriends()
    {
        var queryFriendsOptions = new QueryFriendsOptions()
        {
            LocalUserId =​​​​​ EpicAccountId.FromString(ViewModelLocator.Main.AccountId)
        };

        ViewModelLocator.Main.StatusBarText = $"Querying friends...";

        App.Settings.PlatformInterface.GetFriendsInterface()
.QueryFriends(queryFriendsOptions, null, (QueryFriendsCallbackInfo queryFriendsCallbackInfo) =>
        {
            Debug.WriteLine($"QueryFriends {queryFriendsCallbackInfo.ResultCode}");

            if (queryFriendsCallbackInfo.ResultCode == Result.Success)
            {
                var getFriendsCountOptions = new GetFriendsCountOptions()
                {
                    LocalUserId = EpicAccountId.FromString(ViewModelLocator.Main.AccountId)
                };
                var friendsCount = App.Settings.PlatformInterface.GetFriendsInterface()
.GetFriendsCount(getFriendsCountOptions);

                for (int i = 0; i < friendsCount; i++)
                {
                    var getFriendAtIndexOptions = new GetFriendAtIndexOptions()
                    {
                        LocalUserId = EpicAccountId.FromString(ViewModelLocator.Main.AccountId),
                        Index = ​​​​​​​i
                    };
                    var friend = ​​​​​​​App.Settings.PlatformInterface.GetFriendsInterface()
.GetFriendAtIndex(getFriendAtIndexOptions);

                    if (friend != null)
                    {
                        var getStatusOptions = ​​​​​​​new GetStatusOptions()
                        {
                            LocalUserId = ​​​​​​​EpicAccountId.FromString(ViewModelLocator.Main.AccountId),
                            TargetUserId = friend
                        };
                        var friendStatus = ​​​​​​​App.Settings.PlatformInterface.GetFriendsInterface()
.GetStatus(getStatusOptions);

                        ViewModelLocator.Friends.Friends.Add(new Friend()
                        {
                            EpicAccountId = ​​​​​​​friend,
                            FriendsStatus = ​​​​​​​friendStatus
                        });
                    }
                }
            }

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

 
  • 我们实例化一个“options”类,将它传递给好友接口中的QueryFriends API,从缓存中检索计数,并通过迭代来检索每个好友的状态。
  • 然后,我们将好友(包括好友的状态)添加到ViewModel中,以便在UI中显示。
 
  1. 在Commands文件夹中添加一个FriendsQueryCommand.cs类:

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

    public override void Execute(object parameter)
    {
        ViewModelLocator.Friends.Friends = new ObservableCollection<Friend>();
        FriendsService.QueryFriends();
    }
}

 
  1. 现在再次打开FriendsViewModel.cs,并实例化FriendsQueryCommand:

public FriendsQueryCommand FriendsQuery { get; set; }

public FriendsViewModel()
{
    FriendsQuery = new FriendsQueryCommand();
}

 
  1. 在ViewModelLocator RaiseAuthCanExecuteChanged()方法中添加以下代码行,确保查询状态按钮只有在用户登录时可用:

Friends.FriendsQuery.RaiseCanExecuteChanged();
 
  1. 最后,将PresenceView添加到MainWindow的TabControl中:

<TabItem x:Name="Friends" Header="Friends">
    <views:FriendsView />
</TabItem>



现在我们可以在浏览器中运行应用程序、验证身份,并准许更新的权限,之后我们就能够使用查询好友按钮来获得玩家的好友和状态。然而,正如预期的那样,我们不会取得API返回的任何好友,因为他们没有准许我们的应用程序。我们来解决这个问题。

获取好友的准许

为了验证我们的代码,我们的应用程序必须得到一个好友的准许。注意,我们实现的QueryFriends API只会返回已经接受好友请求的好友。如果这是我们正在开发的一款真正的游戏,我们就能够为应用程序提交一份品牌审查,并与好友分享我们的游戏,以获得他们的准许。

然而,由于我们开发的只是示例,我们必须向我们的开发者门户组织添加好友,否则他们将无法访问我们的应用程序。这样做是为了保护Epic Games生态系统的用户、你(即开发者)、其他游戏开发者以及Epic Games免受品牌欺诈、网络钓鱼等行为的影响。
 
  1. 访问https://dev.epicgames.com/portal/登录开发者门户。
  2. 单击左侧的“组织”选项卡,使用蓝色的邀请按钮邀请一位好友,并选择合适的角色
 
Developer Portal Invite To Organization
向组织中添加人员

现在,我们的好友必须运行示例应用程序,来连接我们的产品、客户端、沙盒和部署。打包项目/bin/目录中的以下文件并共享:
 
  • EOSCSharpSample.dll
  • EOSCSharpSample.exe
  • EOSCSharpSample.runtimeconfig.json
  • EOSSDK-Win32-Shipping.dll

当他们第一次运行应用程序时,会收到准许提示,并且在查询好友时应该能看到我们的账户。接下来,如果我们在好友提供准许之后执行同样的操作,当查询好友时,将会显示他们的账户。
 
App Queried Friends
为登录用户查询到的好友

注意,我们只能看到好友的Epic账户ID,而不是它们的显示名称,但你可以像我们在AuthService.Login()中所做的那样,调用QueryUserInfo来检索每位好友的更多信息。此时,状态列将始终显示“离线”,因为Epic.OnlineServices.Presence.Status的枚举值为0,但我们会在本文中进一步处理这个问题。

订阅更新

查询好友之后,你应该订阅关于好友状态何时发生改变的通知,而不是再次查询。根据Epic.OnlineServices.Friends.FriendsStatus的枚举定义,好友接口将返回好友的这四种状态之一:NotFriends、InviteSent、InviteReceived和Friends。虽然没有直接管理好友的API,但正如刚才所说的,我们仍需要订阅更新,这样,当好友请求被接受,或某人解除好友时,我们才能知道。好友接口提供了API来订阅和取消订阅这些更新,我们现在就来实现它们。
 
  1. 打开FriendsService.cs,并添加以下方法:

public static void SubscribeUpdates()
{
    var addNotifyFriendsUpdateOptions = new AddNotifyFriendsUpdateOptions();

    ViewModelLocator.Main.StatusBarText = $"Adding notification for friends updates...";

    ViewModelLocator.Friends.NotificationId = App.Settings.PlatformInterface.GetFriendsInterface()
.AddNotifyFriendsUpdate(addNotifyFriendsUpdateOptions, null, (OnFriendsUpdateInfo onFriendsUpdateInfo) =>
    {
        Debug.WriteLine($"OnFriendsUpdate: {onFriendsUpdateInfo.TargetUserId}");

        ViewModelLocator.Friends.Friends.SingleOrDefault(f => f.EpicAccountId == onFriendsUpdateInfo.TargetUserId).FriendsStatus = onFriendsUpdateInfo.CurrentStatus;
    });

    ViewModelLocator.Friends.FriendsSubscribeUpdates
.RaiseCanExecuteChanged();
    ViewModelLocator.Friends.FriendsUnsubscribeUpdates
.RaiseCanExecuteChanged();

    ViewModelLocator.Main.StatusBarText = string.Empty;
}

public static void UnsubscribeUpdates()
{
    ViewModelLocator.Main.StatusBarText = $"Removing notification for friends updates...";

    App.Settings.PlatformInterface.GetFriendsInterface()
​​​​​​​.RemoveNotifyFriendsUpdate(ViewModelLocator.Friends.NotificationId);
    ViewModelLocator.Friends.NotificationId = 0;

    ViewModelLocator.Friends.FriendsSubscribeUpdates
.RaiseCanExecuteChanged();
    ViewModelLocator.Friends.FriendsUnsubscribeUpdates
.RaiseCanExecuteChanged();

    ViewModelLocator.Main.StatusBarText = string.Empty;
}

 
  • 我们调用AddNotifyFriendsUpdate,传入空白options,以获取一个返回的通知ID,它将确认通知已经设置完毕,并且,当我们取消订阅更新时,也要用到它(见UnsubscribeUpdates())。
  • 然后我们定义一个回调方法,在其中实现当通知发送完毕时所需的功能。在本例中,我们只是将另一条记录添加到具有新状态的好友列表中。
 
  1. 我们还为这些命令的CanExecuteChanged行为添加了一个触发器,如此一来,只有在我们对好友进行首次查询后,这些命令才可使用:

添加:

ViewModelLocator.Friends.FriendsSubscribeUpdates
.RaiseCanExecuteChanged();
ViewModelLocator.Friends.FriendsUnsubscribeUpdates
.RaiseCanExecuteChanged();


至FriendsService.QueryFriends()中以下行的下方:

[...]
var friendStatus = App.Settings.PlatformInterface.GetFriendsInterface()
.GetStatus(getStatusOptions);

        ViewModelLocator.Friends.Friends.Add(new Friend()
        {
            EpicAccountId = friend,
            FriendsStatus = friendStatus
        });
    }
}


ViewModelLocator.Friends.FriendsSubscribeUpdates
.RaiseCanExecuteChanged();
ViewModelLocator.Friends.FriendsUnsubscribeUpdates
.RaiseCanExecuteChanged();

 
  1. 在Commands文件夹中添加一个FriendsSubscribeUpdatesCommand.cs类:

public class FriendsSubscribeUpdatesCommand : CommandBase
{
    public override bool CanExecute(object parameter)
    {
        return !string.IsNullOrWhiteSpace(ViewModelLocator.Main.AccountId) && (ViewModelLocator.Friends.Friends.Count != 0) && (ViewModelLocator.Friends.NotificationId == 0);
    }

    public override void Execute(object parameter)
    {
        FriendsService.SubscribeUpdates();
    }
}

 
  1. 在Commands文件夹中添加一个FriendsUnsubscribeUpdatesCommand.cs类:
public class FriendsUnsubscribeUpdatesCommand : CommandBase
{
    public override bool CanExecute(object parameter)
    {
        return !string.IsNullOrWhiteSpace(ViewModelLocator.Main.AccountId) && (ViewModelLocator.Friends.Friends.Count != 0) && (ViewModelLocator.Friends.NotificationId != 0);
    }

    public override void Execute(object parameter)
    {
        FriendsService.UnsubscribeUpdates();
    }
}

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

public FriendsQueryCommand FriendsQuery { get; set; }
public FriendsSubscribeUpdatesCommand FriendsSubscribeUpdates { get; set; }
public FriendsUnsubscribeUpdatesCommand FriendsUnsubscribeUpdates { get; set; }

public FriendsViewModel()
{
    FriendsQuery = new FriendsQueryCommand();
    FriendsSubscribeUpdates = new FriendsSubscribeUpdatesCommand();
    FriendsUnsubscribeUpdates = new FriendsUnsubscribeUpdatesCommand();
}


现在,当我们运行应用程序时,就可以查询好友并订阅/取消订阅更新。一旦订阅了更新,通过Epic Games启动程序删除列表中的一个好友后,你会在ListView中看到一个新条目,将该好友的状态显示为NotFriends。

查询好友状态

最后,我们基于上一篇文章中所实现的功能,来实现查询好友在线状态的功能。现在我们可以使用QueryPresence来检索每个好友的状态。
 
  1. 在Commands文件夹中打开PresenceQueryCommand,并用以下代码行替换这两个方法:

public override bool CanExecute(object parameter)
{
    if (parameter == null)
        return !string.IsNullOrWhiteSpace(ViewModelLocator.Main.AccountId);
    else
        return ((Friend)parameter).EpicAccountId != null;
}

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

 
  • 我们修改CanExecute代码,使它也能够检查参数(即我们将从FriendsView ListView中传递的所选好友),并在Execute中添加“else”语句,以在传递好友时调用PresenceService.Query()。
 
  1. 打开FriendsViewModel并实例化PresenceQueryCommand:

public FriendsQueryCommand FriendsQuery { get; set; }
public FriendsSubscribeUpdatesCommand FriendsSubscribeUpdates { get; set; }
public FriendsUnsubscribeUpdatesCommand FriendsUnsubscribeUpdates { get; set; }
public PresenceQueryCommand PresenceQuery { get; set; } 

public FriendsViewModel()
{
    FriendsQuery = new FriendsQueryCommand();
    FriendsSubscribeUpdates = new FriendsSubscribeUpdatesCommand();
    FriendsUnsubscribeUpdates = new FriendsUnsubscribeUpdatesCommand();
    PresenceQuery = new PresenceQueryCommand();
}

 
  1. 最后,在FriendsView.xaml.cs中的FriendsListView_SelectionChanged事件句柄中添加以下内容,确保查询状态按钮只在选中好友时可用:

ViewModel.PresenceQuery.RaiseCanExecuteChanged();

现在,当我们运行应用程序时,点击“查询好友”并选择一个在线的好友,再点击“查询状态”,我们就能在UI中看到他们的状态更新。
 
App Queried Friends Status
被查询好友的状态

正如前一篇文章中所提到的,你应该在制作游戏时实现AddNotifyOnPresenceChanged,以便在好友的在线状态发生变化时获得通知。

获取代码

在下方获取本文的代码。(按照这篇文章中的步骤5和步骤10,将SDK添加到解决方案中,然后将SDK凭证添加到ApplicationSettings.cs中)。
 
在下一篇文章中,我们将开始使用游戏服务,这是Epic在线服务中与平台无关的后端服务。我们首先将介绍连接接口,你可以使用它从众多身份提供商中任选其一,为这些服务验证用户的身份。

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

    你的成功就是我们的成功

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