에픽 온라인 서비스로 플레이어 통계 관리하기

에픽게임즈 테크니컬 어카운트 매니저 Rajen Kishna |
2021년 12월 14일
이번 '시작하기' 시리즈의 다음 세 연재글에서는 서로 밀접하게 관련된 통계, 리더보드, 업적 인터페이스에 대해 자세히 알아볼 것입니다. 먼저 통계부터 살펴보겠습니다. 이 인터페이스는 획득한 아이템 수, 플레이어의 가장 빠른 레벨 완료 시간, 총 승패 수 등 플레이어 통계를 관리하는 데 사용됩니다. 이 연재글에서 알아볼 내용은 다음과 같습니다.

통계, 리더보드, 업적

본격적으로 통계에 대해 알아보기에 앞서 통계, 리더보드, 업적의 관계부터 설명하겠습니다. 통계는 플레이어 통계를 추적하는 기반 시스템을 제공합니다. 이는 리더보드를 통해 플레이어의 순위를 매기거나 자동으로 업적을 잠금 해제하는 데 활용할 수 있습니다. 또한, 통계는 에픽 온라인 서비스의 다른 기능 없이 단독으로 사용되기도 합니다.

업적 인터페이스는 통계에 기반한 자동 잠금 해제 대신 수동으로 업적 잠금 해제를 트리거하는 API를 제공합니다. 반면, 리더보드는 기반 통계와 연결이 되어야만 존재할 수 있습니다. 이러한 연결에 대한 자세한 내용은 다음 두 연재글에서 알아보겠습니다.

클라이언트 정책 변경하기

통계를 사용하려면 우선 다음과 같이 클라이언트 정책에 액션을 추가해야 합니다.
 
  1. https://dev.epicgames.com/portal/에서 개발자 포털에 로그인합니다..
  2. 왼쪽 메뉴에서 해당 제품의 '제품 세팅'으로 이동한 후 제품 세팅 화면의 '클라이언트' 탭을 클릭합니다.
  3. 사용 중인 클라이언트 정책 옆에 있는 점 세 개 버튼을 클릭하고 '세부사항'을 클릭합니다.
  4. 아래의 '기능'으로 스크롤하여 'Stats' 옆의 토글 버튼을 클릭합니다.
  5. 'findStatsForLocalUser' 및 'ingestForLocalUser' 액션 옆의 박스를 체크합니다.
    • 여기서 중요한 것은 필요한 최소한의 액션만 사용해야 서비스 호출이 남용되지 않는다는 점입니다.
  6. '저장 & 종료'를 클릭하여 확인합니다.
 
Developer Portal Client Policy Stats
통계 클라이언트 정책에서 허용하는 기능 및 작업

개발자 포털에서 통계 생성하기

개발자 포털에서 통계를 생성할 수 있으며 통계는 다음 네 가지 집계 유형 중 하나로 정의됩니다.
 
  • 합계(SUM)—수집된 통계의 총합
  • 최신(LATEST)—수집된 통계 중 마지막 값
    • 이 유형은 업적 자동 잠금 해제에 사용할 수 없는 유일한 유형입니다.
  • 최소(MIN)—수집된 통계 중 가장 낮은 정수
  • 최대(MAX)—수집된 통계 중 가장 높은 정수

모든 통계 집계 유형은 단일 32비트 정수로 수집되는 통계를 사용합니다.

각 집계 유형별로 하나씩 통계를 정의하면서 샘플 앱에서의 행동을 살펴보겠습니다.
  1. 왼쪽 메뉴에서 제품을 선택한 후 '게임 서비스' > '통계'로 이동합니다.
  2. 여기서 제품에 구성한 각 디플로이의 기존 통계를 볼 수 있습니다. 샘플 앱에서 사용 중인 디플로이를 선택하고 화면 오른쪽 상단의 파란색 '새 통계' 버튼을 클릭합니다.
  3. 'SumStat'이라는 이름을 입력하고 SUM 집계 유형을 선택합니다. 파란색 '생성' 버튼을 클릭하여 생성을 마칩니다.
  4. 'LATEST', 'MIN', 'MAX' 집계 유형에도 3단계를 반복하고 각 통계를 'LatestStat', 'MinStat', 'MaxStat'으로 명명합니다.

Developer Portal Stats
개발자 포털의 통계

오른쪽 상단 검색 버튼 옆에 연결 아이콘이 있다는 점을 알아두세요. 이 아이콘을 클릭하면 현재 통계가 어느 리더보드 및 업적과 연결되어 있는지 확인할 수 있습니다. 현재는 여기에 아무것도 없지만 향후 리더보드 및 업적 연재글에서 이 아이콘을 다시 살펴보겠습니다.

