Unity/Unity Cloud(UGS)

[Unity] UGS - Cloud Save

달시_Dalsi 2024. 10. 24. 03:28

Cloud Save란?

Cloud Save는 플레이어의 게임 진행 상태, 게임 설정, 인벤토리 등의 작은 데이터를 클라우드에 저장하고 불러올 수 있는 기능입니다. 이를 통해 플레이어가 여러 디바이스에서 동일한 게임 데이터를 사용할 수 있어 데이터를 안전하게 보존할 수 있습니다.

 

주요 기능

  1. 크로스 디바이스 동기화: 플레이어는 다른 기기에서도 같은 데이터로 게임을 이어서 할 수 있습니다.
  2. 데이터 보안: 로컬 저장소에 의존하지 않고 클라우드에 데이터를 백업함으로써 데이터 손실을 방지합니다.

 

Cloud Save 대시보드의 주요 탭들

1. 인덱스 설정 (Index Configuration)

  • 설명: Cloud Save에서 인덱스는 데이터를 빠르게 검색하고 접근할 수 있도록 만들어줍니다. 인덱스를 설정하면 대량의 데이터 중에서도 필요한 데이터를 효과적으로 찾을 수 있기 때문에 성능 최적화에 중요한 역할을 합니다.
  • 활용 예시: 플레이어가 수집한 게임 아이템 목록을 검색하거나, 특정 업적을 달성한 플레이어 데이터를 검색할 때 인덱스가 빠르게 결과를 반환하도록 도와줍니다.

2. 게임 데이터 (Game Data)

  • 설명: 모든 플레이어에게 동일하게 적용되는 게임 데이터는 글로벌 설정에 해당하는 정보들을 저장합니다. 예를 들어 게임 업데이트나 패치 후에도 일관된 데이터가 유지되도록 할 수 있습니다.
  • 활용 예시: 게임 내에서 사용하는 기본 설정(예: 그래픽 옵션, 게임 난이도)이나 이벤트 정보 등을 관리할 때 이 기능을 사용합니다.


3. 플레이어 데이터 (Player Data)

  • 설명: 각각의 플레이어별로 저장되는 개인 데이터를 관리합니다. 이는 플레이어의 진행 상황, 획득한 아이템, 경험치, 설정 등을 포함하며, 각 플레이어마다 고유한 정보를 동기화합니다.
  • 활용 예시: 플레이어가 다른 기기에서 로그인을 해도 동일한 게임 진행 상태를 유지하게 할 수 있습니다. 예를 들어, 플레이어가 모바일에서 진행한 데이터를 PC에서도 불러와 이어서 게임을 진행할 수 있습니다.


4. 플레이어 파일 (Player Files)

  • 설명: 대용량 파일을 클라우드에 저장할 수 있습니다. 예를 들어 플레이어의 저장 파일, 리플레이 영상, 사용자 생성 콘텐츠 등을 저장합니다.
  • 활용 예시: 게임 리플레이 파일을 저장해 언제든 다시 재생할 수 있거나, 플레이어가 직접 만든 캐릭터나 게임 맵 같은 사용자 생성 콘텐츠를 저장하고 불러오는 용도로 사용합니다.

 

Cloud Save 사용하기

1. Unity 서비스 초기화 및 로그인

먼저 Unity Services를 초기화하고, 플레이어가 익명 또는 정식 로그인 방식을 사용해 인증되어야 합니다.

using Unity.Services.CloudSave;
using Unity.Services.Authentication;
using Unity.Services.Core;
using UnityEngine;
using System.Threading.Tasks;

public class CloudSaveExample : MonoBehaviour
{
    public string username = "your_username"; // 사용자 이름
    public string password = "your_password"; // 비밀번호

    async void Start()
    {
        // Unity Services 초기화
        await UnityServices.InitializeAsync();
        Debug.Log("Unity Services Initialized");

        // 익명 로그인
        await SignInAnonymouslyAsync();

        // 사용자 이름과 비밀번호로 로그인
        await SignInWithUsernameAndPasswordAsync(username, password);
    }

    // 익명 로그인 메서드
    async Task SignInAnonymouslyAsync()
    {
        if (!AuthenticationService.Instance.IsSignedIn)
        {
            try
            {
                await AuthenticationService.Instance.SignInAnonymouslyAsync();
                Debug.Log("플레이어가 익명으로 로그인되었습니다.");
            }
            catch (AuthenticationException ex)
            {
                Debug.LogError("익명 로그인 실패: " + ex.Message);
            }
            catch (RequestFailedException ex)
            {
                Debug.LogError("요청 실패: " + ex.Message);
            }
        }
    }

    // 사용자 이름과 비밀번호로 로그인하는 메서드
    async Task SignInWithUsernameAndPasswordAsync(string username, string password)
    {
        try
        {
            await AuthenticationService.Instance.SignInWithUsernameAndPasswordAsync(username, password);
            Debug.Log("로그인 성공!");
            Debug.Log($"PlayerID: {AuthenticationService.Instance.PlayerId}");
        }
        catch (AuthenticationException ex)
        {
            Debug.LogError("인증 실패: " + ex.Message);
        }
        catch (RequestFailedException ex)
        {
            Debug.LogError("요청 실패: " + ex.Message);
        }
    }
}

 

