> ## Documentation Index
> Fetch the complete documentation index at: https://devdocs.xbox.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 엔터티 파일

> 모든 형식으로 PlayFab 엔터티에 첨부된 파일을 읽고 쓰고 업로드하며, 로그인, 로드, 업로드를 다루는 전체 Unity C# 엔터티 파일 루프를 제공합니다.

엔터티 파일을 사용하면 모든 형식으로 엔터티에 첨부된 파일을 읽고 쓸 수 있습니다. 다음 예제는 로그인부터 파일 로드, 새 파일 업로드에 이르는 전체 엔터티 파일 루프를 보여줍니다.

```csharp theme={null}
#if !DISABLE_PLAYFABENTITY_API && !DISABLE_PLAYFABCLIENT_API

using PlayFab;
using PlayFab.Internal;
using System;
using System.Collections.Generic;
using System.Text;
using UnityEngine;

public class EntityFileExample : MonoBehaviour
{
    public string entityId; // Id representing the logged in player
    public string entityType; // entityType representing the logged in player
    private readonly Dictionary<string, string> _entityFileJson = new Dictionary<string, string>();
    private readonly Dictionary<string, string> _tempUpdates = new Dictionary<string, string>();
    public string ActiveUploadFileName;
    public string NewFileName;
    public int GlobalFileLock = 0; // Kind of cheap and simple way to handle this kind of lock

    void OnSharedFailure(PlayFabError error)
    {
        Debug.LogError(error.GenerateErrorReport());
        GlobalFileLock -= 1;
    }

    void OnGUI()
    {
        if (!PlayFabClientAPI.IsClientLoggedIn() && GUI.Button(new Rect(0, 0, 100, 30), "Login"))
            Login();
        if (PlayFabClientAPI.IsClientLoggedIn() && GUI.Button(new Rect(0, 0, 100, 30), "LogOut"))
            PlayFabClientAPI.ForgetAllCredentials();

        if (PlayFabClientAPI.IsClientLoggedIn() && GUI.Button(new Rect(100, 0, 100, 30), "(re)Load Files"))
            LoadAllFiles();

        if (PlayFabClientAPI.IsClientLoggedIn())
        {
            // Display existing files
            _tempUpdates.Clear();
            var index = 0;
            foreach (var each in _entityFileJson)
            {
                GUI.Label(new Rect(100 * index, 60, 100, 30), each.Key);
                var tempInput = _entityFileJson[each.Key];
                var tempOutput = GUI.TextField(new Rect(100 * index, 90, 100, 30), tempInput);
                if (tempInput != tempOutput)
                    _tempUpdates[each.Key] = tempOutput;
                if (GUI.Button(new Rect(100 * index, 120, 100, 30), "Save " + each.Key))
                    UploadFile(each.Key);
                index++;
            }
            // Apply any changes
            foreach (var each in _tempUpdates)
                _entityFileJson[each.Key] = each.Value;

            // Add a new file
            NewFileName = GUI.TextField(new Rect(100 * index, 60, 100, 30), NewFileName);
            if (GUI.Button(new Rect(100 * index, 90, 100, 60), "Create " + NewFileName))
                UploadFile(NewFileName);
        }
    }

    void Login()
    {
        var request = new PlayFab.ClientModels.LoginWithCustomIDRequest
        {
            CustomId = SystemInfo.deviceUniqueIdentifier,
            CreateAccount = true
        };
        PlayFabClientAPI.LoginWithCustomID(request, OnLogin, OnSharedFailure);
    }
    void OnLogin(PlayFab.ClientModels.LoginResult result)
    {
        entityId = result.EntityToken.Entity.Id;
        entityType = result.EntityToken.Entity.Type;
    }

    void LoadAllFiles()
    {
        if (GlobalFileLock != 0)
            throw new Exception("This example overly restricts file operations for safety. Careful consideration must be made when doing multiple file operations in parallel to avoid conflict.");

        GlobalFileLock += 1; // Start GetFiles
        var request = new PlayFab.DataModels.GetFilesRequest { Entity = new PlayFab.DataModels.EntityKey { Id = entityId, Type = entityType } };
        PlayFabDataAPI.GetFiles(request, OnGetFileMeta, OnSharedFailure);
    }
    void OnGetFileMeta(PlayFab.DataModels.GetFilesResponse result)
    {
        Debug.Log("Loading " + result.Metadata.Count + " files");

        _entityFileJson.Clear();
        foreach (var eachFilePair in result.Metadata)
        {
            _entityFileJson.Add(eachFilePair.Key, null);
            GetActualFile(eachFilePair.Value);
        }
        GlobalFileLock -= 1; // Finish GetFiles
    }
    void GetActualFile(PlayFab.DataModels.GetFileMetadata fileData)
    {
        GlobalFileLock += 1; // Start Each SimpleGetCall
        PlayFabHttp.SimpleGetCall(fileData.DownloadUrl,
            result => { _entityFileJson[fileData.FileName] = Encoding.UTF8.GetString(result); GlobalFileLock -= 1; }, // Finish Each SimpleGetCall
            error => { Debug.Log(error); }
        );
    }

    void UploadFile(string fileName)
    {
        if (GlobalFileLock != 0)
            throw new Exception("This example overly restricts file operations for safety. Careful consideration must be made when doing multiple file operations in parallel to avoid conflict.");

        ActiveUploadFileName = fileName;

        GlobalFileLock += 1; // Start InitiateFileUploads
        var request = new PlayFab.DataModels.InitiateFileUploadsRequest
        {
            Entity = new PlayFab.DataModels.EntityKey { Id = entityId, Type = entityType },
            FileNames = new List<string> { ActiveUploadFileName },
        };
        PlayFabDataAPI.InitiateFileUploads(request, OnInitFileUpload, OnInitFailed);
    }
    void OnInitFailed(PlayFabError error)
    {
        if (error.Error == PlayFabErrorCode.EntityFileOperationPending)
        {
            // This is an error you should handle when calling InitiateFileUploads, but your resolution path might vary
            GlobalFileLock += 1; // Start AbortFileUploads
            var request = new PlayFab.DataModels.AbortFileUploadsRequest
            {
                Entity = new PlayFab.DataModels.EntityKey { Id = entityId, Type = entityType },
                FileNames = new List<string> { ActiveUploadFileName },
            };
            PlayFabDataAPI.AbortFileUploads(request, (result) => { GlobalFileLock -= 1; UploadFile(ActiveUploadFileName); }, OnSharedFailure); GlobalFileLock -= 1; // Finish AbortFileUploads
            GlobalFileLock -= 1; // Failed InitiateFileUploads
        }
        else
            OnSharedFailure(error);
    }
    void OnInitFileUpload(PlayFab.DataModels.InitiateFileUploadsResponse response)
    {
        string payloadStr;
        if (!_entityFileJson.TryGetValue(ActiveUploadFileName, out payloadStr))
            payloadStr = "{}";
        var payload = Encoding.UTF8.GetBytes(payloadStr);

        GlobalFileLock += 1; // Start SimplePutCall
        PlayFabHttp.SimplePutCall(response.UploadDetails[0].UploadUrl,
            payload,
            FinalizeUpload,
            error => { Debug.Log(error); }
        );
        GlobalFileLock -= 1; // Finish InitiateFileUploads
    }
    void FinalizeUpload(byte[] body) // body is unused in this example
    {
        GlobalFileLock += 1; // Start FinalizeFileUploads
        var request = new PlayFab.DataModels.FinalizeFileUploadsRequest
        {
            Entity = new PlayFab.DataModels.EntityKey { Id = entityId, Type = entityType },
            FileNames = new List<string> { ActiveUploadFileName },
        };
        PlayFabDataAPI.FinalizeFileUploads(request, OnUploadSuccess, OnSharedFailure);
        GlobalFileLock -= 1; // Finish SimplePutCall
    }
    void OnUploadSuccess(PlayFab.DataModels.FinalizeFileUploadsResponse result)
    {
        Debug.Log("File upload success: " + ActiveUploadFileName);
        GlobalFileLock -= 1; // Finish FinalizeFileUploads
    }
}
#endif
```

