프로그램 강좌/유니티 + 파이어베이스

파이어베이스 유니티 Realtime Database

개양반 2017. 11. 21.

선행 작업


SDK 다운로드 [내용보기]

이메일/비밀번호 로그인 [내용보기]

 

 

FirebaseDatabase SDK 임포트


앞에서 다운로드한 SDK 중에 FirebaseDatabase.unitypackage 를 로그인을 완료한 유니티프로젝트에

임포트합니다. (PS: 임포트할 때, 오류가 생겼는데 다시 유니티를 실행하고 임포트하니 잘되네요.)

 

파이어베이스 콘솔창 설정 변경


콘솔창에서 Database > 시작하기 버튼 클릭을 합니다.

 

변경된 google-services.json 파일을 다시 다운받고 유니티의 google-services 파일은 변경해줍니다.

( 위 내용은 앞의 포스팅에서 설명했습니다. )

 

SetEditorServiceAccountEmail 가져오기


지금 세팅하는 것은 유니티 에디터에서 파이어베이스 데이터베이스를 사용하려면 필요한 세팅입니다. ( 이 세팅을 안해도 모바일에서는 잘 됩니다. )

Firebase 콘솔창에서 좌측 상단 톱니바퀴를 누르시고 설정으로 들어가서 프로젝트ID 를 복사합니다.

 

 

https://console.cloud.google.com/iam-admin/serviceaccounts/project?project=프로젝트ID 를 인터넷 주소창에 입력합니다.

그럼 프로젝트의 서비스 계정 어쩌고 하는 페이지로 이동하는데요. 작업이라고 적힌 버튼을 클릭해서 키 만들기를 선택합니다.

 

P12 형식을 사용하는 코드와의 하위 호환용 을 선택하고 만들기 버튼을 눌러주세요.

 

그럼 파일 하나를 다운 받는데요. 그거를 유니티의 Editor Defult Resources 로 붙여넣기 합니다.

 

 

 

DatabaseReference 가져오기


먼저 유저정보를 다른 스크립트에서 가져오기 위해 

Login.cs 의 FirebaseUser user; 변수를 스테틱으로 선언합니다.

FirebaseUser user -> public static FirebaseUser user;

 

 

유니티에 RealtimeDatabase.cs 를 만듭니다.

아래 코드를 입력합니다.

using UnityEngine;
using Firebase.Unity.Editor;
using Firebase.Database;
using Firebase;

public class RealtimeDatabase : MonoBehaviour
{
    FirebaseApp firebaseApp;
    DatabaseReference databaseReference;


    private void Awake()
    {
        firebaseApp = FirebaseDatabase.DefaultInstance.App;
        firebaseApp.SetEditorDatabaseUrl("https://firsttestapp-eac16.firebaseio.com/");
        databaseReference = FirebaseDatabase.DefaultInstance.RootReference;
	FirebaseApp.DefaultInstance.SetEditorFileName("파일이름.p12");

	// 아래 비밀번호에는 특별하게 설정한거 없으면 notasecret 일 겁니다.
	FirebaseApp.DefaultInstance.SetEditorP12Password("notasecret");
    } 
    
}

 

 

firebaseApp.SetEditorDatabaseUrl( "콘솔창의 정보를 입력해야 한다." );

 

데이터 쓰기


RealtimeDatabase.cs 에 아래 코드를 추가합니다.

    public void InitDatabase()
    {
        if (Login.user != null)
        {
            WriteNewUser(Login.user.UserId , Login.user.DisplayName, Login.user.Email);
        }
    }

    private void WriteNewUser(string uid, string name, string email)
    {
        User user = new User(name, email);
        string json = JsonUtility.ToJson(user);
        databaseReference.Child("users").Child(uid).SetRawJsonValueAsync(json);
    }
 

 

Hierarchy 에 Database 라는 오브젝트를 만들고 RealtimeDatabase.cs를 연결해줍니다.

데이터를 저장시켜주는 버튼을 만든 다음 클릭이벤트에 Database 를 연결해줍니다.

 

모바일로 빌드하고 저장버튼을 누르면 유저의 이름과 이메일주소가 저장됩니다.

 

콘솔창에서 저장된 데이터가 보이네요~

databaseReference.Child("users").Child(uid).SetRawJsonValueAsync(json);

는 동일한 uid 가 들어오면 SetRawJsonValueAsync 의 데이터로 덮어 씌웁니다.

 

특정 변수값만 변경하고 싶다면

databaseReference.Child("users").Child(uid).Child("username").SetValueAsync(name);

를 입력하시면 됩니다.

 

 