마지막으로 알아둘 것은 파란색 '새 통계' 버튼 옆의 '플레이어 통계 리셋' 버튼입니다. 이 버튼을 누르면 플레이어를 PUID로 검색하여 통계 정의 자체에 영향을 미치지 않고 해당 플레이어의 개별 통계를 리셋할 수 있는 플라이아웃 메뉴가 표시됩니다.

마우스 클릭으로 통계 시뮬레이션하기

샘플 앱에서 통계를 시뮬레이션하기 위해 클릭 카운터로 버튼을 설정하겠습니다.
 
  1. Views 폴더에서 StatsView라는 새로운 사용자 컨트롤을 생성합니다.

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

    <StackPanel Grid.Column="1" Grid.Row="0" Grid.RowSpan="2">
        <Button Width="100" Height="23" Margin="2" Content="Ingest stats" Command="{Binding StatsIngest}" />
        <Button Width="100" Height="23" Margin="2" Content="Query stats" Command="{Binding StatsQuery}" />
    </StackPanel>

    <StackPanel Grid.Column="0" Grid.Row="0">
        <StackPanel Orientation="Horizontal">
            <TextBlock Text="Clicks:" Margin="2" />
            <TextBlock Text="{Binding Clicks}" Margin="2" />
        </StackPanel>

        <Button HorizontalAlignment="Left" Width="100" Height="23" Margin="2" Content="Add click" Command="{Binding StatsClick}" />
    </StackPanel>
</Grid>

 
  1. StatsView.xaml.cs를 열어 ViewModel에 어태치합니다.

public partial class StatsView : UserControl
{
    public StatsViewModel ViewModel { get { return ViewModelLocator.Stats; } }

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

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

public class StatsViewModel : BindableBase
{
    private int _clicks;
    public int Clicks
    {
        get { return _clicks; }
        set { SetProperty(ref _clicks, value); }
    }
}

 
  1. ViewModelLocator.cs에 StatsViewModel 레퍼런스를 추가합니다.

private static StatsViewModel _statsViewModel;
public static StatsViewModel Stats
{
    get { return _statsViewModel ??= new StatsViewModel(); }
}

 
  1. StatsClickCommand.cs 클래스를 Commands 폴더에 추가합니다.

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

    public override void Execute(object parameter)
    {
        ViewModelLocator.Stats.Clicks++;
    }
}

 
  1. StatsViewModel.cs를 열어 명령을 선언하고 인스턴스화합니다.

public StatsClickCommand StatsClick { get; set; }

public StatsViewModel()
{
    StatsClick = new StatsClickCommand();
}

 
  1. 연결 인터페이스를 통해 로그인한 뒤에만 통계 UI 함수 기능을 호출할 수 있도록 다음 명령줄을 ViewModelLocator.cs의 RaiseConnectCanExecuteChanged() 메서드에 추가합니다.

Stats.StatsClick.RaiseCanExecuteChanged();
 
  1. 마지막으로 StatsView를 MainWindow.xaml의 TabControl에 추가합니다.

<TabItem x:Name="Stats" Header="Stats">
    <views:StatsView />
</TabItem>


이제 인증 및 연결을 통해 앱을 실행하고 인증할 때 통계 탭에서 버튼을 사용하여 클릭 카운터를 높일 수 있습니다. 다음에 이를 사용하여 통계를 수집할 것입니다.

App Stats Click
앱 UI에서 클릭 시뮬레이션하기

통계 수집하기

방금 만든 클릭 카운터를 사용하여 앞서 만든 통계 4개를 모두 동시에 수집할 것입니다. 이렇게 하면 각 집계 유형 간의 행동 차이가 분명해지고 통계 수집 호출이 간소화됩니다. 
 
  1. StatsService.cs 클래스를 Services 폴더에 추가하여 수집 로직을 보관합니다.

public static class StatsService
{
    public static void Ingest(int count)
    {
        var ingestStatOptions = new IngestStatOptions()
        {
            LocalUserId = ProductUserId.FromString(ViewModelLocator.Main.ProductUserId),
            TargetUserId = ProductUserId.FromString(ViewModelLocator.Main.ProductUserId),
            Stats = new IngestData[]
            {
                new IngestData() { StatName = "SumStat", IngestAmount = count },
                new IngestData() { StatName = "LatestStat", IngestAmount = count },
                new IngestData() { StatName = "MinStat", IngestAmount = count },
                new IngestData() { StatName = "MaxStat", IngestAmount = count }
            }
        };

        ViewModelLocator.Main.StatusBarText = $"Ingesting stats (count: <{count}>)...";

        App.Settings.PlatformInterface.GetStatsInterface()
.IngestStat(ingestStatOptions, null, (IngestStatCompleteCallbackInfo ingestStatCompleteCallbackInfo) =>
        {
            Debug.WriteLine($"IngestStat {ingestStatCompleteCallbackInfo.ResultCode}");

            if (ingestStatCompleteCallbackInfo.ResultCode == Result.Success)
            {
                ViewModelLocator.Stats.Clicks = 0;
            }
            ViewModelLocator.Main.StatusBarText = string.Empty;
        });
    }
}

 
  • 이 호출은 아주 간단합니다. PUID와 수집하고자 하는 통계 배열로 Stats.IngestStatOptions를 인스턴스화하려는 것입니다.
  • 그런 다음 Stats.IngestStat을 호출하여 옵션 구조를 전달하고 수집을 마칩니다.
 
  1. StatsIngestCommand.cs 클래스를 Commands 폴더에 추가합니다.

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

    public override void Execute(object parameter)
    {
        StatsService.Ingest(ViewModelLocator.Stats.Clicks);
    }
}

 
  1. StatsViewModel.cs를 열어 명령을 선언하고 인스턴스화합니다.

