플레이어 현재상태 가져오기 및 설정하기

에픽게임즈 테크니컬 어카운트 매니저 Rajen Kishna |
2021년 10월 12일
프로젝트에 에픽 온라인 서비스를 사용할 때의 다양한 측면을 알아보는 정규 시리즈에 오신 것을 환영합니다. 지난 연재글에서는 인증 인터페이스로 로그인 및 로그아웃 기능을 구현하는 방법을 다뤘으며, 이번에는 현재상태 정보를 가져오고 설정하는 방법을 알아보겠습니다. 오늘 다룰 주제는 다음과 같습니다.
 

현재상태 인터페이스의 사용 시기

현재상태 인터페이스(Presence Interface)는 아래와 같은 두 가지 용도로 사용됩니다.
 
  • 인증된 플레이어의 (풍부한) 현재상태 정보 설정
    • 현재상태 정보 설정과 더불어, 플레이어의 친구에게 임의의 키/값 쌍 데이터를 전달할 때도 사용할 수 있습니다. 여기에는 게임에 참가할 수 없는 이유(예: 초대 전용 파티), 플레이어가 사용 중인 캐릭터 스킨 등 게임에서 추가 정보를 표시하기 위한 모든 데이터가 포함됩니다. 이는 실시간 데이터가 아니므로, 시간이 중요하지 않은 데이터를 전달할 때 사용하는 것이 좋습니다.
  • 플레이어 친구의 현재상태 정보 쿼리
    • SDK는 게임에서 요청하는 권한에 동의한 플레이어 친구만 반환하므로, 로그인해서 게임에 동의했거나 에픽게임즈 스토어에서 게임을 구매하면서 동의한 친구만 쿼리할 수 있습니다.

두 시나리오를 모두 다루고 싶지만, 다음 연재글에서 친구 인터페이스(Friends Interface)를 다루면서 현재상태 쿼리의 실제 사용 사례를 확인할 것입니다.

애플리케이션에 현재상태 권한 추가하기

플레이어가 현재상태 API 사용에 동의할 수 있게 하려면 에픽 계정 서비스(EAS) 애플리케이션에서 온라인 현재상태(현재상태) 권한을 추가해야 합니다.
 
  1. https://dev.epicgames.com/portal/에서 개발자 포털에 로그인합니다.
  2. 좌측 메뉴에서 제품을 찾아 ‘에픽 계정 서비스’를 클릭합니다.
  3. 사용 중인 애플리케이션 옆의 ‘환경설정’ 버튼을 클릭합니다.
  4. 우측 상단의 '권한’ 탭을 클릭합니다.
  5. ‘Online Presence(presence)’ 권한을 활성화합니다. 좌측 프리뷰가 업데이트되어 현재상태 정보 동의가 추가될 것입니다.
  6. ‘SAVE’를 눌러 확인합니다.

Developer Portal Online Presence Permission

EAS 애플리케이션에 온라인 현재상태 권한 추가하기


마지막으로 Visual Studio 솔루션에서 'ApplicationSettings.cs'를 열고 현재상태 'ScopeFlag'를 추가합니다.

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


앱을 실행하고 인증 로그인을 트리거하면, 브라우저에 업데이트된 동의 화면이 나타나며 인증을 성공적으로 끝낼 수 있습니다.

플레이어 현재상태 쿼리하기

