Firebase DataBase

감사콩·2026년 1월 9일

유니티

목록 보기
26/29

서론


오늘은 즐거운 데이터베이스 알아보는 날
활용법을 스크린샷으로 기록해두고

그 이후에 데이터베이스 세이브 로드 로직을 구현해보자



TIL 요약

  1. Firebase Realtime Database 연동 및 유니티 프로젝트 설정
  2. 비동기를 활용한 기본 데이터 Save & Load 로직 구현
  3. List와 Dictionary 자료구조를 Firebase 포맷에 맞춰 변환하고 업로드하는 로직 추가



Database


생성부터 알아보자
파이어베이스 홈페이지 진입





테스트모드 설명필요

잠금모드로 시작



규칙

보안 규칙을 정할 수 있다

수정 시작






어제와 달라졌으니 google-services.json 재갱신 필요

추가사항

google-services.json 은 Assets 이외의 폴더에 넣으면 터짐




데이터베이스를 사용하기 위해
이거 임포트



SAVE 스크립트 작성


파이어베이스 DB 매니저를 만들어보겠다.
현재 목표는 인풋필드에 들어간 내용을 데이터베이스에 Update 시켜주기



using UnityEngine;
using Firebase;
using Firebase.Database;
using Firebase.Auth;
using UnityEngine.UI;
using System.Threading.Tasks;
using System.Collections; //비동기작업용

public class FirebaseDBController : MonoBehaviour
{
    DatabaseReference dbRef;
    FirebaseUser user;

    [SerializeField] InputField moneyField;
    [SerializeField] InputField xpField;
    [SerializeField] InputField levelField;

    private void Start()
    {
        this.dbRef = FirebaseAuthManager.dbRef;
        this.user = FirebaseAuthManager.user;
    }

    public void SaveToDB()
    {
        StartCoroutine(UpdateMoney(int.Parse(moneyField.text)));
        StartCoroutine(UpdateExp(int.Parse(xpField.text)));
        StartCoroutine(UpdateLevel(int.Parse(levelField.text)));
    }

    IEnumerator UpdateMoney(int money)
    {
        //users 폴더를 만들 듯이
        var DBTask = dbRef.Child("users").Child(user.UserId).Child("money").SetValueAsync(money);
        yield return new WaitUntil(predicate: () => DBTask.IsCompleted);

        if (DBTask.Exception != null)
        {
            Debug.LogWarning($"돈 업데이트 실패, {DBTask.Exception}");
        }
        else
        {
            //저장완료 팝업 등
            Debug.Log("저장 완료");
        }
    }

    IEnumerator UpdateExp(int exp)
    {
        //users 폴더를 만들 듯이
        var DBTask = dbRef.Child("users").Child(user.UserId).Child("exp").SetValueAsync(exp);
        yield return new WaitUntil(predicate: () => DBTask.IsCompleted);

        if (DBTask.Exception != null)
        {
            Debug.LogWarning($"경험치 업데이트 실패, {DBTask.Exception}");
        }
        else
        {
            //저장완료 팝업 등
            Debug.Log("저장 완료");
        }
    }

    IEnumerator UpdateLevel(int lvl)
    {
        //users 폴더를 만들 듯이
        var DBTask = dbRef.Child("users").Child(user.UserId).Child("level").SetValueAsync(lvl);
        yield return new WaitUntil(predicate: () => DBTask.IsCompleted);

        if (DBTask.Exception != null)
        {
            Debug.LogWarning($"레벨 업데이트 실패, {DBTask.Exception}");
        }
        else
        {
            //저장완료 팝업 등
            Debug.Log("저장 완료");
        }
    }
}



테스트


실제로 데이터베이스에 등록되는 지 확인

코드상으론 문제 X



반영 완료



LOAD 스크립트 작성


이제 동일한 스크립트에 로드 기능을 만들어 보겠다.

    public void LoadFromDB()
    {
        StartCoroutine(LoadUserData());
    }

    IEnumerator LoadUserData()
    {
        var DBTask = dbRef.Child("users").Child(user.UserId).GetValueAsync();
        yield return new WaitUntil(predicate: () => DBTask.IsCompleted);

        if (DBTask.Exception != null)
        {
            Debug.LogWarning("데이터 불러오기 실패 " + DBTask.Exception);
        }
        else if (DBTask.Result.Value == null)
        {
            Debug.LogWarning("저장된 데이터 없습니다");
            //yield break;
        }
        else
        {
            DataSnapshot snapShot = DBTask.Result;
            moneyField.text = snapShot.Child("money").Exists ? snapShot.Child("money").Value.ToString() : "0";
        }
    }



