[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 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