클라우드에서 게임 전용 데이터 가져오기

에픽게임즈 테크니컬 어카운트 매니저 Rajen Kishna |
2021년 11월 30일
이전 연재글에서 구성한 게임 서비스 인증을 통해 클라우드에서 게임 전용 데이터를 안전하게 가져오기 위한 타이틀 스토리지 사용 준비를 마쳤습니다.이 연재글에서는 다음 사항에 대해 알아보겠습니다.

타이틀 스토리지 vs. 플레이어 데이터 스토리지

에픽 온라인 서비스(EOS)는 데이터에 대해타이틀 스토리지플레이어 데이터 스토리지라는 두 가지의 암호화된 클라우드 스토리지를 제공합니다.암호화와 암호화 해제는 사용자가 지정한 암호화 키를 사용하여 이루어지기 때문에 클라우드에서는 파일을 절대 읽을 수 없습니다.타이틀 스토리지는 모든 플레이어가 액세스할 수 있는 클라우드에서 데이터를 한 곳에 저장하기 위해 사용할 수 있습니다.타이틀 스토리지 데이터는 개발자 포털을 통해 저장되지만 EOS SDK API를 통해 가져올 수 있습니다.예를 들어, 게임을 업데이트하지 않고 플레이어에게 환경설정 데이터를 제공하려는 경우 유용합니다.

플레이어 데이터 스토리지는 이름에서도 알 수 있듯이 각 플레이어별로 지정되며, 플레이어가 로그인한 디바이스에서 API를 통해 저장하고 가져올 수 있습니다. 예를 들어, 클라우드 저장 방식을 구현하기 위해 사용할 수 있으며 필요한 경우 여러 플랫폼에 걸쳐 이를 구현할 수도 있습니다.다음 연재글에서는 플레이어 데이터 스토리지를 집중적으로 다룰 것입니다.

이용 제한

모든 사용자에게 안정적이고 신뢰할 수 있는 생태계를 제공하기 위해 일반적으로 에픽 온라인 서비스에는 특정한 서비스 이용 제한이 있습니다.일부 서비스에는 추가적인 이용 제한이 있으며 타이틀 스토리지는 그러한 서비스 중 하나입니다.이 연재글을 작성하는 시점에는 다음과 같은 제한 사항이 존재하지만, 이와 관련된 최신 정보를 알아보려면 이 문서를 참고해 주세요.
  • 단일 디플로이에서 모든 파일의 총 파일 크기 10GB
  • 단일 디플로이에서 총 파일 수 100개

클라이언트 정책 변경하기

샘플 앱에서 타이틀 스토리지 인터페이스 API를 호출할 수 있으려면 이 서비스를 액세스할 수 있도록 클라이언트 정책을 조정해야 합니다.
  1. https://dev.epicgames.com/portal/에서 개발자 포털에 로그인합니다.
  2. 왼쪽 메뉴에서 '제품 세팅'으로 이동하여 '제품 세팅' 화면에서 '클라이언트' 탭을 클릭합니다.
  3. 사용할 클라이언트 정책 옆에 있는 점 세 개 모양의 아이콘을 클릭하고 '세부사항'을 클릭합니다.
Developer Portal Client Policy Details
클라이언트 정책 세부사항
  1. 스크롤을 내려 '기능'으로 이동하여 '타이틀 스토리지' 옆에 있는 토글 버튼을 클릭합니다.
    • 클라이언트가 수행할 수 있는 동작에 대해 세부적으로 액세스할 수 있도록 개별적으로 체크 표시할 수 있는 허용되는 동작의 목록이 오른쪽에 표시됩니다.
  2. 'list' 및 'access' 기능을 모두 구현할 것이기 때문에 두 동작 옆에 있는 각각의 상자를 체크합니다.
  3. '저장 및 종료'를 클릭하여 확인합니다.
Developer Portal Client Policy Title Storage
타이틀 스토리지 클라이언트 정책에서 허용되는 기능 및 작업

개발자 포털에서 파일 구성하기

