본문 바로가기
공허의 유산/표현의 자유

[백문이 불여일타] Unity용 Firebase 실시간 데이터베이스 시작하기

by 바른생활머시마 2023. 6. 6.
728x90
반응형

NodeJS 기반으로 이것 저것 해보려고 알아보던 중,  Unity 기반으로 멀티플레이로 뭔가 검토 해 볼 것이 있어 Firebase 기반의 Unity 연동에 대해 알아보고자 합니다.

 

일단은 Firebase에서 제공되는 튜토리얼을 한번 살펴 보도록 하겠습니다.

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

 

Unity용 Firebase 실시간 데이터베이스 시작하기

Google I/O 2023에서 Firebase의 주요 소식을 확인하세요. 자세히 알아보기 의견 보내기 Unity용 Firebase 실시간 데이터베이스 시작하기 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장

firebase.google.com

 

제공되는 샘플 앱(MechHamster)도 있는데, 나중에 막혀서 필요하면 보기로 하고, 일단은 튜토리얼 따라서 한번 해보도록 하겠습니다. 

https://github.com/google/mechahamster

 

GitHub - google/mechahamster: Mecha Hamster is a game where you roll through customizable environments that you can share with y

Mecha Hamster is a game where you roll through customizable environments that you can share with your friends. - GitHub - google/mechahamster: Mecha Hamster is a game where you roll through customi...

github.com

 

Firebase Unity SDK

Unity용 SDK 받기 - https://firebase.google.com/download/unity?hl=ko 

Unity용 SDK는 unity package 형태로 되어 있으며, Firebase에서 제공하는 여러가지 서비스별로 분리 되어 있습니다.  SDK는 압축 파일로 되어 있는데 다운로드 받아 압축을 풀면 여러 개의 Firebase 서비스별 unity package가 보입니다.

 

 

Unity 프로젝트에 Firebase 추가

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

 

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

Google I/O 2023에서 Firebase의 주요 소식을 확인하세요. 자세히 알아보기 의견 보내기 Unity 프로젝트에 Firebase 추가 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요

firebase.google.com

위 내용을 보고 따라 해 보기로 합니다. 

 

 

Unity 프로젝트 생성

New project로 새 프로젝트를 만듭니다. 프로젝트 템플릿은 Firebase와 직접 연관이 없고, Firebase와 연동하여 구현 할 Unity application의 성격을 고려하여 템플릿을 선택합니다. 저는 여러 개의 오브젝트가 한 공간 내에 돌아다니는 application을 만들 것이라 3D 템플릿을 선택 합니다.

 

 

SDK를 설치하기 전의 상태는 아래와 같습니다.

 

순서가 좀 애매합니다만, '갑자기' Firebase Project 생성으로 넘어가 보겠습니다. 

갑자기 순서를 변경한 이유는, Firebase Project 생성 과정 중에 Unity SDK 다운로드를 하라는 순서가 있어서 가급적 그 순서대로 진행하려고 급변경 했어요.

 

Project 생성

프로젝트 생성은 아래와 같은 Firebase 콘솔 화면에서 '프로젝트 추가'를 눌러 주면 됩니다.

이제 프로젝트 생성 과정을 진행하는데, 첫번째로 프로젝트 이름을 정합니다.

Firebase Project 생성

다음으로 애널리틱스 적용 옵션 선택 단계인데 여기서 적용하지 않으면, 설정은 끝납니다. 즉, 프로젝트 이름과 애널리틱스 적용 여부만 설정하면 되겠습니다. 적용을 하게 되면, 3단계, 적용하지 않으면 2단계로 진행 됩니다.

만들기 시작하면 혼자서 슥슥슥~~~

자, 다 만들어졌습니다.

이제는 프로젝트의 앱을 추가 하는 단계입니다.

아래 이미지를 보시면 iOS/ Android/ Web이 있고, 그 오른쪽에 Unity와 Flutter가 보입니다. 실제로 만들어지는 앱 형상은 좌측의 3가지이고, 오른쪽은 어떤 환경 기반으로 좌측을 만들 것인지에 사용되는 옵션입니다. 우리 프로젝트의 경우, Unity를 선택할 것인데, 결과적으로는 Unity 기반의 Android앱 프로젝트가 됩니다. 물론, Unity 기반의 iOS도 가능합니다.

