Epic フレンドとそのステータスのクエリ

Rajen Kishna、Epic Games テクニカル アカウント マネージャー |
2021年10月19日
Epic アカウント サービスにおける Epic Online Services の入門編の締めくくりとして、認証 および プレゼンス インターフェースに続き、Friends インターフェースを使用して Epic フレンドとそのステータスをクエリする方法について解説します。この記事では、次の内容を扱います。
 

Friends API のスコープ

クエリ API の詳細を説明する前に、これらの API のスコープについて理解することが重要です。前の記事で簡単に説明したとおり、たとえば、Epic Games Launcher で確認できるような、プレイヤーの完全なフレンド リストは、デフォルトの Friends API は返しません。ご覧のとおり、指定した個人情報 (基本プロファイル、プレゼンス、フレンド) に EAS アプリケーションがアクセスすることへの同意をプレイヤーに求める画面が表示されます。プレイヤーのフレンドがこの内容に同意した後にだけ、クエリしたときに、Friends インターフェースはその個別のフレンドを返します。プレイヤーが Epic Games ストアでこのゲームを購入した場合、購入フローの一部として暗黙的に同意することになります。たとえば
 
  • プレイヤー A はプレイヤー B とプレイヤー C のフレンドで、Epic Games ストアでこのゲームを購入している
  • プレイヤー B はこのゲームを所有していない
  • プレイヤー C はこのゲームを Steam から購入している

この場合、クエリ API を呼び出したとき、プレイヤー A にフレンドが表示されません。プレイヤー C がこのゲームを起動し、認証フローで同意したときに初めて表示されます。プレイヤー B はこのゲームを所有していないので、クエリ API で情報が返りません。

このドキュメントの記載どおり、現時点でフレンドのリクエストを管理する、プレイヤーに対してフレンドを追加/削除する API がないことに注意してください。ソーシャル オーバーレイはこのような機能を果たすように今年中に拡張される予定です。

フレンドの許可をアプリケーションに追加する

プレゼンスで実行した内容と同様に、プレイヤーから正式な同意を得るために、フレンドに関する許可を EAS アプリケーションに追加する必要があります。
 
  1. Developer Portal (https://dev.epicgames.com/portal/) にログインします。
  2. 自分の製品に移動し、左のメニューにある [Epic Account Services (Epic アカウントサービス)] をクリックします。
  3. 使用するアプリケーションの隣にある [Configure (設定)] ボタンをクリックします。
  4. 右上の [Permissions (許可)] タブをクリックします。
  5. [Friends (friends_list)] に関する許可を有効にします。左にプレビューの更新が表示され、フレンド情報への同意が含まれていることに注意してください。
  6. [Save (保存)] をクリックして確定します。
Developer Portal Friends Permission
Friends に関する許可を EAS アプリケーションに追加する

ここで ApplicationSettings.cs を Visual Studio ソリューションで開き、次のとおり、プレゼンス ScopeFlag を追加できます。

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


アプリケーションを起動し、認証ログインをトリガーすると、ブラウザに更新された同意確認画面が表示されます。同時に認証も正常に完了します。

フレンドをクエリする

このように追加されたので、ソリューションにクエリ機能を実装します。
 
  1. 新しい UserControl (FriendsView) を「Views」フォルダに追加します。
  2. 次の XAML を張り付け、空のグリッド ノードを置き換えます。
 

<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>

 
  • API から返されたフレンドを表示するために ListView を使用し、追加のボタンでフレンドのプレゼンスのクエリとともに更新のサブスクリプション/サブスクリプション解除を切り替えます。
 
  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. フレンド情報 (Epic アカウント ID、フレンド ステータス、プレゼンス ステータス) を表示するために、UI でいくつかの項目を結合するので、プロジェクトのルートに「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. FriendsViewModel.cs クラスを「ViewModels」フォルダに追加します。

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. FriendsViewModel への静的参照を ViewModelLocator に追加します。

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

 
  1. FriendsService.cs クラスを「Services」フォルダに追加にします。

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;
        });
    }
}

 
  • オプション クラスをインスタンス化し、QueryFriends API に Friends インターフェースで渡し、カウントをキャッシュから取得し、各フレンドのステータスの取得を繰り返します。
  • 続いて UI に表示されるように、フレンド (フレンド ステータスを含む) を ViewModel に追加します。
 
  1. FriendsQueryCommand.cs クラスを「Commands」フォルダに追加します。

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() メソッドに追加し、[Query presence] ボタンが、必ずユーザーがログインしたときにだけ有効になるようにします。

Friends.FriendsQuery.RaiseCanExecuteChanged();
 
  1. ここで、PresenceView を MainWindow TabControl に追加します:

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