다음으로 수행할 단계는 개발자 포털에서 파일을 구성하는 것입니다.앞서 언급했듯이, 타이틀 스토리지 API는 데이터를 쿼리하고 가져오기 위해서만 사용하기 때문에 파일을 업로드하고 관리하는 것은 개발자 포털을 통해 이루어집니다.
  1. 왼쪽 메뉴에서 해당 제품 > '게임 서비스' > '타이틀 스토리지'로 이동합니다.
  2. 여기에서는 다음과 같은 사항을 확인할 수 있습니다.
    • 이전에 추가한 파일 및 파일 수.현재 화면에서는 '찾은 파일이 없습니다.'라고 표시될 가능성이 높습니다.
    • 10GB 할당량 중 사용량.
    • 현재 표시된 디플로이.여기서는 'Live 샌드박스에 Release'가 디폴트이기 때문에 이것만 확인할 가능성이 높습니다. 하지만 디플로이를 더 추가하면 여기에서 추가한 디플로이를 선택할 수 있습니다.
    • 새 파일을 추가하기 위한 버튼
    • 문서에 대한 링크
Developer Portal Title Storage
개발자 포털에서의 타이틀 스토리지 세팅
  1. 새 파일을 추가하려면 '새 파일 추가' 버튼을 클릭합니다.

잠시 후 암호화 키를 입력할 수 있는 화면이 표시됩니다.이 암호화 키는 숫자 0~9와 문자 A~F를 포함하는 64자로 정의된 256비트 16진수 문자열입니다.코드에 이 암호화 키를 사용할 것이기 때문에 SDK를 통해 암호화와 암호화 해제를 수행할 수 있습니다.에픽게임즈는 이 암호화 키를 저장하지 않으며, 이 키는 파일을 암호화 및 암호화 해제하기 위해 필요하기 때문에 꼭 기억해 둬야 합니다.편의를 위해 .txt 파일로 암호화 키를 저장할 수 있는 옵션이 제공됩니다.
  1. 설명을 위해 여기에서는 다음과 같은 암호화 키를 사용합니다. 하지만 프로덕션 환경에서 SDK를 사용할 때는 안전한 키를 생성하세요.1111111111111111111111111111111111111111111111111111111111111111
  2. 실제 파일의 세부 정보로 이동하기 위해 '다음'을 클릭합니다.

여기서는 파일에 최대 64자로 구성된 이름을 지정하고, API를 통해 파일을 쿼리할 때 유용한 '태그'를 추가하고, 실제 파일과 지정하려는 오버라이드를 업로드할 수 있습니다.

태그는 여러 파일을 한번에 쿼리하는 데 사용되며, 게임에 특정 파일 이름을 하드 코딩하지 않게 해줍니다.타이틀 스토리지에서는 파일에 여러 태그를 추가할 수 있기 때문에 사용할 수 있는 모든 파일을 쿼리하려는 경우 공통 태그로 모든 파일을 태그하고 해당 태그를 쿼리하기만 하면 됩니다.

오버라이드는 특정 PUID, ID 제공자 ID(예: 에픽 계정 ID), 에픽 온라인 서비스 클라이언트를 기반으로 이 파일의 특정 베리언트를 사용하려는 경우를 가리킵니다.오버라이드를 사용하면 별도의 파일을 유지하거나 코드/환경설정을 다르게 할 필요 없이 게임 클라이언트와 신뢰할 수 있는 서버에서 사용할 특정 파일을 얻을 수 있습니다.또한 Android와 PC에 서로 다른 에픽 온라인 서비스 클라이언트를 사용하는 경우 각 버전의 파일을 얻을 수 있습니다.
  1. 샘플 앱을 위한 파일을 생성하는 것이므로 다음과 같이 입력합니다.
    • 파일명:test_file
    • 태그 추가:test_tag(입력 후 '추가' 클릭)
    • 파일 업로드:어떠한 파일도 가능하며 몇 개의 단어를 포함한 간단한 .txt 파일을 생성할 수도 있음
    • 오버라이드:변경하지 않고 그대로 유지
  2. '저장 후 닫기'를 클릭하여 제출합니다.
Developer Portal Title Storage Test File
타이틀 스토리지에 새 파일 추가
  1. 마지막으로, 6단계와 7단계를 한 번 더 반복합니다.
    • 암호화 키:1111111111111111111111111111111111111111111111111111111111111111
    • 파일명:another_test
    • 태그:test_tag
    • 태그 추가:second_tag
    • 파일 업로드:모든 파일 가능
    • 오버라이드:변경하지 않고 그대로 유지

서로 다른 태그로 쿼리 로직을 검사하기 위해 두 파일을 모두 사용할 것입니다.이제 타이틀 스토리지 세팅 화면에 두 파일이 표시되는 것을 확인할 수 있습니다.
Developer Portal Title Storage Two Files Added
타이틀 스토리지에 추가된 두 개의 파일