## 이 예제 분석

* `GlobalFileLock`은 이 예제를 위해 특별히 설계된, 파일 충돌을 방지하기 위한 단순한 방법입니다.
  * 독립적인 파일 작업은 어떠한 문제도 일으키지 않습니다.
  * 각 파일 작업에는 여러 단계와 여러 API 호출이 필요하므로, 같은 파일을 여러 방식으로 동시에 접근하지 마세요.
  * 주의를 기울인다면 어떠한 잠금 메커니즘도 필요하지 않을 수 있습니다.
  * 복잡한 작업을 수행하려는 경우 잠금 메커니즘이 훨씬 더 복잡해질 수 있습니다.
* `OnGUI`는 오래되었지만 스크립트 내에서 완전히 Unity GUI를 구축하는 매우 압축적인 방법입니다.
  * 실제 GUI는 훨씬 개선되고 게임에 특화되어야 합니다.
* 모든 PlayFab 기능은 *먼저* 로그인 또는 인증이 필요합니다.
* `LoadAllFiles()`는 이름 그대로의 작업을 수행합니다. 현재 로그인된 엔터티에 대해 PlayFab에 저장된 모든 파일을 로드합니다.
  * 이 함수는 여러 단계를 필요로 합니다.
    * PlayFab에 파일 위치를 요청한 후,
    * 파일을 개별적으로 다운로드합니다.
