본문 바로가기

유니티 개발 정보/프로그래밍

유니티에서 정적(static) 변수 사용하기

원문 보기

니티에서 정적(static) 변수 사용하기

이 글은 유니티3D 엔진에서 스크립트를 작성할 때, 정적 변수를 사용하는 방법에 대해서 설명하는 글이다. 이 글에 나와있는 예제 스크립트는 C#으로 작성되었지만, 똑같은 가이드라인을 자바스크립트에서도 적용할 수 있다.

이 주제에 대해서 본격적으로 이야기하기 전에, 인터넷에서 손쉽게 찾을 수 있는 "정적 변수는 무슨 수를 써서라도 피해야 한다"는 여러 이유에 대한 부분은 말하지 않을 것이다. 대부분의 경우에 있어서, 싱글톤 패턴을 사용하는 것이 더 낫다. 이 점을 염두해 두고, 여기 정적 변수에 대한 간단한 정의를 소개하겠다.
이들은 클래스에 속해 있는 변수이며, 클래스가 생성하는 객체에는 존재하지 않는다. 이것은 정적 변수가 특정 클래스의 객체에 상관없이, 같은 값을 유지한다는 것을 의미한다. 또, 클래스가 메모리에 있는 한, 항상 참조가 가능한 특성이 있다. 정적 변수는 클래스가 만들어지기 직전에, 그리고 다른 변수와 메소드보다 먼저  컴파일러에 의해서 초기화 된다. 이러한 사실이 유니티3D 스크립트에서는 어떤 의미를 가질까?
바로, 변수를 수정하거나, 읽을 때, 클래스 객체를 만들 필요가 없다는 것을 의미한다. 그 결과, 오직 한번만 게임오브젝트와 컴포넌트들의 참조를 얻으면 된다.
예를 들어: 한 씬에 50개의 적이 있고, 모든 적들은 Player의 Transform을 필요로 하는 A.I 스크립트를 가지고 있다고 생각 해보자. 각 적들의 Awake()에서 정적 변수를 사용하지 않는다고 했을 경우:
  1. public class EnemyBehaviour: MonoBehaviour   
  2. {  
  3.     private Transform playerTransform;  
  4.       
  5.     void Awake()  
  6.     {  
  7.         playerTransform = GameObject.FindWithTag("Player").GetComponent<Transform>();  
  8.     }  
  9.   
  10.     void Update()  
  11.     {  
  12.         //do something based on the player's position  
  13.         if(playerTransform.postion.x>100)  
  14.         {  
  15.             //Do something  
  16.         }         
  17.     }  
  18. }  


딱 한번의 정적 변수를 사용하여, 위와 동일하기 위해서 'GameController'라는 이름의 스크립트를 만들었다. 
Player의 Transform을 한번 얻고, 이 값을 정적 변수에 저장한다:

  1. public class GameController : MonoBehaviour   
  2. {  
  3.     //creating a static variable  
  4.     public static Transform playerTransform;  
  5.       
  6.     void Awake()  
  7.     {  
  8.         playerTransform = GameObject.FindWithTag("Player").GetComponent<Transform>();  
  9.     }  
  10. }  


그리고 나서, 적의 A.I가 Player의 캐릭터 위치를 알아야 할 때, 단지 다음과 같이 변수를 사용하면 된다.

  1. public class EnemyBehaviour: MonoBehaviour   
  2. {  
  3.     void Update()  
  4.     {  
  5.         //acessing the static variable  
  6.         if(GameController.playerTransform.position.x > 100)  
  7.         {  
  8.             //Do something  
  9.         }         
  10.     }  
  11. }  



두번째 예제에서, Player의 Transform을 알기 위해 단 한번의 Awake()를 호출했는 반면에, 첫 번째 예제에서는 각 적마다 Awake()를 호출함으로써, 총 50번의 호출을 하였다.

마지막 예제에서, 정적변수는 씬에 있는 50개의 적에게 사용되었다. 여전히 한번의 Awake() 호출이 필요하긴 하다. GameController에서 Player의 Transform을 얻기 위해 사용되었기 때문인데, 이 참조는 정적 변수로 노출되었다. 결국 각 적마다 호출되는 Awake()는 꼭 필요하지 않았던 것이다.

유니티3D에서 정적변수는 언제 반드시 사용되어야 하는가?

