Unity 최종
Unity + Firebase: 랭킹 UI 비동기 처리 방법 3가지 정리 (ContinueWith vs Coroutine vs async/await)
1vdlrwnsv1
2025. 5. 28. 16:38
국비 교육 중 팀원이 만든 Uinty에서 Firebase데이터를 받아와 유저의 랭킹 정보를 표시 하는 기능이 있는데 UI 오브젝트가 생성이 안되는 문제가 생겼다 결국 여러 방법을 시도하며 안정적인 방법을 찾아냈다
기존 코드
private void Init()
{
FirebaseManager.Instance.DB.Collection("users")
.OrderByDescending("score")
.Limit(3)
.GetSnapshotAsync().ContinueWith(task =>
{
if (task.IsCanceled || task.IsFaulted) return;
var snapshot = task.Result;
foreach (var document in snapshot.Documents)
{
string nickname = document.GetValue<string>("nickname");
int score = document.GetValue<int>("score");
Debug.Log($"닉네임: {nickname}, 점수: {score}");
var scoreItems = ObjectPoolManager.Instance.GetObject<RankDisplayUI>(contentUI, Vector3.zero, Quaternion.identity);
if (scoreItems == null)
{
Debug.LogError("오브젝트 풀에서 RankDisplayUI 생성 실패!");
return;
}
scoreItems.transform.SetParent(contentsPos.transform, false);
scoreItems.SetUI(document);
}
});
}
Unity의 메인 스레드에서 실행되지 않아 ui관련작업(Instantiate, SetParent)시 문제가 발생함 어떤 에러도 코드에 적어놓은 디버그 로그도 찍히지 않았음
트러블 슈팅
ui작업은 반드시 메인 스레더에서
해결 방법: 코루틴, async/await 방식 전환
코루틴 방식
private IEnumerator InitCoroutine()
{
var task = FirebaseManager.Instance.DB.Collection("users")
.OrderByDescending("score")
.Limit(3)
.GetSnapshotAsync();
yield return new WaitUntil(() => task.IsCompleted);
if (task.IsCanceled || task.IsFaulted)
{
Debug.LogError("데이터 가져오기 실패");
yield break;
}
var snapshot = task.Result;
foreach (var document in snapshot.Documents)
{
string nickname = document.GetValue<string>("nickname");
int score = document.GetValue<int>("score");
Debug.Log($"닉네임: {nickname}, 점수: {score}");
var scoreItems = ObjectPoolManager.Instance.GetObject<RankDisplayUI>(contentUI, Vector3.zero, Quaternion.identity);
if (scoreItems == null)
{
Debug.LogError("오브젝트 풀에서 RankDisplayUI 생성 실패!");
continue;
}
scoreItems.transform.SetParent(contentsPos.transform, false);
scoreItems.SetUI(document);
}
}
장점
- Unity 메인 스레드에서 실행되므로 UI 조작이 안전함.
- yield return을 활용해 자연스럽게 대기 가능.
단점
- 코드 가독성이 async/await에 비해 떨어짐.
- 에러 처리가 불편함 (task.Exception 직접 접근 필요).
방법 2
async/await 방식
private async void Init()
{
if (contentUI == null || contentsPos == null)
{
Debug.LogError("contentUI 또는 contentsPos가 인스펙터에서 설정되지 않았습니다.");
return;
}
var query = FirebaseManager.Instance.DB.Collection("users")
.OrderByDescending("score")
.Limit(3);
QuerySnapshot snapshot;
try
{
snapshot = await query.GetSnapshotAsync();
}
catch (System.Exception e)
{
Debug.LogError("Firebase 쿼리 실패: " + e.Message);
return;
}
foreach (var document in snapshot.Documents)
{
string nickname = document.GetValue<string>("nickname");
int score = document.GetValue<int>("score");
Debug.Log($"닉네임: {nickname}, 점수: {score}");
var scoreItems = ObjectPoolManager.Instance.GetObject<RankDisplayUI>(contentUI, Vector3.zero, Quaternion.identity);
if (scoreItems == null)
{
Debug.LogError("오브젝트 풀에서 RankDisplayUI 생성 실패");
continue;
}
scoreItems.transform.SetParent(contentsPos.transform, false);
scoreItems.transform.localScale = Vector3.one;
scoreItems.SetUI(document);
}
}
장점
- 코드가 깔끔하고 가독성 좋음.
- try-catch로 예외 처리도 쉽다.
- 메인 스레드에서 실행되므로 UI 조작도 문제 없음.
결론 : 가능하면 async/await로 구현하자 코드 깔끔하고 유지보수 쉬움
마무리
Firebase 연동은 단순히 데이터만 가져오는 것 같지만, Unity의 스레드 구조를 이해하지 못하면 UI 깨짐, NullReference 등 여러 문제를 만나게 된다.(필자도 아직 제대로 이해 못함)
이번 포스트가 Firebase 연동하면서 겪는 비동기 문제를 해결하는 데 도움이 되길 바람