public StatsClickCommand StatsClick { get; set; }
public StatsIngestCommand StatsIngest { get; set; }

public StatsViewModel()
{
    StatsClick = new StatsClickCommand();
    StatsIngest = new StatsIngestCommand();
}

 
  1. 다음 명령줄을 ViewModelLocator.cs의 RaiseConnectCanExecuteChanged() 메서드에 추가합니다.

Stats.StatsIngest.RaiseCanExecuteChanged();

이제 앱을 다시 실행하고 인증한 뒤 클릭 카운터를 약간 높입니다. 그런 다음, Ingest stats 버튼을 클릭하여 값을 백엔드에 보냅니다. 이를 한 번 더 반복하고(몇 번 클릭하고 수집) 개발자 포털로 이동하여 결과를 확인합니다.
 
  1. 앱의 UI에서 ProductUserId를 클립보드로 복사합니다.
  2. 왼쪽 메뉴에서 해당 제품을 선택한 후 '게임 서비스' > '통계'로 이동합니다.
  3. '플레이어 통계 리셋' 버튼을 클릭하고 플라이아웃 메뉴에서 방금 복사한 PUID를 검색창에 붙여넣은 뒤 '검색' 버튼을 클릭합니다.

이 사용자의 통계 4개와 해당 집계 행동이 표시됩니다.
 
Developer Portal Stats Reset Player Stats
개발자 포털에서 플레이어 통계 UI 리셋하기

통계 쿼리하기

마지막으로 할 작업은 앱에서 이 통계를 쿼리하는 것입니다. 그러면 개발자 포털을 통하지 않고도 직접 볼 수 있게 됩니다.
 
  1. StatsView.xaml의 다음 ListView를 메인 Grid 엘리먼트의 마지막 자손 노드로 추가합니다.

<ListView x:Name="StatsListView" Grid.Row="1" Margin="2" ItemsSource="{Binding Stats}">
    <ListView.View>
        <GridView>
            <GridViewColumn Header="Name" Width="200" DisplayMemberBinding="{Binding Name}">
                <GridViewColumn.HeaderContainerStyle>
                    <Style TargetType="{x:Type GridViewColumnHeader}">
                        <Setter Property="HorizontalContentAlignment" Value="Left" />
                    </Style>
                </GridViewColumn.HeaderContainerStyle>
            </GridViewColumn>
            <GridViewColumn Header="StartTime" Width="150" DisplayMemberBinding="{Binding StartTime}">
                <GridViewColumn.HeaderContainerStyle>
                    <Style TargetType="{x:Type GridViewColumnHeader}">
                        <Setter Property="HorizontalContentAlignment" Value="Left" />
                    </Style>
                </GridViewColumn.HeaderContainerStyle>
            </GridViewColumn>
            <GridViewColumn Header="EndTime" Width="150" DisplayMemberBinding="{Binding EndTime}">
                <GridViewColumn.HeaderContainerStyle>
                    <Style TargetType="{x:Type GridViewColumnHeader}">
                        <Setter Property="HorizontalContentAlignment" Value="Left" />
                    </Style>
                </GridViewColumn.HeaderContainerStyle>
            </GridViewColumn>
            <GridViewColumn Header="Value" Width="100" DisplayMemberBinding="{Binding Value}">
                <GridViewColumn.HeaderContainerStyle>
                    <Style TargetType="{x:Type GridViewColumnHeader}">
                        <Setter Property="HorizontalContentAlignment" Value="Left" />
                    </Style>
                </GridViewColumn.HeaderContainerStyle>
            </GridViewColumn>
        </GridView>
    </ListView.View>
</ListView>

 
  1. StatsViewModel.cs를 열고 다음 멤버를 추가합니다.

private ObservableCollection<Stat> _stats;
public ObservableCollection<Stat> Stats
{
    get { return _stats; }
    set { SetProperty(ref _stats, value); }
}

 
  1. StatsService.cs를 열고 다음 메서드를 추가합니다.