SDK 초기화, EncryptionKey 및 CacheDirectory

개발자 포털에서 정책과 파일을 구성했으므로 이제는 코드 구현을 시작할 수 있습니다.먼저, 앞 섹션에서 정의한 암호화 키를 추가하고 SDK 초기화 코드에 디렉터리 위치를 캐시해야 합니다. 이렇게 해야 파일을 암호화 및 암호화 해제하는 방법을 알 수 있고 다운로드를 캐시하기 위해 사용할 수 있는 폴더가 어떤 것인지 알 수 있습니다.

타이틀 스토리지 및 플레이어 데이터 스토리지는 지정된 캐시 디렉터리의 별도 하위 폴더에 캐시되기 때문에 SDK 초기화 중에 하나의 디렉터리를 설정하기만 하면 됩니다.캐시에 동일한 MD5 해시를 가진 파일이 이미 존재하는 경우에는 후속 읽기 요청 시 클라우드에서 파일을 다시 다운로드하지 않고 캐시에서 파일 콘텐츠를 다시 가져옵니다.
  1. ApplicationSettings.cs를 열고 다음의 두 구성 요소를 추가합니다.캐시 디렉터리로 디폴트 Temp 디렉터리를 사용할 것이지만 해당 애플리케이션이 읽기 및 쓰기 권한을 가지고 있다면 어떤 폴더든 가능합니다.

public string EncryptionKey = "1111111111111111111111111111111111111111111111111111111111111111";
public string CacheDirectory = Path.GetTempPath();

  1. MainWindow.xaml.cs를 열고 옵션 변수 초기화 부분의 IsServer = false 뒤에 다음을 추가합니다.

,
EncryptionKey = App.Settings.EncryptionKey,
CacheDirectory = App.Settings.CacheDirectory


오류가 없는지 확인하기 위해 앱을 실행해 볼 수 있지만 아직 변경된 기능은 없습니다.

이름 또는 태그로 파일 쿼리하기

  1. Views 폴더에서 TitleStorageView라는 새로운 사용자 컨트롤을 생성합니다.

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

    <StackPanel Grid.Column="1">
        <TextBox x:Name="TitleStorageFileNameTextBox" Margin="2" TextChanged="TitleStorageFileNameTextBox_TextChanged" Text="{Binding TitleStorageFileName, UpdateSourceTrigger=PropertyChanged}" />
        <Button Width="100" Height="23" Margin="2" Content="Query file" Command="{Binding TitleStorageQueryFile}" />
        <TextBox x:Name="TitleStorageTagTextBox" Margin="2" TextChanged="TitleStorageTagTextBox_TextChanged" Text="{Binding TitleStorageTag, UpdateSourceTrigger=PropertyChanged}" />
        <Button Width="100" Height="23" Margin="2" Content="Query by tag" Command="{Binding TitleStorageQueryFileList}" />
        <Button Width="100" Height="23" Margin="2" Content="Download file" Command="{Binding TitleStorageReadFile}" />
    </StackPanel>

    <ListView x:Name="TitleStorageFilesListView" Grid.Column="0" Margin="2" ItemsSource="{Binding TitleStorageFiles}" SelectedItem="{Binding SelectedTitleStorageFile}" SelectionChanged="TitleStorageFilesListView_SelectionChanged">
        <ListView.View>
            <GridView>
                <GridViewColumn Header="Filename" Width="150" DisplayMemberBinding="{Binding Filename}">
                    <GridViewColumn.HeaderContainerStyle>
                        <Style TargetType="{x:Type GridViewColumnHeader}">
                            <Setter Property="HorizontalContentAlignment" Value="Left" />
                        </Style>
                    </GridViewColumn.HeaderContainerStyle>
                </GridViewColumn>
                <GridViewColumn Header="MD5Hash" Width="250" DisplayMemberBinding="{Binding MD5Hash}">
                    <GridViewColumn.HeaderContainerStyle>
                        <Style TargetType="{x:Type GridViewColumnHeader}">
                            <Setter Property="HorizontalContentAlignment" Value="Left" />
                        </Style>
                    </GridViewColumn.HeaderContainerStyle>
                </GridViewColumn>
                <GridViewColumn Header="FileSizeBytes" Width="100" DisplayMemberBinding="{Binding FileSizeBytes}">
                    <GridViewColumn.HeaderContainerStyle>
                        <Style TargetType="{x:Type GridViewColumnHeader}">
                            <Setter Property="HorizontalContentAlignment" Value="Left" />
                        </Style>
                    </GridViewColumn.HeaderContainerStyle>
                </GridViewColumn>
                <GridViewColumn Header="UnencryptedDataSizeBytes" Width="150" DisplayMemberBinding="{Binding UnencryptedDataSizeBytes}">
                    <GridViewColumn.HeaderContainerStyle>
                        <Style TargetType="{x:Type GridViewColumnHeader}">
                            <Setter Property="HorizontalContentAlignment" Value="Left" />
                        </Style>
                    </GridViewColumn.HeaderContainerStyle>
                </GridViewColumn>
            </GridView>
        </ListView.View>
    </ListView>