* `UploadFile(string fileName)`은 엔터티의 파일을 서비스에 저장합니다.
  * 간소화를 위해 이 예제는 한 번에 하나의 파일을 저장하지만, 파일을 원자적으로 세트 단위로 업로드할 수도 있습니다.
  * 이 작업의 단계는 다음과 같습니다.
    * 원자적 업로드 작업 초기화,
    * 모든 파일 업로드,
    * 원자적 업로드 작업 완료.
  * 원자적 업로드 작업이 성공적으로 완료될 때까지 엔터티는 파일 업로드가 완료된 것으로 간주하지 않으며, 다른 호출자에게 어떤 변경 사항도 반영하지 않습니다.

## 게임 관리자와 엔터티

게임 관리자를 통해 플레이어의 오브젝트와 파일을 조작할 수 있습니다. 플레이어 개요는 타이틀 플레이어와 마스터 플레이어 계정 정보를 모두 표시하도록 업데이트되었습니다.

<img src="https://mintcdn.com/microsoft-4404708b/3hg2JQs0m7qmDqay/images/playfab/live-service-management/game-configuration/entities/tutorials/game-manager-entities-player-overview.png?fit=max&auto=format&n=3hg2JQs0m7qmDqay&q=85&s=ceac36d3b1e7fd248caeedbf4bea8055" alt="게임 관리자 - 엔터티 - 플레이어 개요" width="1800" height="1406" data-path="images/playfab/live-service-management/game-configuration/entities/tutorials/game-manager-entities-player-overview.png" />

또한 **플레이어(Players)** 탭에 파일과 오브젝트를 위한 별도 섹션이 포함되어 있습니다.

<img src="https://mintcdn.com/microsoft-4404708b/3hg2JQs0m7qmDqay/images/playfab/live-service-management/game-configuration/entities/tutorials/game-manager-entities-player-files.png?fit=max&auto=format&n=3hg2JQs0m7qmDqay&q=85&s=70b96669460d8bb673ec25fdbcd1bd41" alt="게임 관리자 - 엔터티 - 플레이어 파일 및 오브젝트" width="1800" height="969" data-path="images/playfab/live-service-management/game-configuration/entities/tutorials/game-manager-entities-player-files.png" />

## 관련 기능

엔터티 오브젝트와 파일은 다음과 같이 서로 다른 엔터티 유형으로 범위가 지정되어 여러 PlayFab 기능에서 사용됩니다.

| 기능                                                                                | 엔터티 유형                                                       | 사용 사례                      |
| --------------------------------------------------------------------------------- | ------------------------------------------------------------ | -------------------------- |
| [게임 서버 구성](/services/playfab/live-service-management/game-configuration)          | `game_server`                                                | 서버 측 설정 및 서버 권한 데이터        |
| [타이틀 데이터](/services/playfab/live-service-management/game-configuration/titledata) | `namespace`, `title`                                         | 모든 플레이어가 공유하는 전역 키/값 구성    |
| [플레이어 데이터](/services/playfab/player-progression/player-data)                      | `title_player_account`, `master_player_account`, `character` | 플레이어별 프로필, 진행 상황, 환경설정 저장  |
| [그룹](/services/playfab/community/associations/groups)                             | `group`                                                      | 플레이어 그룹 또는 길드 내에서 공유되는 데이터 |


## Related topics

- [엔터티 빠른 시작](/ko/services/playfab/live-service-management/game-configuration/entities/quickstart.md)
- [Content & Configuration Writes 미터 API 설명](/ko/services/playfab/pricing/meters/file-writes.md)
- [PlayFab GDPR - 플레이어 데이터 내보내기](/ko/services/playfab/data-analytics/privacy-compliance/gdpr-exporting-player-data.md)
- [Content & Configuration Reads 미터 API 설명](/ko/services/playfab/pricing/meters/file-reads.md)
- [PFDataGetFilesResponse](/ko/services/playfab/api-references/c/pfdatatypes/structs/pfdatagetfilesresponse.md)
