[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 100% 환급 챌린지 27회차 미션 시작합니다.

03. Firebase 네트워크 - 05-2, 05-3 챕터를 진행합니다.


지난 시간에 이어 사용자 데이터/저장 불러오기 준비단계입니다.
Newtonsoft Json Library에 대한 소개로 시작합니다.




JsonUtility
  - Unity에 포함된 Json Library를 사용하는 것이 일반적입니다.
  - 최소한의 기능만 제공하고 있지만, Unity만을 위한 Vector serialize와 MonoBehaviour serialize를 제공합니다.
  - MonoBehaviour의 Deserialize는 별도의 제공함수인 FromJsonOverwrite()를 사용해야 합니다.

Newtonsoft Json
  - JsonUtility에서 제공되지 않는 추가적인 Json 기능들을 사용할 수 있는 외부 Library
  - BSON 지원 : Binary JSON
  - MonoBehaviour와 Vector에 대한 지원 클래스가 없으므로 별도로 설정하여 사용 필요.
  
추가적으로 JsonUtility는 Property에 대한 Serialize를 지원하지 않는데, Newtonsoft JSON은 Property Serialize가 동작하게 되므로, Property Json 변환을 원하지 않는다면 설정되지 않도록 Attribute를 설정하는 기능이 필요합니다.




Newtonsoft Json 다운로드 및 설정 방법을 정리하였습니다.

1) 다운로드
2) 압축해제하여 폴더 확인
3) Newtonsoft.Json.dll을 드래그하여 Unity Assets 폴더에 드랍하여 설정하면 됩니다.

여기서는 Newtonsoft Json을 사용하여 진행합니다.



Json 함수에 대한 설명입니다.




Json을 사용하는 테스트를 위해 UI를 미리 구현하였고, 나머지는 기존과 동일하며 [Save User Data] [Load User Data]가 추가되었습니다.
이를 클릭하면 User Data를 불러오고 저장하는 기능을 하게 됩니다.

실제 게임에서는 로그인을 하면 바로 Load User Data를 호출하여 사용자 데이터를 가져오면 되고,
사용자 데이터가 변경될 때마다 Save User Data를 호출하여 Firebase database에 데이터를 저장하는 루틴으로 구성하면 되겠습니다.



스크립트 기능 구현을 시작합니다.


public class PlayerDataHandler : MonoBehaviour
{
    private DatabaseReference databaseRef;
    private string UserDataPath => "users"; // 루트 폴더
    private string StatsDataPath => "stats"; // /users/uid/stats
    private string EquipmentDataPath => "equipment";
    private string InventoryDataPath => "inventory";
    
    public StatsObject playerStats;
    public InventoryObject playerEquipment;
    public InventoryObject playerInventory;
    
    void Start() {
        databaseRef = FirebaseDatabase.DefaultInstance.RootReference;
    }
    
    public void OnClickedSave() {
        var userId = FirebaseAuthManager.Instance.UserId;
        if (userId == string.Empty) return;
        
        string statsJson = playerStats.ToJson();
        databaseRef.Child(UserDataPath).Child(userId).Child(StatsDataPath).SetRawJsonValueAsync(statsJson).ContinueWith(task => {
            if (task.IsCanceled) { Debug.LogError("Save user data was canceled"); return; }
            if (task.IsFaulted) { Debug.LogError("Save user data encountered an error: " + task.Exception); return; }
            Debug.LogFormat("Save user data in successfully: {0} {1}", userId, statsJson);
        }); // users/uid/stats
       
        string equipmentJson = playerEquipment.ToJson();
        databaseRef.Child(UserDataPath).Child(userId).Child(EquipmentDataPath).SetRawJsonValueAsync(equipmentJson).ContinueWith(task => {
            if (task.IsCanceled) { Debug.LogError("Save equipment data was canceled"); return; }
            if (task.IsFaulted) { Debug.LogError("Save equipment data encountered an error: " + task.Exception); return; }
            Debug.LogFormat("Save equipment data in successfully: {0} {1}", userId, equipmentJson);
        });
       
        string inventoryJson = playerInventory.ToJson();
        databaseRef.Child(UserDataPath).Child(userId).Child(InventoryDataPath).SetRawJsonValueAsync(inventoryJson).ContinueWith(task => {
            if (task.IsCanceled) { Debug.LogError("Save inventory data was canceled"); return; }
            if (task.IsFaulted) { Debug.LogError("Save inventory data encountered an error: " + task.Exception); return; }
            Debug.LogFormat("Save inventory data in successfully: {0} {1}", userId, inventoryJson);
        });
    }
    