여기서, Firebase의 프로젝트와 앱과의 관계를 카카오톡으로 설명하자면,

카카오톡이라는 서비스는 프로젝트이고, 각 OS별 Client가 앱입니다. 

 

아래 내용과 같이, Unity를 선택하면 첫번째 단계에서 Android와 iOS 앱 선택에 대한 옵션이 나옵니다. 저는 android만 해볼 것이라 android만 선택합니다.

다음으로 구성 파일 다운로드를 합니다. 

이 부분이 어렵지는 않지만 매우 중요합니다. 실제 Unity에서 코드를 작성 할 때 내 프로젝트의 정보 등을 입력할 일이 없는데 바로 이 구성 파일을 참조하기 때문입니다. 즉, 프로젝트의 중요한 정보가 포함되어 있으니, 외부에 노출되지 않도록 잘 관리하기도 해야 할 파일이죠. 다운로드 받아서 Assets 폴더에 두라고 나와 있습니다.

아래와 같이~

앞에서 잠깐 봤지만, 여기서 Firebase SDK를 다운로드 받을 수 있고, 설명처럼 원하는 제품의 unity package를 설치하면 됩니다. 저는 Realtime Database를 쓸 예정입니다.

SDK를 다운로드 받아 압축을 풀면 아래와 같이 여러개의 unity package가 보입니다. 제가 사용 할 Realtime Database가 저기 보이네요.

일반적인 unity package 가져오는 것과 같은 방법으로 import 합니다.

가져오고 나서 자동으로 스크립트 빌드가 진행되는데, 오류가 나서 보니, iOS에 대한 내용이네요. 이렇게 뜨더라도 테스트 하는데는 문제가 없으니 그냥 무시하면 됩니다.

자~ 이제 끝났습니다. 

필요하면 샘플 앱을 보라고 하는데, 앞에서 본 메카닉햄스터인 것 같네요.

샘플앱

https://firebase.google.com/docs/samples?hl=ko&authuser=0&_gl=1*1gdj3vt*_ga*MTM0MzM4NDg5NS4xNjgzMDc4Mjgw*_ga_CW55HF8NVT*MTY4NTkyOTQ2NS4xNi4xLjE2ODU5MzA5NTUuMC4wLjA. 

 

Firebase 문서

Google I/O 2022에서 Firebase의 새로운 기능을 확인하세요. 자세히 알아보기 의견 보내기 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. filter_list 필터링 기준 모

firebase.google.com

 

자, 이제 앱 생성이 완료 되었습니다.

아래와 같이 Unity라는 별도의 앱 종류가 있는 것이 아니고, Unity로 만들어진 Android앱으로 등록 되었습니다. 그러면 Desktop 버젼에서는 사용 못하는건가??? 싶은 생각이 드는데, 방법이 있겠죠..

적당한 궁금함이었는데, 문서를 보니 아래쪽에 베타이긴 합니다만, Desktop에 대한 내용도 있네요.

https://firebase.google.com/docs/unity/setup?hl=ko#desktop-workflow 

 

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

Google I/O 2023에서 Firebase의 주요 소식을 확인하세요. 자세히 알아보기 의견 보내기 Unity 프로젝트에 Firebase 추가 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요

firebase.google.com

 

Unity에 연동하는 마지막 단계로 구글 플레이 요구사항 체크를 하라고 하는데...

https://firebase.google.com/docs/unity/setup?hl=ko#confirm-google-play-version 

 

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

Google I/O 2023에서 Firebase의 주요 소식을 확인하세요. 자세히 알아보기 의견 보내기 Unity 프로젝트에 Firebase 추가 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요

firebase.google.com

아래 코드를 어디에 둬야 하는지는 자세히 나와 있지 않네요.

먼저 실행해야 한다고 하니... 일단 Camera의 Start에 두는 걸로..

Firebase.FirebaseApp.CheckAndFixDependenciesAsync().ContinueWith(task => {
  var dependencyStatus = task.Result;
  if (dependencyStatus == Firebase.DependencyStatus.Available) {
    // Create and hold a reference to your FirebaseApp,
    // where app is a Firebase.FirebaseApp property of your application class.
       app = Firebase.FirebaseApp.DefaultInstance;

    // Set a flag here to indicate whether Firebase is ready to use by your app.
  } else {
    UnityEngine.Debug.LogError(System.String.Format(
      "Could not resolve all Firebase dependencies: {0}", dependencyStatus));
    // Firebase Unity SDK is not safe to use here.
  }
});