2. Player Data

데이터를 키-값 형태로 저장할 수 있습니다.

using Unity.Services.CloudSave;
using Unity.Services.Authentication;
using Unity.Services.Core;
using UnityEngine;
using System.Collections.Generic;
using System.Threading.Tasks;
using Unity.Services.CloudSave.Models.Data.Player;

public class CloudSaveManager : MonoBehaviour
{
    async void Start()
    {
        // Unity Services 초기화
        await UnityServices.InitializeAsync();
        Debug.Log("Unity Services Initialized");

        // 플레이어를 익명으로 로그인
        if (!AuthenticationService.Instance.IsSignedIn)
        {
            await AuthenticationService.Instance.SignInAnonymouslyAsync();
            Debug.Log($"PlayerID: {AuthenticationService.Instance.PlayerId}");
        }
    }

    // 1-1. Save an item: 플레이어 데이터 저장 (default)
    async void SavePlayerData()
    {
        var data = new Dictionary<string, object> { { "testKey_default", "testValue" } };
        await CloudSaveService.Instance.Data.Player.SaveAsync(data);
        Debug.Log("플레이어 데이터 저장 완료: testKey = testValue");
    }

    // 1-2. Save an item: 플레이어 데이터 저장 (public)
    async void SavepublicPlayerData()
    {
        var data = new Dictionary<string, object> { { "testKey_public", 2 } };
        await CloudSaveService.Instance.Data.Player.SaveAsync(data, new Unity.Services.CloudSave.Models.Data.Player.SaveOptions(new PublicWriteAccessClassOptions()));
    }


    // 2-1. Fetch data: 데이터 가져오기
    async void FetchPlayerData()
    {
        var playerData = await CloudSaveService.Instance.Data.Player.LoadAsync(new HashSet<string> { "keyName" });
        if (playerData.TryGetValue("keyName", out var keyName))
        {
            Debug.Log($"keyName: {keyName.Value.GetAs<string>()}");
        }
    }

    // 2-2. Fetch data: 데이터 가져오기 (Public)
    async void FetchPublicPlayerData()
    {
        var playerData = await CloudSaveService.Instance.Data.Player.LoadAsync(new HashSet<string> { "keyName" }, 
            new LoadOptions(new PublicReadAccessClassOptions()));
        if (playerData.TryGetValue("keyName", out var keyName))
        {
            Debug.Log($"keyName: {keyName.Value.GetAs<string>()}");
        }
    }

    // 2-3. Fetch data: 데이터 가져오기 (Protected)
    async void FetchProtectedPlayerData()
    {
        var playerData = await CloudSaveService.Instance.Data.Player.LoadAsync(new HashSet<string> { "keyName" }, 
            new LoadOptions(new ProtectedReadAccessClassOptions()));
        if (playerData.TryGetValue("keyName", out var keyName))
        {
            Debug.Log($"keyName: {keyName.Value.GetAs<string>()}");
        }
    }

    // 2-4. Fetch data: 데이터 가져오기 (지정 플레이어 ID의 퍼블릭 데이터)
    async void LoadPublicDataByPlayerId()
    {
        var playerId = "JE1unrWOOzzbIwy3Nl60fRefuiVE";
        var playerData = await CloudSaveService.Instance.Data.Player.LoadAsync(new HashSet<string> { "keyName" }, 
            new LoadOptions(new PublicReadAccessClassOptions(playerId)));

        if (playerData.TryGetValue("keyName", out var keyName))
        {
            Debug.Log($"keyName: {keyName.Value.GetAs<string>()}");
        }
    }

    // 3. Delete an item: 특정 키 데이터 삭제
    async void DeletePlayerData()
    {
        // 지정값만 삭제
        await CloudSaveService.Instance.Data.Player.DeleteAsync("testKey");

        // 모든 값 삭제
        await CloudSaveService.Instance.Data.Player.DeleteAllAsync();
    }


    // 4-1. Get a list of keys: 저장된 모든 데이터 키 목록 확인
    async void ListPlayerKeys()
    {
        var keys = await CloudSaveService.Instance.Data.Player.ListAllKeysAsync();
        for (int i = 0; i < keys.Count; i++)
        {
            Debug.Log("플레이어 데이터 키 목록: " + keys[i].Key);
        }
    }

    // 4-2. Get a list of keys: 저장된 모든 데이터 키 목록 확인 (Public)
    async void PublicListPlayerKeys()
    {
        var keys = await CloudSaveService.Instance.Data.Player.ListAllKeysAsync(
          new ListAllKeysOptions(new PublicReadAccessClassOptions())
        );
        for (int i = 0; i < keys.Count; i++)
        {
            Debug.Log(keys[i].Key);
        }
    }