    public void OnClickedLoad() {
        var userId = FirebaseAuthManager.Instance.UserId;
        if (userId == string.Empty) return;
        
        databaseRef.Child(UserDataPath).Child(userId).Child(StatsDataPath).GetValueAsync().ContinueWith(task => {
            if (task.IsCanceled) { Debug.LogError("Load user data was canceled"); return; }
            if (task.IsFaulted) { Debug.LogError("Load user data encountered an error: " + task.Exception); return; }
            
            DataSnapshot snapshot = task.Result;
            // snapshot.Child(userId).Child(StatsDataPath).Child("level").GetValue .... // Json 미사용시 처리.. ㅋㅋ
            playerStats.FromJson(snapshot.GetRawJsonValue());
            Debug.LogFormat("Load user data in successfully: {0} {1}", userId, snapshot.GetRawJsonValue());
        });
        
        databaseRef.Child(UserDataPath).Child(userId).Child(EquipmentDataPath).GetValueAsync().ContinueWith(task => {
            if (task.IsCanceled) { Debug.LogError("Load equipment data was canceled"); return; }
            if (task.IsFaulted) { Debug.LogError("Load equipment data encountered an error: " + task.Exception); return; }
            
            DataSnapshot snapshot = task.Result;
            playerEquipment.FromJson(snapshot.GetRawJsonValue());
            Debug.LogFormat("Load equipment data in successfully: {0} {1}", userId, snapshot.GetRawJsonValue());
        });
        
        databaseRef.Child(UserDataPath).Child(userId).Child(InventoryDataPath).GetValueAsync().ContinueWith(task => {
            if (task.IsCanceled) { Debug.LogError("Load inventory data was canceled"); return; }
            if (task.IsFaulted) { Debug.LogError("Load inventory data encountered an error: " + task.Exception); return; }
            
            DataSnapshot snapshot = task.Result;
            playerInventory.FromJson(snapshot.GetRawJsonValue());
            Debug.LogFormat("Load inventory data in successfully: {0} {1}", userId, snapshot.GetRawJsonValue());
        });
    }
}



// 기존 게임 개발시 클래스로서 추가된 코드만 작성하였습니다.
public class StatsObject : ScriptableObject
{
    // 기존코드들..
    
    public string ToJson() {
        return JsonConvert.SerializeObject(levelData, Formatting.Indented);
    }
    
    public void FromJson(string jsonString) {
        PlayerLevelData newLevelData = JsonConvert.DeserializeObject<PlayerLevelData>(jsonString);
        Level = newLevelData.level;
        Exp = newLevelData.exp;
    }
}

public class InventoryObject : ScriptableObject
{
    // 기존코드들..
    
    public string ToJson() {
        return JsonConvert.SerializeObject(container, Formatting.Indented);
    }
    
    public void FromJson(string jsonString) {
        Inventory newContainer = JsonConvert.DeserializeObject<Inventory>(jsonString);
        Debug.Log("from json: " + newContainer.slots.Length);
        
        for (int i = 0; i < Slots.Length; i++) {
            Slots[i].UpdateSlot(newContainer.slots[i].item, newContainer.slots[i].amount);
        }
    }
}