Test


세이브&로드 기능구현 테스트

문제없이 구현 완료



리팩토링


현재 코드의 문제점
각 변수를 하나씩 업로드 해야됨
이걸 참조형 한번에 올리도록 코드를 리팩토링 해보겠다



셋밸류의 인자값이 오브젝트이므로 리스트는 그냥 넣어도 들어감

딕셔너리의 경우엔?



    public void SaveInventoryAndDict()
    {
        dbRef.Child("users").Child(user.UserId).Child("Inventory").SetValueAsync(inventory); //배열계열은 딸깍

        //▼ 딕셔너리는 오브젝트로 변환해서 올리는 작업 필요(인자값이 오브젝트 이기에)
        Dictionary<string, object> invenDicObj = new Dictionary<string, object>();
        foreach (var kvp in inventoryDicVer)
        {
            invenDicObj[kvp.Key] = kvp.Value;
        }
        dbRef.Child("users").Child(user.UserId).Child("InventoryDict").SetValueAsync(invenDicObj);
    }

조금 단계가 복잡해지지만 새로 담기만 하면 된다.



using UnityEngine;
using Firebase;
using Firebase.Database;
using System.Collections.Generic;
using Firebase.Auth;
using UnityEngine.UI;
using System.Threading.Tasks;
using System.Collections; //비동기작업용

public class FirebaseDBController : MonoBehaviour
{
    DatabaseReference dbRef;
    FirebaseUser user;

    [SerializeField] InputField moneyField;
    [SerializeField] InputField xpField;
    [SerializeField] InputField levelField;

    public List<string> inventory = new List<string> { "테스트아이템1", "테스트아이템2", "테스트아이템3" };
    public Dictionary<string, int> inventoryDicVer = new Dictionary<string, int>()
    {
        {"딕셔너리테스트1", 1 },
        {"딕셔너리테스트2", 3 },
        {"딕셔너리테스트3", 6 }
    };

    private void Start()
    {
        this.dbRef = FirebaseAuthManager.dbRef;
        this.user = FirebaseAuthManager.user;
    }

    public void SaveInventoryAndDict()
    {
        dbRef.Child("users").Child(user.UserId).Child("Inventory").SetValueAsync(inventory); //배열계열은 딸깍

        //▼ 딕셔너리는 오브젝트로 변환해서 올리는 작업 필요(인자값이 오브젝트 이기에)
        Dictionary<string, object> invenDicObj = new Dictionary<string, object>();
        foreach (var kvp in inventoryDicVer)
        {
            invenDicObj[kvp.Key] = kvp.Value;
        }
        dbRef.Child("users").Child(user.UserId).Child("InventoryDict").SetValueAsync(invenDicObj);
    }

    IEnumerator LoadInvenCoroutine()
    {
        var DBTask = dbRef.Child("users").Child(user.UserId).Child("Inventory").GetValueAsync();
        yield return new WaitUntil(predicate: () => DBTask.IsCompleted);

        if (DBTask.Exception != null)
        {
            Debug.LogWarning("인벤토리 불러오기 실패 " + DBTask.Exception);
        }
        else if (DBTask.Result.Value == null)
        {
            Debug.LogWarning("인벤토리가 비었습니다");
        }
        else
        {
            inventory.Clear(); //기존 로컬상의 인벤토리 깔끔히 날리고
            foreach (DataSnapshot item in DBTask.Result.Children) //Children 이기에 전부 데려옴
            {
                inventory.Add(item.Value.ToString());
                //yield return null; 등 부하를 나눠담기? 이러면 데이터 손상 가능성이 있음
            }
            Debug.Log("인벤토리 로드 완료" + string.Join(", ", inventory));
        }
    }

    IEnumerator LoadInvenDictCoroutine()
    {
        var DBTask = dbRef.Child("users").Child(user.UserId).Child("InventoryDict").GetValueAsync();
        yield return new WaitUntil(predicate: () => DBTask.IsCompleted);

        if (DBTask.Exception != null)
        {
            Debug.LogWarning("인벤토리 딕셔너리 불러오기 실패 " + DBTask.Exception);
        }
        else if (DBTask.Result.Value == null)
        {
            Debug.LogWarning("인벤토리 딕셔너리가 비었습니다");
        }
        else
        {
            inventoryDicVer.Clear(); //기존 로컬상의 인벤토리 깔끔히 날리고
            foreach (DataSnapshot item in DBTask.Result.Children)
            {
                string key = item.Key;
                if (int.TryParse(item.Value.ToString(), out int value))
                {
                    inventoryDicVer[key] = value;
                }
                else
                {
                    Debug.LogWarning($"딕셔너리 값 변환 실패: {item.Key} = {item.Value}");
                }
            }
        }

    }
    public void LoadInventory()
    {
        StartCoroutine(LoadInvenDictCoroutine());
        StartCoroutine(LoadInvenCoroutine());
    }
    