    // 4-3. Get a list of keys: 저장된 모든 데이터 키 목록 확인 (Protected)
    async void ProtectedListPlayerKeys()
    {
        var keys = await CloudSaveService.Instance.Data.Player.ListAllKeysAsync(
          new ListAllKeysOptions(new ProtectedReadAccessClassOptions())
        );
        for (int i = 0; i < keys.Count; i++)
        {
            Debug.Log(keys[i].Key);
        }
    }
}

 

3. Player Files


    // 1. Save Player File: 파일 저장
    async Task SavePlayerFile()
    {
        byte[] fileData = System.Text.Encoding.UTF8.GetBytes("This is a test file.");
        await CloudSaveService.Instance.Files.Player.SaveAsync("testfile.txt", fileData);
    }

    // 2. Delete Player File: 파일 삭제
    async Task DeletePlayerFile()
    {
        await CloudSaveService.Instance.Files.Player.DeleteAsync("fileName");
    }

    // 3. List Player Files: 저장된 파일 목록 조회
    async Task ListPlayerFiles()
    {
        List<FileItem> files = await CloudSaveService.Instance.Files.Player.ListAllAsync();

        for (int i = 0; i < files.Count; i++)
        {
            // 파일 제목 출력
            Debug.Log($"파일 제목: {files[i].Key}");
        }
    }

    // 4. Get Player File as a byte array: 파일을 바이트 배열로 가져오기
    async Task GetPlayerFileAsByteArray()
    {
        byte[] file = await CloudSaveService.Instance.Files.Player.LoadBytesAsync("testfile.txt");

        // 파일 내용을 문자열로 변환하여 출력 (텍스트 파일일 경우)
        string fileContent = System.Text.Encoding.UTF8.GetString(file);
        Debug.Log($"파일 내용: {fileContent}");
    }

    // 5. Get Player File as a stream: 파일을 스트림으로 가져오기
    async Task GetPlayerFileAsStream()
    {
        using (var stream = await CloudSaveService.Instance.Files.Player.LoadStreamAsync("testfile.txt"))
        {
            using (StreamReader reader = new StreamReader(stream))
            {
                string content = reader.ReadToEnd();
                Debug.Log("플레이어 파일 스트림으로 가져옴: " + content);
            }
        }
    }

    // 6. Get Player File Metadata: 파일 메타데이터 가져오기
    async Task GetPlayerFileMetadata()
    {
        var metadata = await CloudSaveService.Instance.Files.Player.GetMetadataAsync("testfile.txt");
        Debug.Log(metadata.Key);
        Debug.Log(metadata.Size);
        Debug.Log(metadata.ContentType);
        Debug.Log(metadata.Created);
        Debug.Log(metadata.WriteLock);
    }

 

4. Game Data

 // 데이터 가져오기
 public async void LoadCustomItemData()
 {
     // 커스텀 아이템의 ID 설정
     var customItemId = "my-custom-item";

     // Cloud Save 서비스에서 해당 커스텀 아이템 데이터 로드
     var customItemData = await CloudSaveService.Instance.Data.Custom.LoadAllAsync(customItemId);

     // 가져온 각 커스텀 아이템의 키와 값을 출력
     foreach (var customItem in customItemData)
     {
         Debug.Log($"키: {customItem.Key}, 값: {customItem.Value.Value}");
     }
 }

 // Query Custom Items
 public async void QueryCustomItems()
 {
     var query = new Query(
        // 첫 번째 인자는 필터 리스트로, 모든 필터가 true로 평가되어야 결과에 포함됨
        new List<FieldFilter>
        {
    // "indexedKeyName"이 "value"와 같은지 확인하는 필터
    new FieldFilter("indexedKeyName", "value", FieldFilter.OpOptions.EQ, true),
    // "anotherIndexedKeyName"이 "otherValue"와 같지 않은지 확인하는 필터
    new FieldFilter("anotherIndexedKeyName", "otherValue", FieldFilter.OpOptions.NE, true)

        },
        // 두 번째 인자는 응답에 포함할 키들의 리스트 (옵션)
        // 인덱스에 없는 키도 포함 가능하며, 아무것도 지정하지 않으면 플레이어 ID만 반환됨
        new HashSet<string>
        {
    // 응답에서 "indexedKeyName"의 값을 포함하도록 지정
    "indexedKeyName"
        }
     );

     // 쿼리 실행, 결과를 받음
     var results = await CloudSaveService.Instance.Data.Custom.QueryAsync(query);

     // 쿼리 결과의 개수를 로그로 출력
     Debug.Log($"쿼리 결과 항목 수: {results.Count}");

     // 각 결과 항목에 대해 ID와 데이터를 출력
     results.ForEach(r =>
     {
         // 결과 항목의 ID 출력
         Debug.Log($"Custom Item ID: {r.Id}");
         // 결과 항목의 데이터에서 키와 값 출력
         r.Data.ForEach(d => Debug.Log($"키: {d.Key}, 값: {d.Value.GetAsString()}"));
     });
 }

 

 

마무리

Unity Cloud Save는 여러 디바이스에서 동일한 저장데이터, 게임데이터를 제공하는 데 필수적인 도구입니다. 이를 통해 플레이어 데이터를 클라우드에 저장해, 데이터 손실 없이 진행 상황을 유지할 수 있습니다.