[4Idle - GroundZero] 게임 오버 씬 - 랭킹 시스템 구현

- 게임 오버 씬에서는 획득한 보상, 게임 내에서의 기록 등 점수가 나오고, 이후에 기록된 랭킹이 표시된다.

 

Player의 username을 받아와 PlayerData.json에 저장하기

-오큘러스를 사용하는 사용자는 오큘러스의 nickname이 존재한다.

- 시작할때, 이 nickname을 player의 username으로 자동 저장되게 한다.

=> 게임이 끝나면 username과 score를 정보로 저장해 firebase에 기록한다.

https://developer.oculus.com/documentation/unity/ps-setup/

 

Set Up for Platform Development with Unity: Unity | Oculus Developers

 

developer.oculus.com

- 위 링크에 따라 몇가지 셋팅이 필요하다.

https://developer.oculus.com/manage/app/api/

 

Oculus Developer Center | Authenticate

 

developer.oculus.com

-위 링크에서 oculus platform setting에 필요한 앱 ID를 설정할 수 있다.

-설정한 app ID를 oculus>Platform>Edit settings에 입력해준다.

Platform> Edit settings

-Unity Editor에서 Play를 눌러 사용자 정보를 테스트하기 위해서는 AppID를 입력하고, UseStandalone Platform을 체크해 로그인해야한다.

-로그인후에는 상단 이미지의 Currently usint the credentials~와 같은 안내창이 나오게 된다.

-빌드한 앱에서도 사용자를 받아오려면 다음과 같은 절차가 필요하다. data use certify가 필요하다.

-상단에 방문했던 링크(api링크)에서 수정할 수 있다.

 

 

-DeveloperHub의 App Distribution을 누르면 App을 선택할 수 있다.

-앱을 선택 후 ReleaseChannel의 우측에 있는 Upload를 누른 후 빌드파일을 Drag & Drop한다.

 

-Release Channel에 빌드파일을 업로드한 후, Users를 클릭하여 내 계정을 추가하였다.

-하단의 링크를 참고했다.

https://communityforums.atmeta.com/t5/General-Development/GetLoggedInUser-returns-Oculus-ID-0-with-v59-update/m-p/1102695#M4102

 

GetLoggedInUser returns Oculus ID 0 with v59 update

I have my build on three Quest 3 devices. Two are running: 59.0.0.163.706.533208642 The other is running: 57.0.0.297.669.526237896 All devices are using the same login account. When running GetLoggedInUser it returns the correct Oculus ID on v57, and Ocul

communityforums.atmeta.com

-이 때 unity build파일을 업로드 하고, 기기에 설치할 때 몇가지 주의사항이 있다.

1. key 셋팅 - 키가 설정되어있지 않은 경우 ReleaseChannel에 업로드할 수 없다.(Publish Settings> KeyManager)

2. 앱이 기기에 설치 되어있는 경우 - 앱을 uninstall하고 새로운 빌드apk를 설치해야한다.releaseChannel로 부터 앱을 다운받아 설치하려고 할 때 이미 기기에 설치되어있다면 제대로 설치가 안되는 오류가있다.

3. release Channel에 업로드할 때에는 Version과 version code를 변경 후 업로드해야한다. 같은 경우 업로드 불가하다.

 

빌드 파일 실행 후 확인한 로그

-빌드 후 Android Logcat을 사용해 UserID와 DisplayName을 받아오는지 확인하였다.

https://stackoverflow.com/questions/76038469/oculus-users-getloggedinuser-return-empty-string-for-displayname-field

 

Oculus Users.GetLoggedInUser() return empty string for DisplayName field

I am trying to get the userId, ImageURL, OculusID, and DisplayName for the local Oculus user in my Unity 3D game. I get correctly the userId, the ImageURL, and the OculusID, but the DisplayName is ...

stackoverflow.com

- 이 링크의 내용처럼 빌드 후에는 userID를 받은 후에 그것을 통해 한번 더 request해야 DisplayName을 가져올 수 있다.

-즉, unity Editor에서는 바로 가져올 수 있지만, 빌드시에는 요청이 한번 더 필요하다. 

-----------------------------------------------------------------------------------------------------------------------------------------------------------------

Firebase 사용

https://firebase.google.com/docs/unity/setup?hl=ko

 

Unity 프로젝트에 Firebase 추가  |  Firebase for Unity

Firebase 데모 데이가 시작되었습니다. Google 최고의 기술을 활용하여 AI 기반 풀 스택 앱을 빌드하고 성장시키는 방법에 관한 데모를 시청하세요. 의견 보내기 Unity 프로젝트에 Firebase 추가 컬렉션

firebase.google.com

-먼저 프로젝트를 firebase와 연동해주자. 위 링크를 참고하도록한다.

 

데이터 저장