특정 필드 업데이트


다른 하위 노드를 덮어쓰지 않고 특정 하위 노드에 여러개를 동시에 쓰기를 하려면 

UpdateChildrenAsync()updateChildValues() 메소드를 사용합니다.

public void OnClickUpdateChild()
    {
        Dictionary<string, object> childUpdates = new Dictionary<string, object>();
        childUpdates["/users/" + Login.user.UserId + "/" + "username"] = Login.user.DisplayName;
        childUpdates["/users/" + Login.user.UserId + "/" + "score"] = 100;

        databaseReference.UpdateChildrenAsync(childUpdates);
    }
 

 

 

데이터 푸쉬


전투 로그처럼 데이터를 덮어씌우지 않고 새로 저장해야 할 때가 있습니다.

이럴 때는 Push() 를 사용 합니다.

	public void OnClickPush()
    {
        Dictionary<string, object> childUpdates = new Dictionary<string, object>();
        childUpdates["/users/" + Login.user.UserId + "/" + "username"] = Login.user.DisplayName;
        childUpdates["/users/" + Login.user.UserId + "/" + "score"] = 100;

        databaseReference.Push().Child(Login.user.UserId).UpdateChildrenAsync(childUpdates);
    }
 

 

트랜잭션으로 저장하기


여러 사람이 쓰는 데이터는 동시 수정으로 데이터가 손상될 수 있습니다.

예로 랭킹같은 경우 여러 유저가 랭킹데이터를 수정하기 때문에 수정 작업 중에

데이터가 손상되어 각 유저의 랭킹정보가 달라질 수 있죠.

 

이럴 때 사용하는 것이 트랜잭션이라고 합니다.

public void OnClickTransactionSave()
    {
        const int MaxScoreRecordCount = 5;
        int score = Random.Range(0, 100);
        string email = "testEmail";

        databaseReference.Child("users").RunTransaction(mutableData => {
            List<object> leaders = mutableData.Value as List<object>;

            if (leaders == null)
            {
                leaders = new List<object>(); 
            }

            // 랭킹에 등록된 점수를 비교합니다.
            else if (mutableData.ChildrenCount >= MaxScoreRecordCount)
            {
                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)
                {
                    // 현재 점수가 최하위 점수보다 낮으면 중단합니다.(랭킹에 못오르니깐)
                    return TransactionResult.Abort();
                }

                // 기존 최하위 데이터를 제거합니다.(랭킹 변경)
                leaders.Remove(minVal);
            }

            Dictionary<string, object> newScoreMap = new Dictionary<string, object>();

            newScoreMap["score"] = score;
            newScoreMap["email"] = email;

            leaders.Add(newScoreMap);
            mutableData.Value = leaders;
            return TransactionResult.Success(mutableData);
        });
    }

 

 

 

기록을 비교하는 중간의코드를 제외하면 이해하기 쉬우실겁니다.

