[ 문제 상황 ]
LobbyScene에서 레벨 버튼을 클릭해도 GameScene으로 정상적으로 넘어가지 않음.
레벨 정보가 제대로 설정되지 않아서 비정상 종료되어버리며 디버그 로그 코드로 확인해본 결과 어떤 레벨 버튼을 클릭해도 인덱스 값으로 3이 들어감
[ 문제 원인 ]
C#의 클로저를 고려하지 않은 람다식 사용!
클로저: 람다 표현식이나 익명 함수가 외부 변수를 "캡처"하여 사용하는 기능으로, 변수의 값이 아닌 변수 자체에 대한 참조를 캡처
문제 원인 코드
for (int i = 0; i < levelBtnList.Count; i++)
{
// i는 루프 변수
levelBtnList[i].onClick.AddListener(() => OnClickedMenuBtn(i));
// 람다 표현식은 'i 변수 자체'를 참조
}
단계별 동작 과정:
- 루프 실행 중 (i = 0, 1, 2)
- 각 버튼에 람다 표현식이 등록됨
- 하지만 람다는 현재 i의 값(0, 1, 2)을 저장하는 것이 아님
- i 변수 자체에 대한 참조를 저장함
- 루프 종료 후 (i = 3)
- 루프가 끝나면 i는 마지막 값(3)을 가지고 있음
- 모든 람다 표현식은 동일한 i 변수를 참조하고 있음
- 버튼 클릭 시
- 람다 표현식이 실행될 때 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));
}
}
동작 원리:
- 각 루프 반복마다 새로운 levelIndex 변수 생성
- i = 0일 때: levelIndex_0 = 0
- i = 1일 때: levelIndex_1 = 1
- i = 2일 때: levelIndex_2 = 2
- 각 람다는 서로 다른 levelIndex 캡처
- 버튼 0의 람다 → levelIndex_0 참조 (값: 0)
- 버튼 1의 람다 → levelIndex_1 참조 (값: 1)
- 버튼 2의 람다 → levelIndex_2 참조 (값: 2)
- 각 버튼이 고유한 인덱스 유지
[버튼 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);
});
'프로젝트 기록' 카테고리의 다른 글
| [HighRoWin] 상속과 인터페이스를 활용한 리팩토링 (0) | 2025.05.25 |
|---|---|
| [Warning-of-the-Monsters] 인간 상태 머신 설계 (0) | 2025.05.24 |
| UIManager 설계 (0) | 2025.05.24 |
| PoolManager 설계 (0) | 2025.05.24 |