Priv's Blog

SerializeField 속성과 변수 초기화, Null 참조 에러 본문

Dev. Study Note/Unity

SerializeField 속성과 변수 초기화, Null 참조 에러

Priv 2024. 7. 28. 12:44


 

 

1. 캡슐화

Unity 엔진은 C# 언어를 스크립트 언어로 사용한다.

C#은 대표적인 OOP 언어 중 하나로, OOP 언어의 대표적인 특징 중 하나인 캡슐화를 보장하기 위해 다양한 속성들을 제공한다.

그중 대표적인 속성 중 하나가 [SerializeField] 속성이다.


 

Apply object-oriented principles: Encapsulation in object-oriented programming

출처 Encapsulation in object-oriented programming - Unity Learn In this tutorial, you’ll learn about the second pillar in object-oriented programming: encapsulation. Explain how encapsulation is used to write code that can only be used as intended by t

arainablog.tistory.com


이 속성은 변수의 접근 제어자를 private로 설정한 상태로 Unity 엔진의 Inspector 창에서만 해당 변수의 값에 참조를 제어하고 싶을 때 사용한다.

public을 사용해도 Inspector 창에서 참조를 제어할 수 있지만, public 접근 제어자는 외부에서도 해당 변수에 접근할 수 있도록 개방하는 형태이기 때문에 캡슐화 측면에서 보면 위험성이 커진다.

Getter와 Setter를 이용하여 접근 제어를 부분적으로 조작할 수도 있겠지만, private의 폐쇄성을 그대로 가져가면서 오로지 Unity 엔진의 에디터 상에서만 참조를 조작하기를 원한다면 [SerializeField] 속성을 사용하는 것이 더 편할 수 있다.

 


 

2. 객체 초기화

일반적으로 스크립트 상에서 선언된 객체들은 당연히 스크립트 내에서 초기화를 하여 사용한다.

private StringBuilder textBuilder;


private void Init() {
    this.textBuilder = new();
    this.textBuilder.Append("Hello, world!");
}

private void Awake() {
    Init();
}

private void Start() {
    Print();
}

private void Print() {
    Debug.Log(this.textBuilder.ToString());
}

위 코드를 보면 private 접근 제한자를 사용한 StringBuilder 타입의 변수, textBuilder를 Init( ) 메서드에서 new 키워드를 사용하여 초기화하였다.

이후 Start( ) 메서드 내에서 Print( ) 메서드를 호출함으로써 초기화 한 textBuilder 변수를 사용해 "Hello, world!"라는 문자열을 디버그 콘솔에 출력하도록 하였다.

이는 어차피 스크립트 내에서 모든 것을 제어하기 때문에 문제가 벌어질 이유가 없으며, 설령 에러가 발생한다고 하더라도 외부의 요인(Unity 에디터 상에서의 설정 등)까지 고려할 필요가 없다.

스크립트 내에서 변수의 선언, 초기화, 사용까지 모두 이루어졌다.

 


 

3. [SerializeField] 속성의 등장

[SerializeField] 속성이 사용된다면 이야기가 달라진다.

이때부터는 에러가 발생했을 경우, 스크립트 내부의 요인과 스크립트 외부의 요인을 모두 고려하여 코드를 분석해야 한다.

이 속성이 사용되면 변수의 참조를 Unity 에디터 상(스크립트 외부)에서 조작할 수 있기 때문이다.

만약 Null 참조 에러가 발생했다면 스크립트 내에서 변수를 제대로 초기화하였는지, 만약 Unity 에디터 상에서 초기화를 담당하도록 설계하였다면 Unity 에디터 상에서 제대로 초기화하였는지를 함께 검사해야 한다.

또한 Unity 에디터 상에서 제대로 초기화를 하였는데, 실수로 스크립트 내에서 new 키워드를 이용해 다시 초기화하지는 않았는지도 함께 고려해야 한다.

이는 생각보다 쉽게 나타날 수 있는 실수로, 스크립트 내에서는 private 변수로 취급되기 때문에 주의 깊게 보지 않으면 놓치기 쉽다.

무엇보다 스크립트 로직 상에서는 어쨌든 간에 초기화가 이루어졌기 때문에 사용하고 있는 IDE가 에러를 제대로 잡아낼 수 없을 확률이 높아 주의를 기울여야 한다.

    [SerializeField] private UnityEvent OnPlayerBehaviourInit;


    public void Init() {
        // this.OnPlayerBehaviourInit = new();
        this.OnPlayerBehaviourInit.Invoke();
    }

위의 코드는 문제가 없는 코드이다.

Inspector 창에서 이벤트를 제대로 설정하였으며, 위 스크립트에 적힌 Init( ) 메서드가 호출되면 Inspector 창에서 설정한 여러 스크립트들의 Init( ) 메서드가 이벤트 호출에 반응할 것이다.

하지만 여기서 아래와 같이 주석을 풀면 문제가 발생한다.

    [SerializeField] private UnityEvent OnPlayerBehaviourInit;


    public void Init() {
        this.OnPlayerBehaviourInit = new();
        this.OnPlayerBehaviourInit.Invoke();
    }

Unity 엔진은 스크립트 내에서 설정한 변수의 값보다 Inspector 내에서 설정한 변수의 값을 우선적으로 가져와 사용한다.

이 때문에 위 코드를 보면 Unity 엔진에서 설정한 이벤트의 참조 값이 스크립트로 넘어오기는 하지만, 위 스크립트의 Init( ) 메서드가 호출되어 이벤트 변수를 초기화하는 코드(new 키워드를 사용한 코드)를 만나면 '덮어쓰기'가 이루어진다.

이 때문에 Unity 에디터 상에서 설정해 둔 모든 이벤트 참조 값이 날아가고 이벤트 참조가 되어있지 않은, '텅 빈' 이벤트 변수만 남게 된다.

이 상태에서는 Invoke( )를 호출한다고 해도 아무런 일이 벌어지지 않는다.

하지만 IDE 상에서는 이러한 에러가 제대로 잡히지 않으며, 이는 어쨌든 간에 new 키워드를 사용해 이벤트 변수를 초기화했기 때문에 로직 상에서는 문제가 없기 때문이다.

 


 


수고하셨습니다!


Comments