마지막 예제로 보았을 때, 여러 스크립트의 Awake() 혹은 Start()에서 종종 반복적으로 사용되는 게임 오브젝트나 컴포넌트의 참조를 저장할 때 사용하는 것을 추천한다.
이 뿐만 아니라, 게임의 룰에 관련된 기본형 변수 또한 정적 변수를 사용하는 좋은 예가 될 수 있는데, 간단히 예를 들면, 게임 점수, 플레이어의 체력, 게임의 상태(playing, paused, game over), 플레이어의 상태(respawning, alive, dead)같은 것을 말한다.

그렇다면 언제 정적 변수를 사용하지 말아야 하는가?

정적 변수를 언제 사용해야하는지 알았기 때문에, 정적 변수를 언제 사용하면 안되는지 보자: 위에서 언급된 그 밖의 다른 어떤 곳에서도 사용하면 안된다. 
다른 방법이 없거나, 싱글톤 패턴을 구현하는 경우가 아니라면, 무슨 수를 써서라도 정적 변수의 사용을 피해야 한다.
경험상, 성능 향상에 도움이 되는 것이 명확하지 않을때는 정적 변수를 만들지 마라.
다양한 스크립트에 있는 모든 객체의 참조를 정적 변수로 만드는 것은 그렇지 않는 것보다 더 쉽지만, 이는 이미 만들어진 클래스에 불필요한 코드 리펙토링을 초래할 수도 있다. 유니티에서, 정적 변수에 관련해서는,  적은 것이 많은 것보다 확실히 낫다.

스크립트의 시작부분에 항상 정적 변수의 값을 초기화 하자

정적 변수를 덜 사용해야 하는 또 다른 이유는, 정적 변수는 항상 초기화가 필요하기 때문이다. 예를 하나 들어 보자: 플레이어 캐릭터가 죽었는지 살았는지 확인하기 위한 'isDead'라는 이름의 불리안 타입의 정적 변수가 있다. 기본 값으로는 false가 설정되어 있고, 이 값이 true가 되었을 때, 'Game Over' 씬이 로드 될 것이다.
  1. public class GameController : MonoBehaviour   
  2. {  
  3.     /*creates a static variable to check if  
  4.     the main character has died*/  
  5.     public static bool isDead = false;  
  6.       
  7.     void Update ()   
  8.     {  
  9.         //if the main character is dead  
  10.         if(isDead)  
  11.         {  
  12.             //Loads the 'Game Over' scene  
  13.             Application.LoadLevel("GameOver");  
  14.         }  
  15.     }  
  16. }  


위의 스크립트는 잘 작동하고, 메인 캐릭터가 죽은 다음, 유니티3D는 'GameOver' 씬을 로드할 것이다.
그리고, 다시 플레이하기 위해 게임 씬으로 돌아 왔을 때, 유니티3D는 즉시 'GameOver' 씬으로 바꾸어버릴 것이다. 정적 변수는 클래스 변수이고, 이 값은 클래스가 메모리에 존재하는 한 계속해서 유지 될 것이며, 이 말은 5번째 라인은 게임이 실행될 때 딱 한번 실행될 것이라는 의미이다.
위의 예제를 본 다면, isDead는 계속 true일 것이다. 만약 스크립트의 시작 부분에 isDead변수를 초기화하지 않는다면, 계속해서 이런 일이 발생할 것이다.

이를 방지하기 위해, 정적 변수는 Awake() 혹은 Start()에서 값을 초기화 해야 한다.
위의 예제를 다시 재작성해보면, 정적 변수는 Awake()에서 값이 false로 리셋될 것이다:

  1. public class GameController : MonoBehaviour   
  2. {  
  3.     /*creates a static variable to check if  
  4.     the main character has died*/  
  5.     public static bool isDead = false;  
  6.       
  7.     void Awake()  
  8.     {  
  9.         //resetting isDead value to 'false'   
  10.         isDead = false;  
  11.     }  
  12.       
  13.     void Update ()   
  14.     {  
  15.         //if the main character is dead  
  16.         if(isDead)  
  17.         {  
  18.             //Loads the game over scene  
  19.             Application.LoadLevel("GameOver");  
  20.         }  
  21.     }  
  22. }  


위 스크립트는 우리가 예상한대로 정상적으로 작동할 것이다. 정적 변수는 스크립트의 앞부분에 초기화 해야한다는 것을 언제나 명심하자.