// 가져올 데이터의 이름
databaseReference.Child("users").RunTransaction(mutableData => {
            List<object> leaders = mutableData.Value as List<object>;
            /* 작업할 내용 시작
            
                // 랭킹 5위의 기록을 비교하면서
                // 현재 점수가 5위 안에 들어갈 수 있는지 등을
                // 체크

            작업할 내용 끝   */

            Dictionary<string, object> newScoreMap = new Dictionary<string, object>();
            leaders.Add(newScoreMap);            
            mutableData.Value = leaders; // 랭킹 5위의 기록을 덮어씌웁니다.
            return TransactionResult.Success(mutableData); // 값을 전달합니다.
}

 

 

기타 설명


네크워크가 종료되어도 자동으로 로컬에 저장한 뒤에 해당 데이터를 원격 데이터베이스 서버 및 다른 클라이언트와 '최선을 다해' 동기화 한다고 하네요. ( 최선을 다한다는게 무섭네요? )

네트워크에 다시 연결되면 앱에서 해당 이벤트 세트를 수신하여 클라이언트와 현재 서버 상태를 동기화한다고 합니다.

그래서 별도의 코드를 작성할 필요가 없다네요.

 

데이터 삭제


데이터를 삭제하는 코드는 간단합니다.

지울려는 대상.RemoveValueAsync() 를 하면 됩니다.

 public void OnClickRemove()
    {
        databaseReference.Child("users").Child("노드이름").Child("노드이름")
	.RemoveValueAsync();
    }
 

 

데이터 읽기


 

한번만 읽어오기


public void OnClickDataRead()
    {
        FirebaseDatabase.DefaultInstance
            .GetReference("users") // 읽어올 데이터 이름
            .GetValueAsync().ContinueWith(task =>
            {
                if (task.IsFaulted)
                {
                }
                else if (task.IsCompleted)
                {
                    DataSnapshot snapshot = task.Result;
                    
                    // DataSnapshot 타입에 저장된 값 불러오기
                    foreach (var item in snapshot.Children)
                    {
                        Debug.Log(item.Child("email").Value);
                        Debug.Log(item.Child("score").Value);
                    }
                }
            });
    }
 
 

 

이벤트 수신해서 읽어오기


데이터베이스에 저장된 값이 변경/삭제/이동/추가 등을 감지하고 각 클라이언트에게 변경된 내용을

동기화시켜줄 수 도 있습니다. 

public void OnClickEventListner()
    {
        
        DatabaseReference reference = FirebaseDatabase.DefaultInstance.GetReference("users");
        
        reference.ValueChanged += HandleValueChanged;
        reference.ChildAdded += HandleChildAdded;
        reference.ChildChanged += HandleChildChanged;
        reference.ChildRemoved += HandleChildRemoved;
        reference.ChildMoved += HandleChildMoved;
    }

    //users 데이터 전체의 변화에 수신
    void HandleValueChanged(object sender, ValueChangedEventArgs args)
    {
        if (args.DatabaseError != null)
        {
            Debug.LogError(args.DatabaseError.Message);
            return;
        }
        SnapshotAllDataRead(args.Snapshot);
    }

    //users 데이터의 하위목록의 값이 추가되면 수신
    void HandleChildAdded(object sender, ChildChangedEventArgs args)
    {
        if (args.DatabaseError != null)
        {
            Debug.LogError(args.DatabaseError.Message);
            return;
        }
        SnapshotDataRead(args.Snapshot);
    }


    //users 데이터의 하위목록의 값이 변경되면 수신
    void HandleChildChanged(object sender, ChildChangedEventArgs args)
    {
        if (args.DatabaseError != null)
        {
            Debug.LogError(args.DatabaseError.Message);
            return;
        }
        SnapshotDataRead(args.Snapshot);
    }

    //users 데이터의 하위목록의 값이 제거되면 수신
    void HandleChildRemoved(object sender, ChildChangedEventArgs args)
    {
        if (args.DatabaseError != null)
        {
            Debug.LogError(args.DatabaseError.Message);
            return;
        }
        SnapshotDataRead(args.Snapshot);
    }

    //users 데이터의 하위목록의 값이 이동하면 수신
    void HandleChildMoved(object sender, ChildChangedEventArgs args)
    {
        if (args.DatabaseError != null)
        {
            Debug.LogError(args.DatabaseError.Message);
            return;
        }
        SnapshotDataRead(args.Snapshot);
    }


    // 데이터를 읽어온다.
    public void SnapshotDataRead(DataSnapshot Snapshot)
    {
        DataSnapshot snapshot = Snapshot;

        foreach (var item in snapshot.Children)
        {
            Debug.Log(item.Value);
        }
    }

    public void SnapshotAllDataRead(DataSnapshot Snapshot)
    {
        foreach (var item in Snapshot.Children)
        {
            Debug.Log(item.Child("email").Value +" : "+ item.Child("score").Value);
         }
    }
 
 

데이터 정렬


하위 키의 값에 따라 / 하위 키에 따라 / 하위 값에 따라 결과를 정렬할 수 있습니다.

아래는 score 로 정렬한 방법에 대한 내용입니다.

public void OnClickOrderby()
    {
        FirebaseDatabase.DefaultInstance
            .GetReference("users").OrderByChild("score")
            .GetValueAsync().ContinueWith(task =>
            {
                if (task.IsFaulted)
                {
                }
                else if (task.IsCompleted)
                {
                    DataSnapshot snapshot = task.Result;

                    foreach (var item in snapshot.Children)
                    {
                        Debug.Log(item.Child("email").Value + " : " + item.Child("score").Value);
                    }
                }
            });
    }​

 

 

보안규칙


파이어베이스(firebase) 콘솔창의 Database -> 규칙탭에서 유저의 쓰기/읽기 권한을 조절할 수 있습니다.

예로 쓰기할때는 

자신의 유저노드에만 쓸 수 있고 타인의 유저노드는 읽기만 가능하도록 할 수 있습니다.

위의 내용은 디바이스에서 테스트할 때만 적용됩니다.

 

규칙에서는 데이터 관련 다양한 규칙들을 설정할 수 있습니다.

입력 글자 수 제한 등

댓글

💲 추천 글