스크립트 입력도 할 겸, 필요한 다른 것도 좀 해두도록 하겠습니다.

먼저 Android로 플랫폼을 변경하고, Scene도 추가합니다.

그랬더니, 처음 보는 화면이었는데, 뭘 Enable/ Disable 선택하라고 하는데,  Disable이면 수동...

 일단 Enable로 선택하니 뭐가 한참 진행 되었습니다.

스크립트 폴더에 스크립트 파일 생성하여 카메라에 할당합니다.

코드를 넣어보니 app이 미리 정의 되어 있어야 하고, app의 정체에 대해서는 별 다른 설명이 없었으니 나중에 만들어 보는걸로하고 이건 주석 처리해둡니다.

 

 

Firebase Realtime Database 

 

Firebase console로 가서 RealtimeDatabase를 선택하고, '데이터베이스 만들기'를 누릅니다. 

데이터베이스 설정도 특별히 어려울 것은 없습니다. 위치는 미국으로~..

싱가폴이 가깝긴한데....전에 무슨 과금인지 뭔지 미국 것만 무료로 쓸 수 있다는 뭔가를 본 적이 있는데 그냥...

다음으로 초기 보안 설정을 선택하는 화면인데, 테스트 모드에서는 권한이 없어도 데이터에 접근이 가능합니다. 이게 문제가 많았는지 기한을 한시적으로 정하도록 만들어 지내요. 이 기간은 나중에 변경 가능하며, 당연하게도 조건에 따라 접근 권한을 통제 할 수 있습니다.

데이터베이스가 만들어지고 나면 설정에 들어가서 여러가지 내용을 볼 수 있는데, '규칙'에 들어가 보면 앞에서 설정한 권한도 볼 수 있습니다.

d

 

아래 나오는 내용은, 데이터의 정의/읽기/쓰기 등에 관한 내용인데, 내용이 많습니다.

그래서, 간단히 주제 정도만 체크하고 넘어가고 실제 구현을 해 보면서 확인 하도록 하겠습니다.

 

 

 

데이터베이스 구조화

https://firebase.google.com/docs/database/unity/structure-data?hl=ko 

 

데이터베이스 구조화  |  Firebase 실시간 데이터베이스

Google I/O 2023에서 Firebase의 주요 소식을 확인하세요. 자세히 알아보기 의견 보내기 데이터베이스 구조화 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 시작

firebase.google.com

 

 

DB 만드는건 앞에서 했고, 이제 데이터에 대해서 알아보겠습니다.

 

JSON으로 저장, NoSQL

데이터 중, 데이터 고유의 ID는 직접 지정 할 수도 있고, 데이터 추가만 하고 자동으로 ID를 할당 받을 수도 있고. 

Depth를 너무 깊게 하면 불필요한 다른 데이터도 함께 끌려오기 때문에 가급적 중첩은 피하도록 하라고 하네요. 이런 특성은 NoSQL에 대한 이해가 도움이 될 것 같습니다. 요약하면, 필요한 데이터만 최소한으로 가져 올 수 있도록 스키마 모델링을 잘해야 한다는 이야기...

 

데이터 저장

https://firebase.google.com/docs/database/unity/save-data?hl=ko#saving_data 

 

데이터 저장  |  Firebase 실시간 데이터베이스

Google I/O 2023에서 Firebase의 주요 소식을 확인하세요. 자세히 알아보기 의견 보내기 데이터 저장 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 시작하기 전에

firebase.google.com

 

 

저장에 사용하는 메쏘드는 다음 5가지 뿐이므로 간단히 사용 할 수 있을 것 같아 보입니다...만, 항상 문제는 엉뚱한 곳에서 생기죠. 그래도 메쏘드 개수가 적다는 것은 장점이니~~~

 

 

DatabaseReference 가져오기

https://firebase.google.com/docs/database/unity/save-data?hl=ko#get_a_databasereference 

 

데이터 저장  |  Firebase 실시간 데이터베이스

Google I/O 2023에서 Firebase의 주요 소식을 확인하세요. 자세히 알아보기 의견 보내기 데이터 저장 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 시작하기 전에