    public void SaveToDB()
    {
        StartCoroutine(UpdateMoney(int.Parse(moneyField.text)));
        StartCoroutine(UpdateExp(int.Parse(xpField.text)));
        StartCoroutine(UpdateLevel(int.Parse(levelField.text)));
        SaveInventoryAndDict();
    }

    public void LoadFromDB()
    {
        StartCoroutine(LoadUserData());
    }

    IEnumerator LoadUserData()
    {
        var DBTask = dbRef.Child("users").Child(user.UserId).GetValueAsync();
        yield return new WaitUntil(predicate: () => DBTask.IsCompleted);

        if (DBTask.Exception != null)
        {
            Debug.LogWarning("데이터 불러오기 실패 " + DBTask.Exception);
        }
        else if (DBTask.Result.Value == null)
        {
            Debug.LogWarning("저장된 데이터 없습니다");
            //yield break;
        }
        else
        {
            DataSnapshot snapShot = DBTask.Result;
            moneyField.text = snapShot.Child("money").Exists ? snapShot.Child("money").Value.ToString() : "0";
            xpField.text = snapShot.Child("exp").Exists ? snapShot.Child("exp").Value.ToString() : "0";
            levelField.text = snapShot.Child("level").Exists ? snapShot.Child("level").Value.ToString() : "0";
        }
    }

    IEnumerator UpdateMoney(int money)
    {
        //users 폴더를 만들 듯이
        var DBTask = dbRef.Child("users").Child(user.UserId).Child("money").SetValueAsync(money);
        yield return new WaitUntil(predicate: () => DBTask.IsCompleted);

        if (DBTask.Exception != null)
        {
            Debug.LogWarning($"돈 업데이트 실패, {DBTask.Exception}");
        }
        else
        {
            //저장완료 팝업 등
            Debug.Log("저장 완료");
        }
    }

    IEnumerator UpdateExp(int exp)
    {
        //users 폴더를 만들 듯이
        var DBTask = dbRef.Child("users").Child(user.UserId).Child("exp").SetValueAsync(exp);
        yield return new WaitUntil(predicate: () => DBTask.IsCompleted);

        if (DBTask.Exception != null)
        {
            Debug.LogWarning($"경험치 업데이트 실패, {DBTask.Exception}");
        }
        else
        {
            //저장완료 팝업 등
            Debug.Log("저장 완료");
        }
    }

    IEnumerator UpdateLevel(int lvl)
    {
        //users 폴더를 만들 듯이
        var DBTask = dbRef.Child("users").Child(user.UserId).Child("level").SetValueAsync(lvl);
        yield return new WaitUntil(predicate: () => DBTask.IsCompleted);

        if (DBTask.Exception != null)
        {
            Debug.LogWarning($"레벨 업데이트 실패, {DBTask.Exception}");
        }
        else
        {
            //저장완료 팝업 등
            Debug.Log("저장 완료");
        }
    }
}



TEST


일단 에디터 상 Load 체크 완료

파이어베이스도 가보자



SAVE 정상 작동 확인



보완할 사항


마지막으로 지금까지의 스크립트에 수정이 필요할 부분에 대해 정리해두자

  1. UpdateChildrenAsync 등을 활용하여 딕셔너리 하나에 money, exp, level을 다 담아서 보내기
    2.JSON 직렬화

JSON 직렬화는 예시만 적어두겠다
방식은 C# 클래스를 하나 만들고 JsonUtility 직렬화

[System.Serializable]
public class UserData {
    public int money;
    public int exp;
    public int level;
    public List<string> inventory;
}

//저장 시
UserData data = new UserData(); 



~~~데이터 채우기



string json = JsonUtility.ToJson(data);
dbRef.Child("users").Child(userId).SetRawJsonValueAsync(json);



마무리


오늘은 파이어베이스의 데이터베이스 활용 방식과
그에 맞는 코드 작성을 연습해봤다.
처음 C# 공부할 때처럼 리팩토링 할 부분이 수도 없이 많다.
천천히 익숙해져가자.

profile
안녕하시와요

0개의 댓글