ここでこのアプリケーションを実行し、認証し、ブラウザで更新されたアクセス許可への同意を得ることができます。その後、[Query friends] ボタンで、プレイヤーのフレンドとステータスを入手できます。ただし、想定どおり、API からフレンド情報が返りません。誰もこのアプリケーションに同意していないからです。これを修正します。

フレンドから同意を得る

このコードを検証するには、フレンドにこのアプリケーションに同意してもらう必要があります。自分のフレンド リクエストに同意したフレンドのみが、実装した QueryFriends API から返ることに注意してください。実際に開発中のゲームである場合は、アプリケーションの Brand Review サブミッションを完了し、同意を得るフレンドとゲームを共有します。 

ただし、ここではこのサンプルを開発しているだけなので、フレンドを Developer Portal の組織 に追加する必要があります。実行しないと、アプリケーションにアクセスできません。これを実行するのは、Epic Games のエコシステム ユーザー、デベロッパー、他のゲーム デベロッパー、Epic Games を、ブランドのなりすまし、フィッシングなどから保護するためです。  
  1. Developer Portal (https://dev.epicgames.com/portal/) にログインします。
  2. 左側の [Organization (組織)] タブをクリックして、青の [Invite (招待)] ボタンでフレンドを招待し、適切な ロール を選択します。
 
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 呼び出しを使用し、各フレンドの詳細情報を取得できることに注意してください。ステータス列にこの時点で “Offline” が常に表示されます。Epic.OnlineServices.Presence.Status 列挙型の値が 0 だからですが、この後で扱います。

更新をサブスクライブする

一度フレンドをクエリしたら、再びクエリして、フレンド ステータスの変更を確認しないために、通知にサブスクライブする必要があります。Friends インターフェースでは、フレンドの 4 種類のステータスのいずれかを、次の Epic.OnlineServices.Friends.FriendsStatus 列挙型に応じて返します。NotFriends、InviteSent、InviteReceived、Friends説明したとおり、フレンドを直接管理する API はありませんが、フレンド リクエストが受け付けられた、またはフレンドではなくなったタイミングを知るために更新をサブスクライブする必要があります。Friends インターフェースには 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 で空のオプションを渡して呼び出し、Notification ID を取得します。両方とも、通知がセットアップされていることを確認し、更新のサブスクリプションを解除するために使用する必要があります (UnsubscribeUpdates() に表示されているとおり)。
  • それから通知が送信されたときに必要な機能を備えたコールバック メソッドを定義します。この場合は、単に、新しいステータスの Friends リストに別のレコードを追加するだけです。
 
  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. FriendsSubscribeUpdatesCommand.cs クラスを「Commands」フォルダに追加:

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. FriendsUnsubscribeUpdatesCommand.cs クラスを「Commands」フォルダに追加:
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 Launcher を通じて、リストされたフレンドを削除でき、ListView にステータスが NotFriends のフレンドの新しいエントリが表示されます。

フレンドのプレゼンスをクエリする

最後に実行することは、フレンドのプレゼンス ステータスをクエリする機能を実装することで、前回の記事で実装した内容を作成します。QueryPresence を利用して、各フレンドのプレゼンスを取得できます。 
 
  1. PresenceQueryCommand を「Commands」フォルダから開き、次のコードで両方のメソッドを置き換えます。

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 から渡した、選択 Friend) もチェックします。さらに、Friend が渡されるとき、PresenceService.Query() を呼び出すために “else” ステートメントを Execute に追加します。
 
  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();

ここでアプリケーションを実行して、[Query friends] をクリックすると、オンラインのフレンドが選択され、[Query status] をクリックすると、UI でステータス更新が表示されます。
 
App Queried Friends Status
クエリされたフレンド ステータス

前の記事で説明したとおり、AddNotifyOnPresenceChanged を製品ゲームに実装し、フレンドのプレゼンス ステータスで変更があるとき、通知する必要があります。

コードを入手する

この記事のコードを次から入手してください (この記事 のステップ 5~10 を実行して、SDK をソリューションに追加し、自分の SDK 資格情報を ApplicationSettings.cs に追加)。
 
次の記事では、Epic Online Services にある、プラットフォームに依存しないバックエンド サービスであるゲーム サービスの使用を開始します。まず Connect インターフェースについて説明します。これは多くの ID プロバイダの一つによる、これらのサービスに対するユーザー認証に使用されます。
シリーズ リファレンス にこのシリーズの完全な記事のリストがあります。この記事に対する質問やフィードバックがある場合は、下のボタンで、コミュニティ フォーラムに移動し、お知らせください。

    デベロッパーみなさんの成功が、Epic の成功です

    Epic では、オープンで調和のとれたゲーム コミュニティを実現することは可能であると信じています。オンライン サービスをすべての人に無料で提供することにより、より多くのデベロッパーがそれぞれのプレイヤー コミュニティにサービスを提供できるようにすることが目標です。