[Unity] 인스펙터 값이 저장해도 초기화될 때, 범인은 ExecuteAlways! (Application.isPlaying으로 완벽 해결)

유니티(Unity)로 개발할 때 마주치는 가장 당혹스러운 문제 중 하나는, 분명히 인스펙터(Inspector)에서 값을 설정하고 씬을 저장했는데도 불구하고, 특정 조건 하에 그 값이 초기화되는 현상입니다. 마치 유령처럼 사라지는 이 값 때문에 원인을 찾느라 소중한 개발 시간을 허비하는 경우가 많습니다.
이 문제는 단순히 플레이 버튼을 눌렀을 때뿐만 아니라, 에디터 상에서 게임 오브젝트를 비활성화했다가 다시 켜는 것만으로도 발생할 수 있어 개발자를 더욱 혼란에 빠뜨립니다. 이 글에서는 해당 문제의 근본적인 원인과, 아주 간단하고 명쾌한 해결책을 제시합니다.

현상 분석: 왜 값은 초기화되는가?
먼저 문제의 증상을 명확히 해보겠습니다.
- [SerializeField]를 통해 인스펙터에 노출된 변수의 값을 수정합니다. (예: monsterHealth를 100으로 설정)
- 씬을 저장(Ctrl+S)하여 변경 사항을 확실히 기록합니다.
- 플레이 버튼을 누르거나, 해당 컴포넌트가 붙은 게임 오브젝트를 껐다가 켭니다.
- 다시 확인해보면 monsterHealth 값이 0이나 다른 기본값으로 돌아가 있습니다.
더 이상한 점은, 같은 스크립트 내의 어떤 변수는 값이 유지되는데 특정 변수만 초기화되어, 버그의 원인을 추측하기 더욱 어렵게 만듭니다. 이 현상의 배경에는 유니티 에디터의 강력한 기능이 숨어있습니다.
진범은 바로 [ExecuteAlways] 속성!
이 문제의 직접적인 원인은 스크립트 상단에 선언된 [ExecuteAlways] 속성일 가능성이 매우 높습니다.
[ExecuteAlways]
public class MonsterController : MonoBehaviour
{
// ...
}
[ExecuteAlways]는 유니티의 생명주기 함수(Awake, Start, Update 등)를 플레이 모드뿐만 아니라 에디터 모드에서도 항상 실행하도록 만드는 강력한 기능입니다. 이 기능 덕분에 에디터에서 실시간 미리보기나 커스텀 툴 제작이 가능해집니다.
하지만 이 강력함은 양날의 검과 같습니다. 플레이 모드에서만 실행되어야 할 코드가 에디터에서 예기치 않게 실행되면서, 개발자가 직접 설정한 인스펙터 값을 덮어쓰는 부작용을 낳을 수 있습니다.

버그의 작동 원리: 에디터에서의 Start() 호출
[ExecuteAlways]가 적용된 스크립트의 Start() 함수에 다음과 같은 코드가 있다고 가정해 봅시다. 이 코드는 게임이 시작될 때 게임 매니저로부터 몬스터의 기본 체력을 받아와 설정하는, 일반적인 런타임 초기화 로직입니다.
public class MonsterController : MonoBehaviour
{
[SerializeField]
private int monsterHealth = 100;
private void Start()
{
// 게임이 시작되면 기본 체력값으로 초기화
this.monsterHealth = GameManager.Instance.GetDefaultMonsterHealth(); // 기본값은 0이라 가정
}
}
이 코드는 플레이 모드에서는 의도대로 작동하지만, 에디터 모드에서는 다음과 같은 문제를 일으킵니다.
- 개발자: 인스펙터에서 monsterHealth를 특별한 보스 몬스터를 위해 5000으로 수정하고 씬을 저장합니다.
- 개발자: 오브젝트를 잠시 비활성화했다가 다시 활성화합니다.
- Unity: [ExecuteAlways] 속성 때문에, 에디터 모드임에도 불구하고 Start() 함수를 호출합니다.
- 코드: Start() 함수는 GameManager로부터 기본 체력값 0을 받아옵니다.
- 코드: 인스펙터에 있던 5000이라는 값은 0으로 덮어씌워집니다.
결과적으로 개발자가 설정한 값은 사라지고, 런타임 초기화 로직이 에디터 데이터를 오염시키는 상황이 발생하는 것입니다.

명쾌한 해결책: Application.isPlaying
이 문제의 해결책은 유니티가 제공하는 간단한 조건문 하나로 완성됩니다. 바로 Application.isPlaying 입니다. 이 프로퍼티는 현재 코드가 플레이 모드에서 실행 중일 때만 true를 반환합니다.
이것을 이용해 런타임 전용 코드를 보호하는 '가드(Guard)'를 세울 수 있습니다.
수정 전 코드:
private void Start()
{
// 게임이 시작되면 기본 체력값으로 초기화
this.monsterHealth = GameManager.Instance.GetDefaultMonsterHealth();
}
수정 후 코드:
private void Start()
{
// 이 코드는 오직 '플레이 모드'에서만 실행되도록 제한합니다.
if (Application.isPlaying)
{
// 게임이 시작되면 기본 체력값으로 초기화
this.monsterHealth = GameManager.Instance.GetDefaultMonsterHealth();
}
}
이렇게 수정하면, 에디터에서 Start() 함수가 호출되더라도 Application.isPlaying이 false이므로 조건문 내부의 코드는 실행되지 않습니다. 따라서 인스펙터에서 설정한 값은 안전하게 보존되며, 해당 로직은 우리가 원래 의도했던 대로 플레이 모드에 진입했을 때만 정상적으로 작동하게 됩니다.
결론
[ExecuteAlways] 속성은 에디터의 생산성을 비약적으로 향상시킬 수 있는 강력한 기능입니다. 하지만 그 힘을 제대로 제어하지 못하면, 데이터가 초기화되는 등 디버깅하기 어려운 문제에 부딪힐 수 있습니다. [ExecuteAlways]를 사용할 때는 Start(), Awake(), OnEnable() 등 생명주기 함수 내의 로직이 에디터에서 실행되어도 안전한지 항상 검토하고, 런타임 전용 코드는 Application.isPlaying으로 보호하는 습관을 들이는 것이 중요합니다.

'Unity' 카테고리의 다른 글
| 유니티 에셋 스토어 소스 코드, 직접 수정 없이 안전하게 커스터마이징 하는 방법 (업데이트 문제 해결) (1) | 2025.08.27 |
|---|---|
| 당신의 유니티 앱이 무거운 진짜 이유: UPM 패키지 최적화 완벽 가이드 (빌드 용량 20MB 줄이기) (0) | 2025.08.24 |
| 유니티 고수만 아는 싱글턴 관리 비법: '이 코드' 한 줄로 하이어라키(Hierarchy) 깔끔하게 정리하기 (0) | 2025.08.22 |
| Unity 이벤트 값 전달이 안될 때? 99%는 이 문제입니다 (동적 vs 정적) (0) | 2025.08.21 |
| Unity Input Field 이벤트 완벽 정복: On Value Changed, On End Edit, On Select, On Deselect 비교 분석 및 활용법 (1) | 2025.08.21 |