</Grid>

  • 쿼리를 통해 얻은 파일을 표시하기 위해 ListView를 사용합니다.
  • 파일 이름 또는 태그로 쿼리하기 위해 버튼이 있는 두 개의 TextBox가 있습니다.
  • 마지막으로, ListView에서 선택한 실제 파일을 다운로드하기 위한 버튼이 있습니다.
  1. ViewModel을 어태치하기 위해 TitleStorageView.xaml.cs를 열고 다음과 같은 UI 이벤트 핸들러를 추가합니다.

public partial class TitleStorageView :UserControl
{
    public TitleStorageViewModel ViewModel { get { return ViewModelLocator.TitleStorage; } }

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

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

    private void TitleStorageFileNameTextBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        ViewModel.TitleStorageQueryFile.RaiseCanExecuteChanged();
    }

    private void TitleStorageTagTextBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        ViewModel.TitleStorageQueryFileList.RaiseCanExecuteChanged();
    }
}

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

public class TitleStorageViewModel :BindableBase
{
    private ObservableCollection<FileMetadata> _titleStorageFiles;
    public ObservableCollection<FileMetadata> TitleStorageFiles
    {
        get { return _titleStorageFiles; }
        set { SetProperty(ref _titleStorageFiles, value); }
    }

    private FileMetadata _selectedTitleStorageFile;
    public FileMetadata SelectedTitleStorageFile
    {
        get { return _selectedTitleStorageFile; }
        set { SetProperty(ref _selectedTitleStorageFile, value); }
    }

    private string _titleStorageFileName;
    public string TitleStorageFileName
    {
        get { return _titleStorageFileName; }
        set { SetProperty(ref _titleStorageFileName, value); }
    }

    private string _titleStorageTag;
    public string TitleStorageTag
    {
        get { return _titleStorageTag; }
        set { SetProperty(ref _titleStorageTag, value); }
    }
}

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

private static TitleStorageViewModel _titleStorage;
public static TitleStorageViewModel TitleStorage
{
    get { return _titleStorage ??= new TitleStorageViewModel(); }
}

  1. Add a TitleStorageService.cs class to the Services folder to hold the query logic:

public static class TitleStorageService
{
    public static void QueryFile(string fileName)
    {
        var queryFileOptions = new QueryFileOptions()
        {
            LocalUserId = ProductUserId.FromString(ViewModelLocator.Main.ProductUserId),
            Filename = fileName
        };

        ViewModelLocator.Main.StatusBarText = $"Querying title storage file <{queryFileOptions.Filename}>...";

        App.Settings.PlatformInterface.GetTitleStorageInterface()
.QueryFile(queryFileOptions, null, (QueryFileCallbackInfo queryFileCallbackInfo) =>
        {
            Debug.WriteLine($"QueryFile {queryFileCallbackInfo.ResultCode}");

            if (queryFileCallbackInfo.ResultCode == Result.Success)
            {
                var copyFileMetadataByFilenameOptions = new CopyFileMetadataByFilenameOptions()
                {
                    LocalUserId = ProductUserId.FromString(ViewModelLocator.Main.ProductUserId),
                    Filename = fileName
                };
                var result = App.Settings.PlatformInterface.GetTitleStorageInterface()
.CopyFileMetadataByFilename(copyFileMetadataByFilenameOptions, out var metadata);

                if (result == Result.Success)
                {
                    ViewModelLocator.TitleStorage.TitleStorageFiles.Add(metadata);
                }
            }

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

    public static void QueryFileList(string tag)
    {
        var queryFileListOptions = new QueryFileListOptions()
        {
            LocalUserId = ProductUserId.FromString(ViewModelLocator.Main.ProductUserId),
            ListOfTags = new string[] { tag }
        };

        ViewModelLocator.Main.StatusBarText = $"Querying title storage files by tag <{tag}>...";

        App.Settings.PlatformInterface.GetTitleStorageInterface()
.QueryFileList(queryFileListOptions, null, (QueryFileListCallbackInfo queryFileListCallbackInfo) =>
        {
            Debug.WriteLine($"QueryFileList {queryFileListCallbackInfo.ResultCode}");

            if (queryFileListCallbackInfo.ResultCode == Result.Success)
            {
                for (uint i = 0; i < queryFileListCallbackInfo.FileCount; i++)
                {
                    var copyFileMetadataAtIndexOptions = new CopyFileMetadataAtIndexOptions()
                    {
                        Index = i,
                        LocalUserId = ProductUserId.FromString(ViewModelLocator.Main.ProductUserId)
                    };
                    var result = App.Settings.PlatformInterface.GetTitleStorageInterface()
.CopyFileMetadataAtIndex(copyFileMetadataAtIndexOptions, out var metadata);

                    if (result == Result.Success)
                    {
                        ViewModelLocator.TitleStorage.TitleStorageFiles.Add(metadata);
                    }
                }
            }

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

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

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

    public override void Execute(object parameter)
    {
        ViewModelLocator.TitleStorage.TitleStorageFiles = new ObservableCollection<FileMetadata>();
        TitleStorageService.QueryFile(ViewModelLocator.TitleStorage
.TitleStorageFileName);
    }
}

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

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

    public override void Execute(object parameter)
    {
        ViewModelLocator.TitleStorage.TitleStorageFiles = new ObservableCollection<FileMetadata>();
        TitleStorageService.QueryFileList(ViewModelLocator.TitleStorage
.TitleStorageTag);
    }
}

  1. TitleStorageViewModel.cs를 열고 다음 두 개의 명령을 선언하고 인스턴스화합니다.

public TitleStorageQueryFileCommand TitleStorageQueryFile { get; set; }
public TitleStorageQueryFileListCommand TitleStorageQueryFileList { get; set; }

public TitleStorageViewModel()
{
    TitleStorageQueryFile = new TitleStorageQueryFileCommand();
    TitleStorageQueryFileList = new TitleStorageQueryFileListCommand();
}

  1. 연결 인터페이스를 통해 성공적으로 로그인한 후 타이틀 스토리지 API만 호출할 수 있도록 ViewModelLocator.cs에서 RaiseConnectCanExecuteChanged() 메서드에 다음 두 줄을 추가합니다.

TitleStorage.TitleStorageQueryFile.RaiseCanExecuteChanged();
TitleStorage.TitleStorageQueryFileList.RaiseCanExecuteChanged();

  1. 마지막으로, MainWindow.xaml에서 TabControl에 TitleStorageView를 추가합니다.

<TabItem x:Name="TitleStorage" Header="Title Storage">
    <views:TitleStorageView />
</TabItem>


앱을 실행하고 인증 및 연결을 통해 인증하면 '타이틀 스토리지' 탭으로 이동하여 이름(test_file 또는 another_test)으로파일을 쿼리할 수 있습니다.태그로 쿼리할 수도 있습니다. test_tag를 사용하면 두 파일 모두 반환됩니다.
App Title Storage Queried Files
태그로 쿼리한 타이틀 스토리지 파일

파일 읽기

이제 클라우드 스토리지에서 파일 읽기를 구현해 보겠습니다.게임의 필요에 따라 파일 읽기 구현이 달라질 수 있습니다. 하지만 지금은 캐시 디렉터리로 사용하고 있는 동일한 Temp 디렉터리 위치로 파일을 간단히 다운로드할 것입니다.
  1. TitleStorageService.cs에 다음 메서드를 추가합니다.

public static void ReadFile(FileMetadata fileMetadata)
{
    var readFileOptions = new ReadFileOptions()
    {
        LocalUserId = ProductUserId.FromString(ViewModelLocator.Main.ProductUserId),
        Filename = fileMetadata.Filename,
        ReadChunkLengthBytes = 1048576,
        ReadFileDataCallback = (ReadFileDataCallbackInfo readFileDataCallbackInfo) =>
        {
            using var fs = new FileStream($"{App.Settings.CacheDirectory}{readFileDataCallbackInfo.Filename}", FileMode.Append, FileAccess.Write);
            fs.Write(readFileDataCallbackInfo.DataChunk, 0, readFileDataCallbackInfo.DataChunk.Length);
            return ReadResult.RrContinuereading;
        },
        FileTransferProgressCallback = (FileTransferProgressCallbackInfo fileTransferProgressCallbackInfo) =>
        {
            var percentComplete = (double)fileTransferProgressCallbackInfo.BytesTransferred / (double)fileTransferProgressCallbackInfo.TotalFileSizeBytes * 100;
            ViewModelLocator.Main.StatusBarText = $"Downloading file <{fileTransferProgressCallbackInfo.Filename}> ({System.Math.Ceiling.(percentComplete)}%)...";
        }
    };

    ViewModelLocator.Main.StatusBarText = $"Downloading file <{readFileOptions.Filename}> (creating buffer)...";

    var fileTransferRequest = App.Settings.PlatformInterface.GetTitleStorageInterface().ReadFile(readFileOptions, null, (ReadFileCallbackInfo readFileCallbackInfo) =>
    {
        Debug.WriteLine($"ReadFile {readFileCallbackInfo.ResultCode}");

        if (readFileCallbackInfo.ResultCode == Result.Success)
        {
            Debug.WriteLine($"Successfully downloaded {readFileCallbackInfo.Filename} to {App.Settings.CacheDirectory}.");
            ViewModelLocator.Main.StatusBarText = string.Empty;
        }
    });

    if (fileTransferRequest == null)
    {
        Debug.WriteLine("Error downloading file:bad handle");
        ViewModelLocator.Main.StatusBarText = string.Empty;
    }
}
  • 문서에서 설명한 것처럼 TitleStorage.ReadFile은 파일을 청크로 읽습니다.TitleStorage.ReadFileOptions에서는 청크 크기를 지정하고 데이터를 읽기 위한 콜백 메서드와 진행 상태를 보고하기 위한 또 다른 콜백을 지정합니다.
  • 파일을 저장하기 위해 Temp 폴더(기본적으로 C:\Users\<Windows_User>\AppData\Local\Temp\ 또는 %Temp%)를 사용합니다.
  1. TitleStorageReadFileCommand.cs 클래스를 Commands 폴더에 추가합니다.

public class TitleStorageReadFileCommand :CommandBase
{
    public override bool CanExecute(object parameter)
    {
        return ViewModelLocator.TitleStorage.SelectedTitleStorageFile != null;
    }

    public override void Execute(object parameter)
    {
        TitleStorageService.ReadFile(ViewModelLocator.TitleStorage
.SelectedTitleStorageFile);
    }
}

  1. TitleStorageViewModel.cs를 열고 새로운 명령을 선언하고 인스턴스화합니다.

public TitleStorageQueryFileCommand TitleStorageQueryFile { get; set; }
public TitleStorageQueryFileListCommand TitleStorageQueryFileList { get; set; }
public TitleStorageReadFileCommand TitleStorageReadFile { get; set; }

public TitleStorageViewModel()
{
    TitleStorageQueryFile = new TitleStorageQueryFileCommand();
    TitleStorageQueryFileList = new TitleStorageQueryFileListCommand();
    TitleStorageReadFile = new TitleStorageReadFileCommand();
}

  1. 마지막으로, TitleStorageView.xaml.cs를 열고 TitleStorageFilesListView_SelectionChanged() 메서드에 다음을 추가합니다.

ViewModel.TitleStorageReadFile.RaiseCanExecuteChanged();

이제 앱을 실행하고, 인증하고, 파일을 쿼리하고, 파일을 다운로드할 때 %Temp% 디렉터리의 디스크에 파일이 있게 됩니다.이 경우, 파일을 열고 파일의 내용을 확인할 수 있도록 .txt 파일 확장명을 추가하기 위해 파일명 test_file 또는 another_test를 바꿀 수 있습니다.
App Title Storage Downloaded File
타이틀 스토리지에서 다운로드한 파일

코드 받기

아래 링크를 통해 이 연재글에서 사용된 코드를 받을 수 있습니다.다운로드한 코드를 설정하려면 GitHub Repository의 사용 지침을 따르세요.
타이틀 스토리지와 플레이어 데이터 스토리지의 차이점에 대해 알아봤습니다. 다음 연재글에서는 플레이어 데이터 스토리지를 사용하여 플레이어 전용 데이터를 저장하고 가져오는 것을 구현해 보겠습니다.

전체 연재글은 시리즈 참조에서 확인할 수 있습니다.질문이나 피드백은 커뮤니티 포럼에 올려주세요.

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

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