이렇든 Json을 사용하면 다층화된 데이터 구조를 가지는 구조 데이터를 간결하게 구현할 수가 있습니다.





이제 Unity 플레이를 하여 로그인하고,
[Save User Data]를 진행하여 다음과 같이 Firebase Database에 저장된 데이터를 확인할 수 있습니다.




마찬가지로 Database 내에서 데이터 정보를 강제로 수정을 하고, 유니티에서 [Load User Data]를 진행하면 변경된 정보를 가져와서 업데이트되는 것을 확인할 수 있습니다.


복잡하고 어려운 듯한 강의였지만,

결론적으로 회원 가입 및 사용자 데이터 관리 등의 복잡한 기능을 해주는 Firebase를 잘 활용하자! 이고..
Json이라는 엄청난 Library를 이용하여 복잡하고 다계층화된 데이터의 접근 처리를 쉽게할 수 있다! 라고 결론지으면 되겠습니다.

오래전에 DataManager라는 동일한 기능을 하는 클래스를 만들어서 썼었는데.. Json을 썼으면 되는 거였네요.. ^^;;; 무지로 인한 고생인거였겠죠.. ㅎㅎ




<위의 코드들은 제가 보면서 주요한 함수, 코드를 확인하기 위해 타이핑한 용도로, 전체 소스코드가 아님에 주의해 주세요. 전체 코드는 교육 수강을 하면 완벽하게 받으실 수가 있답니다 ^^>

패스트캠퍼스 - 올인원 패키지 : 유니티 포트폴리오 완성 bit.ly/2R561g0

 

유니티 게임 포트폴리오 완성 올인원 패키지 Online. | 패스트캠퍼스

게임 콘텐츠 프로그래머로 취업하고 싶다면, 포트폴리오 완성은 필수! '디아블로'와 '배틀그라운드' 게임을 따라 만들어 보며, 프로그래머 면접에 나오는 핵심 개념까지 모두 잡아 보세요!

www.fastcampus.co.kr

 

