Zenocide 출시를 위해 시험 공부도 미루고 마무리 중인데, 큰 문제가 발생했다.
위는 로비에서 불러오기를 눌렀을 때 뜨는 화면이다. 우측의 로딩 화면이 나타난 후, 좌측처럼 진행하던 스토리를 그대로 띄워야 한다.
그런데, 에디터에선 잘 되는데 빌드만 뽑으면 씬이 멈춰버렸다.
이번엔 그 해결 과정을 기록해보고자 한다.
대체 뭐가 문젠데?
우선, 도저히 감을 못 잡겠어서 Chat-GPT를 이용해 문제 발견에 나섰다. 아래는 GPT와 나눈 대화 중 일부다.
나
에디터에선 데이터 로드 후 씬 전환이 잘 되는데, 빌드 후 실행해보면 로딩 화면에서 멈춰. BGM이 제대로 나오는 걸보면 씬은 넘어간 것 같아. 어떤 증상일까?
GPT
빌드 후 로딩 화면에서 멈추는 증상은 일반적으로 비동기 로딩, 리소스 접근, 또는 스크립트 실행 순서 문제에서 발생할 수 있습니다. 다음과 같은 원인들이 있을 수 있으니 확인해 보세요.
1. 비동기 로딩 문제
- 비동기 방식으로 씬을 로드하는 경우, 씬 로드가 완료되지 않았거나 스크립트가 씬 로드 완료를 인식하지 못해 로딩 화면에서 멈출 수 있습니다. 이 문제는 AsyncOperation의 allowSceneActivation 플래그가 false로 설정되어 있으면 발생할 수 있습니다.
- 해결 방법: 비동기 로딩의 allowSceneActivation이 true로 설정되어 있는지 확인하고, isDone 상태를 체크하며 씬 전환이 잘 이루어지는지 점검해 보세요.
2. 스크립트 실행 순서 문제
- 특정 데이터 로드나 초기화 과정이 완료되지 않은 상태에서 씬 전환이 발생하면, 빌드에서는 로딩 화면에서 멈출 수 있습니다.
- 해결 방법: 에디터에서는 로딩 속도가 빠르기 때문에 실행 순서가 영향을 덜 미치지만, 빌드에서는 시간 차이로 문제가 발생할 수 있습니다. 필요한 경우 Script Execution Order 설정을 통해 로딩 관련 스크립트가 먼저 실행되도록 설정할 수 있습니다.
3. 필요한 리소스 누락
- Addressable Assets나 Resources 폴더에서 필요한 리소스가 빌드에 포함되지 않은 경우 발생할 수 있습니다. 에디터에서는 Resources에서 자동 로드가 이루어지지만, 빌드에서는 누락된 리소스로 인해 멈출 수 있습니다.
- 해결 방법: 필요한 리소스가 Addressable Assets에 포함되어 있는지 확인하거나, Resources.Load()로 로드되는 파일이 빌드에 포함되었는지 확인하세요.
4. 플랫폼 차이에 따른 비동기 로드 차이
- 모바일이나 콘솔 등 특정 플랫폼에서 에디터와 다른 방식으로 로딩 동작을 할 수 있습니다. 특히 iOS나 Android에서는 특정 기능이 다르게 동작하여 지연이 발생할 수 있습니다.
- 해결 방법: 로딩 화면이 멈출 때 로그를 남겨보거나, Unity Profiler를 통해 빌드 환경에서 로딩 관련 문제를 디버깅하는 것도 좋습니다.
에디터에서는 잘 작동하지만 빌드에서 문제가 발생하는 경우, 대개 실행 순서나 리소스 포함 여부와 관련된 경우가 많으니 이 부분들을 우선적으로 확인해 보세요.
나
씬 로드에 필요한 모든 리소스를 어떻게 확인하지? 에디터는 되는데?
GPT
Unity에서 에디터에서는 리소스를 자동으로 불러오기 때문에 빌드 시에는 누락이 생길 수 있습니다. 씬 로드에 필요한 모든 리소스가 빌드에 포함되었는지 확인하려면 다음과 같은 방법을 사용할 수 있습니다.
1. Addressable Assets 사용
- Addressables은 리소스를 관리하고 로드하는 데 용이한 Unity 시스템으로, 필요한 리소스를 쉽게 빌드에 포함시킬 수 있습니다.
- Addressable Groups 창에서 리소스를 관리하고, 필요한 에셋을 Addressables에 포함하면, 빌드 시 자동으로 필요한 리소스가 포함됩니다.
- Addressables를 사용하려면 Window > Asset Management > Addressables > Groups에서 Addressable Groups 창을 열고, 필요한 리소스를 그룹에 추가합니다.
2. Resources 폴더 사용
- Resources 폴더에 있는 리소스는 빌드 시 자동으로 포함되며, Resources.Load()를 통해 로드할 수 있습니다.
- Resources 폴더는 특별한 폴더로, 여기에 넣은 모든 리소스는 자동으로 빌드에 포함되므로, 씬에서 필요한 리소스를 Resources 폴더로 옮기고 관리할 수 있습니다.
3. Build Report 확인
- Unity는 빌드가 완료되면 **빌드 보고서(Build Report)**를 생성하여 빌드에 포함된 모든 리소스를 확인할 수 있습니다.
- Edit > Preferences > General에서 Script Debugging과 Development Build를 활성화하고 빌드하면, 에디터 로그에서 Editor.log를 통해 포함된 에셋의 목록을 확인할 수 있습니다.
- 보고서에서 특정 리소스가 누락되었는지 점검하고, 추가가 필요한 리소스를 Addressables 또는 Resources 폴더에 넣어 추가할 수 있습니다.
4. AssetBundle로 리소스 관리
- 특정 리소스만 포함하고 싶은 경우, AssetBundle로 관리할 수 있습니다. AssetBundle은 빌드 시 특정 리소스만 묶어 별도로 불러올 수 있어, 씬에 필요한 리소스를 효과적으로 관리할 수 있습니다.
- AssetBundle로 리소스를 빌드에 포함하고, 필요할 때 로드하도록 구현하면 됩니다.
5. 씬 로드 테스트
- 에디터에서는 잘 실행되지만 빌드에서는 누락되는 경우가 자주 발생하므로, 빌드 후 개발자 모드로 실행하면서 Profiler를 통해 리소스가 제대로 로드되는지 점검해 볼 수 있습니다.
- 또한 로딩 중 로드되지 않는 리소스에 대한 디버그 로그를 남겨 문제의 원인을 추적할 수도 있습니다.
이와 같이 Addressables, Resources 폴더, AssetBundle을 활용하거나 Build Report를 통해 씬 로드에 필요한 리소스가 빌드에 포함되었는지 확인할 수 있습니다.
나
Build Report를 확인하려해도 preference에 그런 설정이 없어. 유니티 2022.3.9f1 버전으로 알려줘
GPT
Unity 2022.3.9f1에서는 Build Report를 별도의 설정 없이 Unity Editor 로그 파일에서 확인할 수 있습니다. 빌드 완료 후 이 로그 파일을 열어 포함된 리소스를 확인할 수 있습니다.
Build Report 확인 방법
- 빌드 설정
- File > Build Settings에서 Development Build와 Script Debugging 옵션을 체크합니다. 이 옵션을 활성화하면 빌드에 대한 자세한 로그를 확인할 수 있습니다.
- Build
- 원하는 빌드 플랫폼으로 빌드를 완료합니다.
- Editor.log 파일 열기
- 빌드가 완료되면 Unity는 Editor.log 파일에 빌드된 리소스와 관련된 정보를 기록합니다.
- 각 운영체제에 따라 Editor.log 파일 위치가 다르므로, 아래 경로에서 파일을 열어 로그를 확인하세요.
- Windows: C:\Users\<YourUserName>\AppData\Local\Unity\Editor\Editor.log
- macOS: ~/Library/Logs/Unity/Editor.log
- Build Report 분석
- Editor.log 파일에서 Build Report 섹션을 찾아 빌드에 포함된 씬, 스크립트, 에셋을 확인합니다. 누락된 에셋이 있는 경우 Addressables 또는 Resources 폴더로 옮겨 빌드에 포함시킬 수 있습니다.
참고
Editor.log 파일은 빌드 후 다시 빌드하면 덮어쓰기 되므로, 필요한 경우 별도로 복사해 두는 것이 좋습니다.
Development Build와 Script Debugging을 켠 후 빌드하니 화면이 위처럼 나왔다.
나
(Editor.log의 내용을 붙여넣은 후) 에디터 로그야. 문제점을 분석해줘
GPT
이 로그에 포함된 정보를 보면, NullReferenceException 예외가 씬 로딩 과정에서 특정 오브젝트가 인스턴스화되지 않았거나 null인 상태로 참조되었기 때문에 발생한 것으로 보입니다.
로그 분석과 문제 요약
- NullReferenceException 예외 발생 위치
- 예외가 CardSlot.cs의 Setup 메서드 (17번째 라인)에서 발생했습니다.
- 이후 CardInventory의 UpdateAllCardSlot, CardManager의 LoadDeck, DataManager의 StartLoadedGame, 그리고 SceneLoader의 LoadSceneEnd 등 호출 체인 상에 연쇄적으로 영향을 미칩니다.
- 가능한 원인
- CardSlot.Setup에서 CardData 오브젝트를 참조할 때, 해당 오브젝트가 null이어서 예외가 발생한 것입니다. 이는 CardData가 제대로 초기화되지 않았거나 씬 전환 시 불러오지 못했음을 의미합니다.
- 이와 같은 null 참조 예외는 씬이 빌드 시 필요한 리소스를 제대로 로드하지 못하거나, 코드에서 오브젝트 참조를 올바르게 초기화하지 않았을 때 자주 발생합니다.
- 빌드와 에디터 간 동작 차이
- 에디터에서 잘 작동하지만 빌드 후에는 오류가 발생하는 것은 주로 프리팹, 리소스 파일, Addressables 또는 ScriptableObject가 빌드 설정에 포함되지 않았기 때문일 가능성이 큽니다.
- 특정 객체나 리소스가 참조되지 않을 때 Unity는 에디터 모드에서는 자동으로 참조할 수 있지만, 빌드에서는 명시적으로 불러와야 합니다.
해결 방법 제안
- CardData 인스턴스 확인
- CardSlot.Setup 메서드에서 CardData가 null이 아닌지 확인하는 코드를 추가해보세요.
csharp코드 복사public void Setup(CardData item) { if (item == null) { Debug.LogError("CardData is null in CardSlot.Setup"); return; } // Other setup code }- 이처럼 null 검사를 추가하면 어떤 참조가 누락되었는지 확인이 쉬워질 수 있습니다.
- 리소스 및 Addressables 설정 확인
- CardData나 CardSlot이 프리팹 또는 Addressables로 관리되는 경우, 빌드에 포함되었는지 다시 확인하세요.
- Addressables로 관리된다면 Addressables 그룹이 빌드에 포함되도록 설정이 필요한지 확인합니다.
- 빌드된 파일에서 디버깅 확인
- Unity의 개발 빌드(Development Build) 설정을 활성화하여 좀 더 상세한 디버그 메시지를 확인할 수 있습니다.
- "Auto Connect Profiler"와 "Script Debugging" 옵션도 활성화하면 빌드 후 프로파일러와 디버거를 사용하여 원인 분석에 도움이 됩니다.
위 작업을 진행한 후에도 문제가 지속되면, 추가적인 프리팹 설정 또는 씬 전환과 관련된 리소스 관리 방식(특히 Addressables 활용 여부)을 점검하는 것이 좋습니다.
여기서 빌드 후 실행 중 NullPointerException이 발생했음을 알게 되었고, 스크립트를 추적해나갔다.
Development Build로 디버깅하기
이후 문제가 발생한 함수들 아래 Debug.LogError("");를 삽입했고, CardSlot은 몇 번째 카드 슬롯에서 에러가 났는지를 파악하기 위해 gameObject.name을 함께 출력하게 했다.
세이브 데이터를 불러오는데 성공했고, 6개의 카드가 deck에 있다.
하지만 첫번째 카드 슬롯부터 item이 null이었다.
즉, 파일은 불러오고 읽었지만, 그 내용이 이상하다는 것.
이후 수많은 디버깅 끝에 알아낸 건, 파일을 읽은 후 deck에 저장할 때부터 null이 들어갔다는 것이다.
그런데, 에디터에선 잘 동작했다. 그럼 왜 빌드 시에만 null이 나왔을까?
CardData가 빌드에 포함되지 않았다?
ScriptableObject로 만들어서 가능성이 있어 보였지만, 수많은 삽질 끝에 새로 시작하면 정상적으로 불러와진다는 걸 깨달았다.
즉, 포함이 안 된 경우가 아니다.
아, 진짜 설마.
해결 방법은 의외의 곳에서 찾았다. 새로 시작을 하면 제대로 작동하는 걸 깨닫고, 새로 시작 후 로비로 돌아가기로 새 세이브 파일을 만들었는데...
고마우면서도 화날 정도로 잘 작동했다.
그래서 해결인가? 싶었는데, 게임을 재실행하면 또 먹통이 되었다.
이후의 삽질들
이후로도 스크립트 호출 순서도 바꿔보고, 싱글톤 패턴의 Instance가 생성 안 됐나 싶어 체크도 해봤지만 버그는 해결되지 않았다. 전부 이론상 가능한 것들이었지만, 핵심이 아니었다.
해결은 의외의 곳에서
이후 코드를 뒤져보다가, 이상한 점을 하나 발견했다.
왜 LoadData를 아무데서도 실행 안 시키는데?
몇 시간 전에도 이걸 발견하고 Awake, Start에 넣어봤으나 해결되지 않았었다.
그런데, 보면 볼수록 이상한 것이, Json 파일을 해석하지 않았는데 어떻게 지금껏 로딩이 됐지?
만약 저게 없어서 데이터 파일이 없는 채였다면, null이 들어간 것도 해석은 된다. 다만, 지금까진 deck 6장이 null로 들어왔기에, 6장이 맞다는 것에 빠져 넘어갔다.
이후 SceneLoad 후 실행되는 함수의 첫 부분에 넣어봤는데, 해결됐다.
...
시험과 맞바꾼 삽질. 하지만 배운 점
저걸 지금까지 빼먹은 것에 화가 났지만 다행히 문제가 해결됐고, 그 과정에서 배운 점도 있었다.
우선 첫번째로, Development build에 대해 배웠다.
빌드 파일을 실행하면서, 오류가 있으면 콘솔을 띄워주는 고마운 친구. 덕분에 에디터에선 발견할 수 없는 에러들을 디버깅할 수 있었다.
다음으론 Addressable 패키지. 비록 써먹진 못 했지만, 이걸 쓰면 클릭으로 간단히 빌드에 포함시킬 수 있다.
가장 늘어난 건 GPT 사용법인 것 같다. 정확히 상황을 파악하고, 내가 가진 문제들을 던져준 후 후보군들을 공부해가며 위의 두 가지를 배울 수 있었다. 물론 내일이 시험이고 아직 공부는 시작도 못 했지만, 유니티 개발자로서 성장했으면 된 거 아닐까? ...비록 시험 공부는 못 했지만.
가끔은 1인 개발이 그리울 때가 있다.
협업하는 사람이 늘어나고, 코드의 양이 기하급수적으로 올라가면 혼자 개발하던 때가 그리워지곤 한다. 특히 남이 짠 코드를 내가 디버깅할 땐 참담하기 그지없다.
하지만 여럿이 함께 했기에 여기까지 올 수 있었다. 앞으로도 협업할 일은 많을 거고, 그때마다 불평하는 건 내 성격에 안 맞다. 결국 내가 더 잘 이해하면 해결 가능하니까. 뒤끝은 좀 있겠지만
요즘 소프트웨어공학 수업을 듣고 있는데, 참 마음에 든다. 단순히 클린 코드, 디자인 패턴을 따로 학습하면서 이렇게 하면 쉬울 것 같은데?했던 두루뭉술한 것들이, 정규 수업으로 들어오니 그렇게 재밌을 수가 없다.
앞으로도 미래의 나를 위해, 그리고 내 코드를 읽을 동료들을 위해. 읽기 좋고 유지보수 편하고 효율적인 코드를 위한 공부는 계속될 것이다.
'삽질의 기록' 카테고리의 다른 글
[C++] 소인수분해: 두 소수 p, q의 곱 n이 주어질 때, p, q 알아내기 (0) | 2023.12.12 |
---|---|
[Unity] UI Text 가비지 제거, 삽질의 기록 (0) | 2023.02.13 |