[2025_12_30] Unity UI 이벤트 구독/해제

2025. 12. 30. 21:09TIL

TIL - Unity UI 이벤트 구독/해제 


문제 상황

증상:

  • 미션 패널에서 개별 Claim 버튼을 눌러도 아무 반응이 없음
  • 하지만 Claim All 버튼은 정상 작동
  • 에디터를 재시작하거나 패널을 다시 열어도 동일한 문제 발생

혼란스러웠던 점:

  • OnRewardItemRequested?.Invoke()는 호출됨 (로그 확인)
  • 하지만 핸들러인 OnRewardClaimHandler()는 실행 안됨
  • 이벤트 구독은 InitializeCards()에서 명확히 했는데 왜 안 되는지 이해 불가
 
 
csharp
// UIMissionCard.cs
private void OnButtonClicked()
{
    if (isCompleted)
    {
        Debug.Log("Claim 이벤트 발동!");  // 로그 찍힘
        OnRewardItemRequested?.Invoke(MissionIndex);  // 실행됨
    }
}

// UIMissionPanel.cs
private void OnRewardClaimHandler(int missionIndex)
{
    Debug.Log("핸들러 호출!");  // 여기는 실행 안됨!
    // ...
}

원인 분석

UIBase를 상속받은 UI는 다음과 같은 라이프사이클을 가짐:

 
csharp
// 최초 1회
SetupUI() → SubscribeEvents()

// 패널 열 때마다
OnShow()

// 패널 닫을 때마다
OnHide() → (내부적으로) UnsubscribeEvents()

// 파괴 시
OnDestroy() → UnsubscribeEvents()

2. 잘못된 이벤트 구독 시점

 
csharp
// UIMissionPanel.cs
protected override void SetupUI()
{
    InitializeCards();  // ← 여기서 카드 생성 + 이벤트 구독
}

private void InitializeCards()
{
    foreach (var questData in dailyMissions)
    {
        var card = Instantiate(dailyCardPrefab, dailyCardContainer).GetComponent<UIMissionCard>();
        card.Init(questData);
        SubscribeCardEvents(card);  // 최초 1회 구독
        dailyCards.Add(card);
    }
}

protected override void UnsubscribeEvents()
{
    // 패널이 닫힐 때 자동 호출됨!
    foreach (var card in dailyCards)
        UnsubscribeCardEvents(card);  // 이벤트 해제!
}

protected override void OnShow()
{
    // 패널이 다시 열릴 때
    UpdateTabUI(currentTabIndex);
    UpdateClaimAllButton();
    // 이벤트 재구독 없음!
}

문제:

  1. 최초에는 이벤트 구독됨 
  2. 패널 닫으면 UnsubscribeEvents() 자동 호출 → 이벤트 해제 
  3. 패널 다시 열면 OnShow() 호출 → 이벤트 재구독 안함 
  4. 카드 이벤트는 null 상태로 남음!

3. Claim All은 왜 작동했나?

csharp
private void OnClaimAllButtonClicked()
{
    var dailyMissions = missionManager.GetDailyMissions();
    foreach (var (data, progress) in dailyMissions)
    {
        missionManager.ClaimReward(data.index);  // ← 직접 호출!
        card.SetCardClaimed();
    }
}

Claim All은 이벤트를 사용하지 않고 직접 메서드를 호출하기 때문에 정상 작동!


해결 방법

수정된 코드

csharp
// UIMissionPanel.cs
protected override void SetupUI()
{
    InitializeCards();  // 카드만 생성 (이벤트 구독 X)
}

private void InitializeCards()
{
    foreach (var questData in dailyMissions)
    {
        var card = Instantiate(dailyCardPrefab, dailyCardContainer).GetComponent<UIMissionCard>();
        card.Init(questData);
        // SubscribeCardEvents(card);  // 여기서 구독하지 않음!
        dailyCards.Add(card);
    }
}

protected override void OnShow()
{
    base.OnShow();
    
    UpdateTabUI(currentTabIndex);
    UpdateClaimAllButton();
    
    // 패널이 열릴 때마다 이벤트 구독!
    SubscribeAllCardEvents();
}

현재 프로젝트 이벤트 관리

  • 영구적 이벤트: SubscribeEvents()에서 구독 (MissionManager 이벤트, 버튼 onClick 등)
  • 일시적 이벤트: OnShow()에서 구독, OnHide()/UnsubscribeEvents()에서 해제 (카드 이벤트 등)

    현재 프로젝트 Unity UI 패턴
    이벤트 종류구독 시점해제 시점예시
    Global SubscribeEvents() UnsubscribeEvents() MissionManager 이벤트
    Button SubscribeEvents() UnsubscribeEvents() onClick
    Dynamic OnShow() OnHide()/UnsubscribeEvents() 동적 생성 카드 이벤트

     

    교훈: 이벤트 구독/해제 시점을 명확히 설계하자!