public static void Query()
{
    var queryStatsOptions = new QueryStatsOptions()
    {
        LocalUserId = ProductUserId.FromString(ViewModelLocator.Main.ProductUserId),
        TargetUserId = ProductUserId.FromString(ViewModelLocator.Main.ProductUserId)
    };

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

    App.Settings.PlatformInterface.GetStatsInterface()
.QueryStats(queryStatsOptions, null, (OnQueryStatsCompleteCallbackInfo onQueryStatsCompleteCallbackInfo) =>
    {
        Debug.WriteLine($"QueryStats {onQueryStatsCompleteCallbackInfo.ResultCode}");

        if (onQueryStatsCompleteCallbackInfo.ResultCode == Result.Success)
        {
            var getStatCountOptions = new GetStatCountOptions()
            {
                TargetUserId = ProductUserId.FromString(ViewModelLocator.Main.ProductUserId)
            };
            var statCount = App.Settings.PlatformInterface
.GetStatsInterface().GetStatsCount(getStatCountOptions);

            for (uint i = 0; i < statCount; i++)
            {
                var copyStatByIndexOptions = new CopyStatByIndexOptions()
                {
                    StatIndex = i,
                    TargetUserId = ProductUserId.FromString(ViewModelLocator.Main.ProductUserId)
                };
                var result = App.Settings.PlatformInterface.GetStatsInterface()
.CopyStatByIndex(copyStatByIndexOptions, out var stat);

                if (result == Result.Success)
                {
                    ViewModelLocator.Stats.Stats.Add(stat);
                }
            }
        }

        ViewModelLocator.Main.StatusBarText = string.Empty;
    });
}
  • Stats.QueryStats를 사용하여 에픽 온라인 서비스의 통계로 캐시를 채웁니다.
    • Stats.QueryStatOptions에 통계 이름 배열을 지정하여 특정 통계만 쿼리할 수도 있습니다.
  • 그런 다음, Stats.GetStatsCount를 사용하여 얼마나 많은 통계를 반복작업했는지 확인하고 Stats.CopyStatByIndex를 사용하여 캐시에서 통계 데이터를 얻습니다.
 
  1. StatsQueryCommand.cs 클래스를 Commands 폴더에 추가합니다.

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

    public override void Execute(object parameter)
    {
        ViewModelLocator.Stats.Stats = new ObservableCollection<Stat>();
        StatsService.Query();
    }
}

 
  1. StatsViewModel.cs를 열어 명령을 선언하고 인스턴스화합니다.

public StatsClickCommand StatsClick { get; set; }
public StatsIngestCommand StatsIngest { get; set; }
public StatsQueryCommand StatsQuery { get; set; }

public StatsViewModel()
{
    StatsClick = new StatsClickCommand();
    StatsIngest = new StatsIngestCommand();
    StatsQuery = new StatsQueryCommand();
}

 
  1. 다음 명령줄을 ViewModelLocator.cs의 RaiseConnectCanExecuteChanged() 메서드에 추가합니다.

Stats.StatsQuery.RaiseCanExecuteChanged();

이제 앱을 다시 실행하고 쿼리 통계 버튼으로 현재 플레이어의 통계를 UI에서 바로 확인할 수 있습니다.
 
App Stats Query
앱 UI의 쿼리 통계

사용 제한

모든 사용자에게 안정성과 가용성을 보장하기 위해 통계에는 사용 제한이 있습니다. 이 연재글 작성 당시의 사용 제한(최신 정보는 이 문서 참조)은 다음과 같습니다.
  • 디플로이당 통계 정의 총 500개
  • 수집 호출당 플레이어 통계 최대 3000개
  • 통계 이름 길이 256자

또한, 클라이언트 호출과 서버 호출에는 각각 API 호출에 대한 사용자별 또는 디플로이별 속도 제한이 있습니다. 이에 대한 정보는 이 문서에서 확인할 수 있습니다.

코드 다운로드

아래에서 이 연재글에 쓰인 코드를 다운로드하세요. 다운로드한 코드를 GitHub 저장소의 사용 지침에 따라 설정하세요.
이제 플레이어 통계를 관리하는 방법을 알았으니, 다음으로는 EOS 리더보드를 사용하여 이 점수의 순위를 매기는 방법을 자세히 알아보겠습니다.

이 연재글 시리즈의 전체 목록은 시리즈 참조를 확인하세요. 피드백이나 질문은 커뮤니티 포럼을 방문해 주세요.

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

    에픽은 개방되고 통합된 게이밍 커뮤니티를 지향합니다.
    에픽 온라인 서비스를 누구에게나 무료로 제공함으로써 더 많은 개발자들이 플레이어 커뮤니티에 더 나은 서비스를 제공할 수 있도록 지원하고자 합니다.