게임이 종료되었을 때 플레이어의 점수와 이름을 Fireabase에 저장해야한다.

-이 저장된 데이터를 포함하여 업데이트된 랭킹창을 띄운다.

-이제 플레이어의 정보를 가져왔으므로 이를 데이터로 저장해 firebase로 넘겨야한다.

=>  firebase로 데이터는 어떤 순간에 넘어가야하는가? username과 score를 모두 받아오고 난 순간에 넘겨야한다.

GetUser.cs 수정

 

실행 후 firebase에 추가된 정보 확인

 

-데이터가 잘 저장되는것을 확인했다. 그러나 현재는 중복된 값이 있어도 계속 새롭게 저장된다. 따라서 중복값인 경우에 최대점수이면 update하고, 아닌 경우에는 저장하지 않도록 해야한다.

GetUser.cs 수정

-코드를 수정해 user가 있는지 확인하고 없다면 rank 기록을 새로 작성하도록 하였다.

-기록이 있다면, 새로 획득한 점수가 큰 경우에 변경된다.

 

데이터 불러오기

-데이터를 저장할 수 있게되었으니, firebase로부터 불러올 수도 있어야 한다.

-데이터는 어떤 때 불러와야하는가?

=> 저장 한 후, rank가 보여질 스크롤 뷰가 setActive되기 전에 불러와야한다.

=> 즉, 저장하고 바로 불러오게 하면 된다.

정렬된 결과

 

스크롤뷰 생성 및 연동

https://meltingmelvin.tistory.com/108

 

[UGUI연습] 미션(동적 스크롤뷰 활용)

빈오브젝트 (content) 만들고 앵커프리셋 시프트 + 알트 좌상단 컴포넌트 Content Size Fitter 붙이고 스크롤뷰에 ScrollRect붙이고 Vertical 만 체크, scrollrect에 content를 넣어준다.스크롤 뷰에 마스크 추가 -

meltingmelvin.tistory.com

 

 

 

-게임 오버씬과 게임클리어씬이 필요하다.

-랭킹은 기존에 생성된 정보들이 다 보여지고 난 후, 마지막에 보여져야하므로 꺼두었다가 활성화 되도록한다.

GameClearMain.cs

 

GetUser.cs

-onRankActive에서 랭크캔버스가 활성화되고 코루틴을 통해 데이터를 가져온다.

-이 때, onLoadFirebaseData를 통해 랭킹 데이터를 가져오도록 한다.

-GetUser의 Text와 PlayerRankText에 각각 할당해준다. 

-Text에는 랭킹 텍스트가, PlayerRankText에는 유저의 랭킹을 알려주는 텍스트가 뜨게된다.

 

 

구현결과

 

 

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Oculus.Platform;
using Oculus.Platform.Models;
using System.Linq;
using TMPro;
using Firebase;
using Firebase.Database;


public class GetUser : MonoBehaviour
{
    class Rank
    {
        public string name;
        public int score;
        

        public Rank(string name, int score)
        {
            this.name = name;
            this.score = score;
        }
    }
    public DatabaseReference reference { get; set; }
    // 라이브러리를 통해 불러온 FirebaseDatabase 관련객체를 선언해서 사용
    private string leaderboardData = "";
    private string playerRankData = "";
    private int rank;
    public int score;
    public string OculusUserName= "";
    private ulong userId;
    private System.Action onGetUserName;
    private System.Action onLoadFirebaseData;
    public System.Action onRankActive;
    private Dictionary<string, int> sortRank = new Dictionary<string, int>();