앞서 언급했듯이 쿼리 기능을 더 일반화하여 구현할 예정이므로, 다음 연재글에서 친구 현재상태를 쿼리할 때 다시 사용할 수 있습니다. 인증을 제외하면 처음 구현하는 인터페이스이므로, UI에 앞으로 추가할 서비스에 대한 탭을 만드는 몇 가지 절차를 따로 진행하겠습니다.
 
  1. 먼저 프로젝트 루트에 'Views'라는 폴더를 하나 만듭니다.
  2. 이 폴더에 'PresenceView'라는 UserControl을 새로 만듭니다
  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>

 
  • 디자인 뷰에서 볼 수 있듯이, 이렇게 하면 쿼리할 현재상태 정보를 표시하는 간단한 UI를 추가할 수 있으며 대응하는 수정 버튼으로 상태도 변경할 수 있습니다.
 
  1. 'PresenceView.xaml.cs'를 열고 DataContext를 정의합니다.

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

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

 
  1. 'PresenceViewModel.cs' 클래스를 'ViewModels' 폴더에 추가합니다.

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()는 인증된 플레이어가 아닌 사용자의 현재상태를 얻을 때 사용합니다. 인증 인터페이스 사용 때와 동일한 패턴을 따릅니다. 옵션 클래스를 인스턴스화하고, 현재상태 인터페이스에 QueryPresence를 호출해 콜백 메서드로 전달한 뒤, 결과를 검증합니다.
    • Copy()는 QueryPresence가 로컬로 결과를 캐싱하고 인증된 플레이어를 인증 인터페이스 로그인 호출에 성공한 뒤 현재상태를 자동으로 이용할 수 있게 되면 사용합니다. 캐시에서 실제 현재상태 정보를 얻기 위해 CopyPresence를 사용합니다.
  • 이 경우에는 로컬 사용자와 타깃 사용자가 동일하므로, 현재상태 정보로 PresenceViewModel를 채워 UI에 정보가 표시되도록 합니다.
 
  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 enum에 포함된 모든 옵션으로 'StatusOptions'도 인스턴스화합니다.
 
  1. 사용자가 로그인한 상태에서만 현재상태 쿼리 버튼이 활성화되도록 'ViewModelLocator RaiseAuthCanExecuteChanged()' 메서드에 다음과 같은 줄을 추가합니다.

Presence.PresenceQuery.RaiseCanExecuteChanged();
 
  1. 마지막으로 'PresenceView'를 'MainWindow TabControl'에 추가합니다.

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


이제 앱을 실행하고, 인증하고, 브라우저에서 업데이트된 권한에 동의하면 현재상태 쿼리 버튼을 사용해 해당 시점의 현재상태 정보 및 관련 정보를 캐시에서 얻을 수 있습니다.  
App MainWindow Queried Presence UI
인증된 플레이어의 현재상태 정보

프로덕션 게임에서는 QueryPresence를 다시 호출하지 않아도 현재상태 정보가 변경되면 알림을 받을 수 있도록 항상 AddNotifyOnPresenceChanged를 구현해야 합니다.

현재상태 정보 설정하기

이제 드롭다운에서 원하는 항목을 선택해 현재상태 정보를 설정할 수 있습니다.  
  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만 사용하지만, 현재상태와 관련된 Rich Text를 변경할 수도 있습니다(예를 들어 플레이어가 플레이하고 있는 레벨 표시). 앞서 언급한 대로 커스텀 현재상태 데이터를 추가, 대체, 삭제하여 임의의 키/값 쌍 데이터를 전달할 수도 있습니다.
  • 마지막으로 현재상태에 JoinInfo를 설정하여 EOS 오버레이에서 게임으로 정보를 전달하면, 게임 로직에 따라 친구가 게임에 참가할 수 있습니다. 이때 오버레이 알림 구독을 하면, 플레이어가 참가 요청을 수락했을 때 등의 정보를 받을 수 있습니다.
 
  1. 또한 현재상태 수정 명령의 CanExecuteChanged 행동에 트리거를 추가해 플레이어 현재상태 초기 쿼리 이후에만 이용 가능하도록 합니다.

다음 코드를,

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# 솔루션 설정하기 섹션 5~9단계를 따라 코드를 얻어 솔루션에 SDK를 추가하고, ApplicationSettings.cs를 편집해 SDK 크리덴셜을 추가합니다.
궁금한 점이나 피드백이 있다면 언제든 말씀해 주세요. 다음 연재글에서는 에픽 친구와 친구의 상태를 쿼리하는 방법을 알아보겠습니다. 시리즈의 다른 연재글은 시리즈 참조를 확인하시기 바랍니다.

    여러분의 성공이 곧 에픽의 성공입니다

    에픽은 통합되고 열려 있는 게임 커뮤니티를 지향합니다. 에픽은 이러한 온라인 서비스를 모두에게 무료로 제공함으로써, 더 많은 개발자들이 자체 플레이어 커뮤니티에 서비스를 지원할 수 있도록 하는 것을 목표로 합니다.