firebase.google.com

데이터베이스의 데이터를 코드를 통해서 다루려면 뭔가 데이터 베이스에 해당하는 객체가 있어야 합니다. 이것을 레퍼런스라고 표현했고, 이것을 얻어오는 방법입니다. 간단하죠? 이것이 있어야 읽고 쓰고 할 수 있으니 필수적이라 할 수 있습니다.

using Firebase;
using Firebase.Database;

public class MyScript: MonoBehaviour {
  void Start() {
    // Get the root reference location of the database.
    DatabaseReference reference = FirebaseDatabase.DefaultInstance.RootReference;
  }
}

 

데이터 저장 메쏘드

SetValueAsync - 일반적인 타입 데이터를 저장 할 수 있는 메쏘드 입니다. 

형식이 정해진 객체는 객체 정보를JsonUtility.ToJson() 으로 JSON 으로 만들고. SetRawJsonValueAsync()  을 이용하여 JSON으로 직접 저장

 

참고 코드

public class User {
    public string username;
    public string email;

    public User() {
    }

    public User(string username, string email) {
        this.username = username;
        this.email = email;
    }
}

private void writeNewUser(string userId, string name, string email) {
    User user = new User(name, email);
    string json = JsonUtility.ToJson(user);

    mDatabaseRef.Child("users").Child(userId).SetRawJsonValueAsync(json);
}

private void updateUserName(string userId, string name) {
	mDatabaseRef.Child("users").Child(userId).Child("username").SetValueAsync(name);
}

 

데이터를 추가 할 때 시스템에서 부여하는 ID(Key)를 사용할 수도 있습니다.

업데이트 할 때는 UpdateChildrenAsync()를 이용하여 그 하위의 데이터를 한꺼번에 처리 할 수도 있습니다.

public class LeaderboardEntry {
    public string uid;
    public int score = 0;

    public LeaderboardEntry() {
    }

    public LeaderboardEntry(string uid, int score) {
        this.uid = uid;
        this.score = score;
    }

    public Dictionary<string, Object> ToDictionary() {
        Dictionary<string, Object> result = new Dictionary<string, Object>();
        result["uid"] = uid;
        result["score"] = score;

        return result;
    }
}

private void WriteNewScore(string userId, int score) {
    // Create new entry at /user-scores/$userid/$scoreid and at
    // /leaderboard/$scoreid simultaneously
    string key = mDatabase.Child("scores").Push().Key;
    LeaderBoardEntry entry = new LeaderBoardEntry(userId, score);
    Dictionary<string, Object> entryValues = entry.ToDictionary();

    Dictionary<string, Object> childUpdates = new Dictionary<string, Object>();
    childUpdates["/scores/" + key] = entryValues;
    childUpdates["/user-scores/" + userId + "/" + key] = entryValues;

    mDatabase.UpdateChildrenAsync(childUpdates);
}

 

데이터 삭제

RemoveValue를 호출하는 방법,  혹은 값 할당/업데이트 메쏘드에 null을 설정하는 방법

 

 

데이터를 트랜잭션으로 저장

처리에 조금 시간이 필요한 과정을 여러 클라이언트가 호출 할 때 이들 사이의 순서가 정해져서 데이터가 손상 되지 않도록 해준다네요. 가급적이면 트랜잭션을 써야 할지...

private void AddScoreToLeaders(string email,
                               long score,
                               DatabaseReference leaderBoardRef) {

    leaderBoardRef.RunTransaction(mutableData => {
      List<object> leaders = mutableData.Value as List<object>

      if (leaders == null) {
        leaders = new List<object>();
      } else if (mutableData.ChildrenCount >= MaxScores) {
        long minScore = long.MaxValue;
        object minVal = null;
        foreach (var child in leaders) {
          if (!(child is Dictionary<string, object>)) continue;
          long childScore = (long)
                      ((Dictionary<string, object>)child)["score"];
          if (childScore < minScore) {
            minScore = childScore;
            minVal = child;
          }
        }
        if (minScore > score) {
          // The new score is lower than the existing 5 scores, abort.
          return TransactionResult.Abort();
        }

        // Remove the lowest score.
        leaders.Remove(minVal);
      }

      // Add the new high score.
      Dictionary<string, object> newScoreMap =
                       new Dictionary<string, object>();
      newScoreMap["score"] = score;
      newScoreMap["email"] = email;
      leaders.Add(newScoreMap);
      mutableData.Value = leaders;
      return TransactionResult.Success(mutableData);
    });
}

 