   // [SerializeField] GameObject cellGo;
    //[SerializeField] GameObject contentGo;
    [SerializeField] TextMeshProUGUI text;
    [SerializeField] TextMeshProUGUI playerRankText;
    [SerializeField] Canvas rankCanvas;
    private void Awake()
    {
        Core.Initialize();
        //this.score = 0;
        this.score = InfoManager.Instance.GetPlayerInfo().maxScore;
    }
    void Start()
    {
        //GameObject go = Instantiate(this.cellGo, this.contentGo.transform);//scrollview의 content의 자식으로 cell생성
        Oculus.Platform.Entitlements.IsUserEntitledToApplication().OnComplete(EntitlementCallback);
        this.getLoggedInUser();
     
            this.onGetUserName = () => {
           
            var reference = FirebaseDatabase.DefaultInstance.RootReference;
             
            reference.Child("rank").OrderByChild("name").EqualTo(this.OculusUserName).GetValueAsync().ContinueWith(task =>
            {
                if (task.IsCompleted)
                {
                    DataSnapshot snapshot = task.Result;
                    if (snapshot.Exists)
                    {//이미 user의 기록이 존재하는 경우
                        Debug.Log("user score exist");
                        foreach (var data in snapshot.Children)
                        {
                            IDictionary rank = (IDictionary)data.Value;
                            int databaseScore = int.Parse(rank["score"].ToString());

                            //  if (databaseScore <= this.score)
                            {//새로운 점수가 기록된 점수보다 값이 크다면                              
                             // Debug.Log(rank["score"]);
                                Rank rank2 = new Rank(this.OculusUserName, this.score);
                                string json = JsonUtility.ToJson(rank2);//데이터를 json형태로 반환
                                string key = data.Key;
                                //root의 자식rank에 key 값 추가
                                reference.Child("rank").Child(key).SetRawJsonValueAsync(json);//생성된 키의 자식으로 json 데이터를 삽입
                                                                                              //
                                                                                              // Debug.Log("이름: " + rank["name"] + ", 점수: " + rank["score"]);
                            }
                        }
                    }
                    else
                    {
                        Debug.Log("user's record doesn't exist!");
                        Rank rank = new Rank(this.OculusUserName, score);
                        string json = JsonUtility.ToJson(rank);//데이터를 json형태로 반환
                        string key = reference.Child("rank").Push().Key;
                        //root의 자식rank에 key 값 추가
                        reference.Child("rank").Child(key).SetRawJsonValueAsync(json);//생성된 키의 자식으로 json 데이터를 삽입
                    }
                }

            });


        };

        this.onLoadFirebaseData = () =>
        {
           // string str = "";
            var query = FirebaseDatabase.DefaultInstance.GetReference("rank").OrderByChild("score");          
            query.GetValueAsync().ContinueWith(task =>
            {
                
                if (task.IsCompleted)
                { // 성공적으로 데이터를 가져왔으면
                    Debug.Log("loaded");
                    DataSnapshot snapshot = task.Result;
                    // 데이터를 출력하고자 할때는 Snapshot 객체 사용함
                    int count = (int)snapshot.ChildrenCount+1;
                    //string leaderboardData = "";
                    foreach (DataSnapshot data in snapshot.Children)
                    {
                        IDictionary rank = (IDictionary)data.Value;
                        count--;
                        Debug.LogFormat("이름: {0},점수: {1}, count:{2}", rank["name"], rank["score"],count);
                        //  StartCoroutine(this.CoWait());
                        string username = rank["name"].ToString();
                        string score = rank["score"].ToString();
                        this.leaderboardData += "  "+count+"  이름: "+username+"  점수: "+score+"\n\n";

                        if (this.OculusUserName == rank["name"].ToString())
                        {
                            this.rank = count;
                            Debug.LogFormat("Player's rank is {0}", this.rank);
                            this.playerRankData += "Your Rank  :  "+count;
                            //GameObject go = Instantiate(this.cellGo, this.contentGo.transform);//scrollview의 content의 자식으로 cell생성
                        }
                    }
                }
            });
        };
        this.onRankActive = () => {
            this.rankCanvas.gameObject.SetActive(true);
            StartCoroutine(this.CoLoadData());
        };
    }
    private IEnumerator CoLoadData()
    {
        yield return new WaitForSeconds(0f);
        this.onLoadFirebaseData();
       
    }
    private void SortRank()
    {
        var sortRank = this.sortRank.OrderByDescending(rank => rank);
        foreach(var data in sortRank)
        {
            Debug.LogFormat("Data: {0}",data.Key);
        }
        //Debug.Log()
    }
    void Update()
    {
        this.text.text = this.leaderboardData;
        this.playerRankText.text = this.playerRankData;

        
    }
    void getLoggedInUser()
    {
        Users.GetLoggedInUser().OnComplete(getUserCallback);
     


    }
    void getUserCallback(Message<User> msg)
    {
        if (!msg.IsError)
        {
             User user = msg.Data;
           
            Debug.LogFormat("UserID: {0},DisplayName : {1}",user.ID,user.DisplayName);
            this.OculusUserName = user.DisplayName;
            Users.Get(msg.Data.ID).OnComplete(message =>
            {
                if (!message.IsError)
                {
                    Oculus.Platform.Models.User user = message.GetUser();
                    this.OculusUserName = user.DisplayName;
                    Debug.LogFormat("UserID2: {0},DisplayName : {1}", user.ID, user.DisplayName);
                    this.onGetUserName();
                    //StartCoroutine(this.CoLoadData());
                }
                else
                {
                    var e = message.GetError();
                }
            });

        }
        else
        {
            Error error = msg.GetError();
        }
    }
    void EntitlementCallback(Message msg)
    {
        if (msg.IsError) // User failed entitlement check
        {
            // Implements a default behavior for an entitlement check failure -- log the failure and exit the app.
            Debug.LogError("You are NOT entitled to use this app.");
            UnityEngine.Application.Quit();
        }
        else // User passed entitlement check
        {
            // Log the succeeded entitlement check for debugging.
            Debug.Log("You are entitled to use this app.");
        }
    }
}

 

 

myoskin