[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 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
'[컴퓨터] > 웹 | 앱 | 게임 개발' 카테고리의 다른 글
[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 100% 환급 챌린지 27회차 미션 (0) | 2020.11.14 |
---|---|
[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 100% 환급 챌린지 26회차 미션 (0) | 2020.11.13 |
[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 100% 환급 챌린지 24회차 미션 (0) | 2020.11.11 |
[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 100% 환급 챌린지 23회차 미션 (0) | 2020.11.10 |
[패스트캠퍼스 수강 후기] 올인원 패키지 : 유니티 포트폴리오 완성 100% 환급 챌린지 22회차 미션 (0) | 2020.11.09 |