프로젝트 기록

[트러블슈팅] 버튼 이벤트 인덱스 오류

binary는 호남선 2025. 10. 1. 13:16

[ 문제 상황 ]

LobbyScene에서 레벨 버튼을 클릭해도 GameScene으로 정상적으로 넘어가지 않음.

레벨 정보가 제대로 설정되지 않아서 비정상 종료되어버리며 디버그 로그 코드로 확인해본 결과 어떤 레벨 버튼을 클릭해도 인덱스 값으로 3이 들어감

 

[ 문제 원인 ]

C#의 클로저를 고려하지 않은 람다식 사용!

클로저: 람다 표현식이나 익명 함수가 외부 변수를 "캡처"하여 사용하는 기능으로, 변수의 값이 아닌 변수 자체에 대한 참조를 캡처

 

문제 원인 코드

for (int i = 0; i < levelBtnList.Count; i++)
{
    // i는 루프 변수
    levelBtnList[i].onClick.AddListener(() => OnClickedMenuBtn(i));
    // 람다 표현식은 'i 변수 자체'를 참조
}

단계별 동작 과정:

  1. 루프 실행 중 (i = 0, 1, 2)
    • 각 버튼에 람다 표현식이 등록됨
    • 하지만 람다는 현재 i의 값(0, 1, 2)을 저장하는 것이 아님
    • i 변수 자체에 대한 참조를 저장함
  2. 루프 종료 후 (i = 3)
    • 루프가 끝나면 i는 마지막 값(3)을 가지고 있음
    • 모든 람다 표현식은 동일한 i 변수를 참조하고 있음
  3. 버튼 클릭 시
    • 람다 표현식이 실행될 때 i 변수의 현재 값을 읽음
    • 이때 i는 이미 루프가 끝난 후의 값(마지막 인덱스)을 가지고 있음
    • 결과: 어떤 버튼을 눌러도 같은 인덱스가 전달됨
[버튼 0] → 람다 { OnClickedMenuBtn(i) } ─┐
[버튼 1] → 람다 { OnClickedMenuBtn(i) } ─┼→ 모두 같은 'i' 변수 참조 → i = 3 (루프 종료 후)
[버튼 2] → 람다 { OnClickedMenuBtn(i) } ─┘

 

[ 해결 방법 ]

각 람다 표현식이 고유한 변수를 참조하도록 만들기 위해 로컬 변수를 사용

private void OnEnable()
{
    for (int i = 0; i < levelBtnList.Count; i++)
    {
        // 로컬 변수에 현재 인덱스 저장
        int levelIndex = i;
        // 로컬 변수 사용
        levelBtnList[i].onClick.AddListener(() => OnClickedMenuBtn(levelIndex));
    }
}

동작 원리:

  1. 각 루프 반복마다 새로운 levelIndex 변수 생성
    • i = 0일 때: levelIndex_0 = 0
    • i = 1일 때: levelIndex_1 = 1
    • i = 2일 때: levelIndex_2 = 2
  2. 각 람다는 서로 다른 levelIndex 캡처
    • 버튼 0의 람다 → levelIndex_0 참조 (값: 0)
    • 버튼 1의 람다 → levelIndex_1 참조 (값: 1)
    • 버튼 2의 람다 → levelIndex_2 참조 (값: 2)
  3. 각 버튼이 고유한 인덱스 유지
[버튼 0] → 람다 { OnClickedMenuBtn(levelIndex) } → levelIndex_0 = 0
[버튼 1] → 람다 { OnClickedMenuBtn(levelIndex) } → levelIndex_1 = 1
[버튼 2] → 람다 { OnClickedMenuBtn(levelIndex) } → levelIndex_2 = 2

 

[ 다른 해결 방법 ]

1. 명시적 메서드 사용

private void SetupButton(int index)
{
    levelBtnList[index].onClick.AddListener(() => OnClickedMenuBtn(index));
}

private void OnEnable()
{
    for (int i = 0; i < levelBtnList.Count; i++)
    {
        // ... 버튼 생성 코드 ...
        SetupButton(i); // 메서드 호출로 값 전달
    }
}

 

2. 버튼에 인덱스 컴포넌트 추가

public class LevelButton : MonoBehaviour
{
    public int LevelIndex { get; set; }
}

// 사용 예시
levelBtn.GetComponent<LevelButton>().LevelIndex = i;
levelBtn.onClick.AddListener(() => {
    int idx = levelBtn.GetComponent<LevelButton>().LevelIndex;
    OnClickedMenuBtn(idx);
});

 

참고 자료: https://learn.microsoft.com/ko-kr/dotnet/csharp/programming-guide/classes-and-structs/local-functions#local-functions-vs-lambda-expressions