[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 100% 환급 챌린지 26회차 미션 시작합니다.

03. Firebase 네트워크 - 04-3, 05-1 챕터를 진행합니다.



저번 시간에 이어 LeaderboardController를 구현합니다.
금번 교육 내용만 작성하였습니다.




핵심은 score를 기준으로 데이터를 가져와서 정렬하여 처리하는 것입니다.

public class LeaderboardController : MonoBehaviour
{
    // 기존코드들..
    
    private bool sendUpdatedLeaderboardEvent = false;
    private event EventHandler<LeaderboardArgs> OnUpdatedLeaderboard;
    
    void Update() {
        // 기존 코드들..
        
        if (sendRetrievedScoreEvent) {
            sendRetrievedScoreEvent = false;
            OnRetrievedScore(this, retrievedScoreArgs);
        }
        
        if (sendUpdatedLeaderboardEvent) {
            sendUpdatedLeaderboardEvent = false;
            OnUpdatedLeaderboard(this, new LeaderboardArgs {
                scores = topScores,
                startDate = -1,
                endDate = -1
            });
        }
    }
    
    private bool gettingUserScore = false;
    public int GetUserScore(string userId) {
        gettingUserScore = true;
        databaseRef.Child(AllScoreDataPath) // 해당 Path에서
            .OrderByChild(UserScore.userIdPath) // Id Path 기준
            .StartAt(userId) // 시작부터
            .EndAt(userId) // 끝까지 취합
            .GetValueAsync().ContinueWith(task => {
                if (task.Exception !=) throw task.Exception;
                if (!task.IsCompleted) return;
                
                if (task.Result.Children == 0) {
                    retrievedScoreArgs = new UserScoreArgs(null, string.Format("No Scores for User {0}", userId);
                }
                else {
                    var scores = ParseValidUserScoreRecords(task.Result, -1, -1).ToList(); // 시간 내에 있는지
                    if (scores.Count == 0) {
                        retrievedScoreArgs = new UserScoreArgs(null, string.Format("No Scores for User {0} within time range {1} ~ {2}", userId, startDate, endDate);
                    }
                    else {
                        var orderedScored = scores.OrderBy(score => score.score);
                        var userScore = orderedScored.Last(); // 가장 큰값
                        retrievedScoreArgs = new UserScoreArgs(userScore, userScore.userId + " Retrieved!");
                    }
                }
                
                gettingUserScore = false;
                sendRetrievedScoreEvent = true; // Update에서 Event 발생.
            });
    }
    
    private List<UserScore> ParseValidUserScoreRecords(DataSnapShot snapshot, long startTicks, long endTicks) {
        return snapshot.Children
            .Select(scoreRecord => UserScore.CreateScoreFromRecord(scoreRecord))
            .Where(score => score != null && score.timestamp > startTicks && score.timestamp <= endTicks)
            .Reverse()
            .ToList();
    }
   
    
    private bool gettingTopScores = false;
    private void GetInitialTopScores() {
        gettingTopScores = true;
        var query = databaseRef.Child(AllScoreDataPath).OrderByChild("score");
        query = query.LimitToLast(20); // 20개
        query.GetValueAsync().ContinueWith(task => {
            if (task.Exception != null) { return; } // error
            if (!task.IsCompleted || !task.Result.HasChildren) return; // error
            
            var scores = ParseValidUserScoreRecords(task.Result, -1, -1);
            foreach (var userScore in scores) {
                if (!userScores.Contains(userScore.userId)) { // 기존값 존재하지 않는다면
                    userScores[userScore.userId] = userScore;
                }
                else { // 기존값 존재한다면
                    if (userScores[userScore.userId].score < userScore.score)
                        userScores[userScore.userId] = userScore; // score 갱신
                }
            }
            
            SetTopScores();
        });
    }
    
    private void SetTopScores() {
        topScores.Clear();
        topScores.AddRange(userScores.Values.OrderbyDecending(score => score.score));
        sendUpdatedLeaderboardEvent = true;
        gettingTopScores = false;
    }
}






Canvas에 "Leaderboard Handler"와 "Leaderboard UI Controller"를 Attach 시켰습니다.
플레이를 진행합니다.



화면과 같이 user_id, username, score를 입력하고 [Add] [Get User Score] [Update] 등의 버튼을 클릭하여, 사용자의 Score를 입력, 최대점수 얻기, Score 갱신 등을 테스트할 수 있습니다.

그리고 오른쪽의 [Get Leaderboard]를 클릭하면 최고점수의 user 부터 표시되는 것을 확인할 수 있습니다.

소스코드 예외 사항에 대한 처리는 동영상에는 없지만, 강의 자료에는 포함되어 있으므로 해당 소스를 보면 좀더 파악하기 좋을 것입니다.

여기까지 하여 기본적인 함수들을 이용하여 Leaderboard의 구현이 완료되었습니다.





이제 사용자의 데이터를 저장/불러오기 작업입니다.

firebase의 database 보안규칙

auth = 인증된 사용자 토큰.






권한 규칙을 설정할 수 있습니다.

"규칙 플레이그라운드"를 통해서 읽기/쓰기 권한에 대한 테스트를 해볼 수 있습니다.

인증이 된 경우에만 읽기/쓰기 작업이 되는 것 등을 확인할 수 있습니다.
또한 인증은 되었지만 다른 아이디로 테스트를 하는 경우에도 보안 규칙이 적용되어 에러가 발생하는 것 등을 확인할 수 있습니다.





위의 내용은 데이터 검증과 색인에 대한 내용입니다. 권한 규칙과 비슷해보이지만 다른 내용입니다. ㅎㅎ

.validate - 데이터가 올바른 형식인지 등
.indexOn - 개발상에서는 설정하지 않지만 Release시에는 규모가 커져도 정상 작동하도록 색인화를 하게됨. Database 용량이 많이 켜져도 쿼리 성능을 유지하기 위한 것이라고 보면 되겠습니다.




<위의 코드들은 제가 보면서 주요한 함수, 코드를 확인하기 위해 타이핑한 용도로, 전체 소스코드가 아님에 주의해 주세요. 전체 코드는 교육 수강을 하면 완벽하게 받으실 수가 있답니다 ^^>

패스트캠퍼스 - 올인원 패키지 : 유니티 포트폴리오 완성 bit.ly/2R561g0

 

유니티 게임 포트폴리오 완성 올인원 패키지 Online. | 패스트캠퍼스

게임 콘텐츠 프로그래머로 취업하고 싶다면, 포트폴리오 완성은 필수! '디아블로'와 '배틀그라운드' 게임을 따라 만들어 보며, 프로그래머 면접에 나오는 핵심 개념까지 모두 잡아 보세요!

www.fastcampus.co.kr

 

[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 100% 환급 챌린지 25회차 미션 시작합니다.

03. Firebase 네트워크 - 04-1, 04-2 챕터를 진행합니다.




leaderboard를 구현하려면 역시 Firebase를 설정해 주어야 합니다.

Firebase에는 아직 Unity에 대한 매뉴얼 등의 자료가 충분하지 않아 초반 설정 및 사용에 어려움이 있을 수 있습니다.

Firebase - Database - [Realtime 데이터베이스 만들기]

+ 잠금 모드로 시작 - read / write를 반드시 인증하여야 사용 가능
+ 테스트 모드로 시작 - 누구나 접근이 가능



Realtime 데이터베이스 만들기를 클릭하면 위와 같이 Database가 생성된 것을 확인할 수 있습니다.
현재는 테스트 모드로 진행을 하고 추후 접근 권한 설정은 다시 설명합니다.




이제 Unity에서 Install Package..를 진행하여 dotnet4 - FirebaseDatabase를 설치해줍니다.




FirebaseDatabase의 주요 함수들입니다.

Unity 에디터에서 Database에 접근하여 사용하기 위해 SetEditorDatabaseUrl()을 호출하여 사용하는데 이때 전달해야 하는 URL 정보는 google-services-desktop.json에 저장된 "firebase_url"과 동일해야 합니다.




위와 같이 잘 설정되어 있다면, 해당 URL 정보는 FirebaseApp.DefaultInstance.Options.DatabaseUrl에 저장된다는 내용입니다.



사용되는 함수에 대한 지속 설명입니다.

데이터 읽기, 변경 이벤트 등록, 정렬, 필터링 등 다양한 데이터에 접근하여 사용할 수 있는 함수들이 제공된다고 보면 됩니다.



Unity에서 구현할 Script classes 구조 설명입니다.

+ UserScore - User의 Score값을 저장하는 하나의 단위이며, FirebaseDatabase를 구성하는 한 단위입니다.
+ LeaderboardHandler - topScores 정보를 가지고 있고, Thread based 함수를 사용하여 Firebase Database에 접근합니다.
   또한 Firebase의 Database에는 사용자 순위 정보가 시간순으로 되어 있기 때문에 LeaderboardHandler에서 순위별 정렬 등도 처리합니다.


스크립트들을 구현합니다.

[Serializable]
public class UserScore_New
{
    public static string userIdPath = "user_id";
    public static string userNamePath = "username";
    public static string scorePath = "score";
    public static string timestampPath = "timestamp";
    public static string otherDataPath = "data";
    
    public string userId;
    public string userName;
    public long score;
    public long timestamp;
    public Dictionary<string, object> otherData;
    
    public UserScore_New(string userId, string userName, long score, long timestamp, Dictionary<string, object>otherData = null) {
        this.userId = userId;
        this.userName = userName;
        this.score = score;
        this.timestamp = timestamp;
        this.otherData = otherData;
    }
   
    // Firebase에서 전달받은 Record로 처리
    public UserScore_New(DataSnapshot record) {
        // record는 database 처리의 한 단위
        userId = record.Child(userIdPath).Value.ToString();
        if (record.Child(userNamePath).Exists) userName = record.Child(userNamePath).Value.ToString();
        long score;
        if (Int64.TryParse(record.Child(scorePath).Value.ToString(), out score)) this.score = score;
        else this.score = Int64.MinValue;
        long timestamp;
        if (Int64.TryParse(record.Child(timestampPath).Value.ToString(), out timestamp)) this.timestamp = timestamp;
        if (Int64.TryParse(record.Child(otherDataPath).Exists && record.Child(otherDataPath).HasChildren) {
            this.otherData = new Dictionary<string, object>();
            foreach (var keyValue in record.Child(otherDataPath).Children) {
                otherData[keyValue.Key] = keyValue.Value;
            }
        }
    }
    
    public static UserScore CreateScoreFromRecord(DataSnapshot record) {
        if (record == null) { Debug.LogWarning("Null DataSnapshot record in UserScore.CreateScoreFromRecord"); return null; }
        if (record.Child(userIdPath).Exists && record.Child(scorePath).Exists && record.Child(timestampPath).Exists) {
            return new UserScore(record);
        }
        Debug.ogWarning("Invalid record format in UserScore.CreateScoreFromRecord");
        return null;
    }
    
    // json to dictionary
    public Dictionary<string, object> ToDictionary() {
        return new Dictionary<string, object>() {
            {userIdPath, userId },
            {userNamePath, userName },
            {scorePath, score },
            {timestampPath, timestamp },
            {otherDataPath, otherData }
        };
    }
}




public static class FirebaseInitializer_New
{
    private static List<Action<DependencyStatus>> initializeCallbacks = new List<Action<DependencyStatus>>();
    private static DependencyStatus dependencyStatus;
    
    private static bool initialized = false;
    private static bool fetching = false;
    private static bool activateFetch = false;
    
    public static void Initialize(Action<DependencyStatus> callback) {
        lock (initializeCallbacks) {
            if (initialized) {
                callback(DependencyStatus);
                return;
            }
            
            initializeCallbacks.Add(callback);
            FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(task => {
                lock (initializeCallbacks) {
                    dependencyStatus = task.Result;
                    initialized = true;
                    CallInitializedCallbacks();
                }
            });
        }
    }
    
    private static void CallInitializedCallbacks() {
        lock (initializeCallbacks) {
            foreach (var callback in initializeCallbacks) {
                callback(dependencyStatus);
            }
            initializeCallbacks.Clear();
        }
    }
}





public class UserScoreArgs: EventArgs
{
    // User 1명에 대한 읽기, 쓰기 이벤트
    public UserScore score;
    public string message;
    public UserScoreArgs(UserScore score, string message) {
        this.score = score;
        this.message = message;
    }
}
public class LeaderboardArgs: EventArgs
{
    // User Score에 대한 읽기, 쓰기 이벤트
    pubic DataTime startDate;
    public DataTime endDate;
    public List<UserScore> scores;
}
public class LeaderboardController : MonoBehaviour
{
    private bool initialized = false;
    private bool readytoInitialize = false;
    private DatabaseReference = databaseRef;
    public string AllscoreDataPath => "all_scores"; // Firebase의 root
    
    pubic event EventHandler OnInitialized;
    
    private bool addingUserScore = false;
    private bool sendAddedScoreEvent = false;
    private UserScoreArgs addedScoreArgs;
    private event EventHandler<UserScoreArgs> OnAddedScore;
    
    privte void Start() {
        FirebaseInitializer_New.Initialize(dependencyStatus => {
            if (dependencyStatus == Firebase.DependencyStatus.Available) {
                readytoInitialize = true;
                InitializeDatabase();
            }
            else
                Debug.LogError("Could not resolve all Firebase dependencies: " + dependencyStatus);
        });
    }
   
    private void InitializeDatabase() {
        if (initialized) return;
        FirebaseApp app = FirebaseApp.DefaultInstance;
        if (app.Options.DatabaseUrl != null)
            app.SetEditorDatabaseUrl(app.Options.DatabaseUrl);
        databaseRef = FirebaseDatabase.DefaultInstance.RootReference;
        initialized = true;
        readytoInitialize = false;
        OnInitialized(this, null);
    }
    
    void Update() {
        if (sendAddedScoreEvent) { // user가 점수 쓰기 완료.
            sendAddedScoreEvent = false;
            OnAddedScore(this, addedScoreArgs);
        }
    }
   
    public Task AddScore(string userId, string userName, int score, long timestamp = 1L, Dictionary<string, object> otherData = null) {
        if (timestamp <= 0) timestamp = DateTime.UtcNow.Ticks / TimeSpan.TicksPerSecond; // Unity TactTime -> Unix TactTime
        
        var userScore = new UserScore(userId, userName, score, timestamp, otherData);
        return AddScore(userScore);
    }
   
    pubic Task<UserScore> AddScore(UserScore userScore) {
        if (addingUserScore) { Debug.LogError("Running add user score task!"); return null; }
        
        var scoreDictionary = userScore.ToDictionary();
        addingUserScore = true;
        
        return Task.Run(() => {
            var newEntry = databaseRef.Child(AllscoreDataPath).Push();
            return newEntry.SetValueAsync(scoreDictionary).ContinueWith(task => {
                if (task.Exception != null) { Debug.LogWarning("Exception adding score: " + task.Exception); return null; }
                if (!task.IsCompleted) { return null; }
                addingUserScore = false;
                addedScoreArgs = new UserScoreArgs(userScore, userScore.userId + " Added!");
                sendAddedScoreEvent = true;
                return userScore;
            }).Result;
        });
    }
    
    private bool gettingUserScore = false;
    public int GetUserScore(string userId) {
        gettingUserScore = true;
        databaseRef.Child(AllscoreDataPath)
            .OrderByChild(UserScore.userIdPath)
            .StartAt(userId)
            .EndAt(userId)
            .GetValueAsync().ContinueWith(task => {
                if (task.Exception !=) throw task.Exception;
                if (!task.IsCompleted) return;
            });
    }
}


오늘도 어려운 내용을 후루룩 머리속에 집어넣느라고 굳은 머리가 고생하네요.
이거 초기에 설정잡고 연동하고, 이벤트 처리 로직 등 할 때에는 고생했을 것 같네요 ㅠ.
잘 된 구조를 만들어 주셨으니 잘 가져다 쓰면 되겠습니다. ㅎㅎ





Firebase내 Database에 저장된 데이터의 구조입니다.


다음 시간에 leaderboard 마무리 및 사용자 데이터 처리에 대해 진행합니다.




<위의 코드들은 제가 보면서 주요한 함수, 코드를 확인하기 위해 타이핑한 용도로, 전체 소스코드가 아님에 주의해 주세요. 전체 코드는 교육 수강을 하면 완벽하게 받으실 수가 있답니다 ^^>

패스트캠퍼스 - 올인원 패키지 : 유니티 포트폴리오 완성 bit.ly/2R561g0


 

유니티 게임 포트폴리오 완성 올인원 패키지 Online. | 패스트캠퍼스

게임 콘텐츠 프로그래머로 취업하고 싶다면, 포트폴리오 완성은 필수! '디아블로'와 '배틀그라운드' 게임을 따라 만들어 보며, 프로그래머 면접에 나오는 핵심 개념까지 모두 잡아 보세요!

www.fastcampus.co.kr

 

+ Recent posts