오프라인으로 데이터 쓰기

Firebase Database는 네트워크 연결이 끊어지더라도 내부 버젼을 통해서 계속 사용 할 수 있게 합니다.

뭔가 문제가 생길 수 있는 경우도 있겠지만, 기본적인 처리는 따로 동기화 처리를 해주지 않아도 Firebase가 해준다는 점~

 

 

데이터 검색

https://firebase.google.com/docs/database/unity/retrieve-data?hl=ko#retrieving_data_2 

 

데이터 검색  |  Firebase 실시간 데이터베이스

Google I/O 2023에서 Firebase의 주요 소식을 확인하세요. 자세히 알아보기 의견 보내기 데이터 검색 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. 이 문서에서는

firebase.google.com

 

데이터는 직접 조회(GetValueAsync()) 하거나 이벤트를 연결하여 이벤트 발생 시 통지 되도록 합니다.

조회도 마찬가지로 데이터베이스 레퍼런스를 통해 처리 합니다.

using Firebase;
using Firebase.Database;
using Firebase.Extensions.TaskExtension; // for ContinueWithOnMainThread

public class MyScript: MonoBehaviour {
  void Start() {
    // Get the root reference location of the database.
    DatabaseReference reference = FirebaseDatabase.DefaultInstance.RootReference;
  }
}

 

GetValueAsync 를 이용하여 데이터 (스냅샷) 한 번 읽기, 없으면 null

    FirebaseDatabase.DefaultInstance
      .GetReference("Leaders")
      .GetValueAsync().ContinueWithOnMainThread(task => {
        if (task.IsFaulted) {
          // Handle the error...
        }
        else if (task.IsCompleted) {
          DataSnapshot snapshot = task.Result;
          // Do something with snapshot...
        }
      });

 

이벤트 리스너를 추가하여 변경 사항을 받아보는 방법

이벤트를 통하면 지정 된 경로 하위 모든 데이터의 변경에 대한 통지를 받으니, 적당한 수준을 잘 유지해야 겠습니다.

리더보드 점수 검색의 예 입니다.

      FirebaseDatabase.DefaultInstance
        .GetReference("Leaders")
        .ValueChanged -= HandleValueChanged; // unsubscribe from ValueChanged.
    }

 

유사한 내용으로 특정 노드 하위에서 발생하는 특정 작업에 대응하는 트리거로 '하위 이벤트'가 있습니다.

      var ref = FirebaseDatabase.DefaultInstance
      .GetReference("GameSessionComments");

      ref.ChildAdded += HandleChildAdded;
      ref.ChildChanged += HandleChildChanged;
      ref.ChildRemoved += HandleChildRemoved;
      ref.ChildMoved += HandleChildMoved;
    }

    void HandleChildAdded(object sender, ChildChangedEventArgs args) {
      if (args.DatabaseError != null) {
        Debug.LogError(args.DatabaseError.Message);
        return;
      }
      // Do something with the data in args.Snapshot
    }

    void HandleChildChanged(object sender, ChildChangedEventArgs args) {
      if (args.DatabaseError != null) {
        Debug.LogError(args.DatabaseError.Message);
        return;
      }
      // Do something with the data in args.Snapshot
    }

    void HandleChildRemoved(object sender, ChildChangedEventArgs args) {
      if (args.DatabaseError != null) {
        Debug.LogError(args.DatabaseError.Message);
        return;
      }
      // Do something with the data in args.Snapshot
    }

    void HandleChildMoved(object sender, ChildChangedEventArgs args) {
      if (args.DatabaseError != null) {
        Debug.LogError(args.DatabaseError.Message);
        return;
      }
      // Do something with the data in args.Snapshot
    }

그 외, 정렬/ 필터링/ 쿼리 정렬 등은 일단 Skip!!

아무래도 이론적인 내용만 너무 많으니 지칠 것 같네요. 실제 필요한 내용을 구현하면서 익혀보도록 하겠습니다.

 

 

 

 

 

 

728x90
반응형

댓글