-Static
static 키워드는 클래스 멤버나 메서드에 사용되어 해당 멤버나 메서드가 클래스의 인스턴스 없이 호출되도록 한다.
static 키워드가 붙은 멤버는 인스턴스가 아니라 클래스 자체에 속하게 되므로, 해당 멤버는 클래스의 모든 인스턴스에서 공유됩니다. static 키워드를 사용한 변수는 클래스가 메모리에 올라갈 때 자동으로 생성이 된다.
이에따라 해당 멤버나 메서드가 클래스의 인스턴스 없이 호출될 수 있도록 하여, 클래스의 모든 인스턴스에서 공유된다 .
-Singleton Class
이에따라 개발을 하다보면, 게임 시스템을 관리하는 기능들이 필요할 수가 있다.
예를들어, 뱀서라이크, 슈팅게임 등 오브젝트 생성과 파괴가 빈번할 작업을 Object Pooling을 통해 메모리 관리를 효율적으로 할 수 있기에, Object Pool class는 여러 오브젝트에서 자주 호출되어 사용될 것이다.
따라서 게임의 여러 중요 시스템을 맡을 각각의 class가, 어디서든 접근할 수 있도록 만들어야 하는데, 이는 디자인 패턴 중 하나인 Singleton Class를 통해 구현할 수 있다. 이를 위해 정적 변수를 사용하여 클래스의 인스턴스를 저장하고 정적 메서드를 사용하여 만들 수 있다.
-Manager Class
그래서, 앞서말한 게임 시스템을 관리하는 class들이 필요하다 했는데, 보통 이를 Manager class라 부른다. 이 Manager Class의 특징은 다음과 같다..
1. 게임 시스템에서 전체를 관장
2. 게임 시스템상 전역 변수의 역할
3. 씬 로드시 데이터가 파괴되지 않고 유지
4. 여러 오브젝트에서 접근이 가능한 스크립트의 역할
5. 단 한개의 객체만 존재
그러면 Manager Class는 어떻게 만들까
public class GameManager
{
private static GameManager Inst;
private void Awake()
{
if (Inst == null)
{
Inst = this;
DontDestroyOnLoad(gameObject);
}
else
{
if (Inst != this)
Destroy(this.gameObject);
}
}
}
이렇게 작성하면 기본적인 구조는 완성이다. Static변수를 통해 GameManager의 Inst를 전역변수로 만들어 준다.
그 후 Awake 함수에서, Inst에 자기자신을 넣어주고, 씬 로드시 파괴가 되게 하면 안되기에, DonDestroyOnLoad를 이용해준다.
보통 이제 else문에서 처리하는 부분이 작성하는 스타일 마다 다르긴 한데, 결국에는 싱글톤 클래스는 단 한개의 객체만 존재해야하기 때문에, 이를 보장해주는 것이다. 즉 Inst가 이미 있다면 자기자신을 Destroy해 Awake를 취소하고, 단 한개만 있도록 보장해주는 것이다.
아래는 예전에 Turn based Card Game 개발을 연습하면서 만든 TurnManager script이다. 이런식으로 Manager를 만들어서, 게임 내에서 Turn 시스템을 관리할 수 있도록 한다.
public class TurnManager : MonoBehaviour
{
public static TurnManager Inst { get; private set; }
[SerializeField] public Button turnEndButton;
[SerializeField] public float turnCountFloat;
[SerializeField] public int turnCount;
[SerializeField] private ETurnState eTurnState;
enum ETurnState {My, Enemy}
public bool isLoading;
public bool myTurn;
WaitForSeconds _delay05 = new WaitForSeconds(0.5f);
WaitForSeconds _delay10 = new WaitForSeconds(1.0f);
public static Action onEnemyTurnStart;
public static Action onEnemyCanAttack;
public static Action onPlayerTurnStart;
public static Action onPlayerTurnEnd;
#region 이벤트 함수
void Awake()
{
Inst = this;
}
#endregion
void GameSetup()
{
switch (eTurnState)
{
case ETurnState.My:
myTurn = true;
break;
case ETurnState.Enemy:
myTurn = false;
break;
}
}
public IEnumerator StartGameCo()
{
GameSetup();
isLoading = true;
yield return _delay05;
//onMyTurnStart.Invoke();
StartCoroutine(StartTurnCo());
}
IEnumerator StartTurnCo()
{
isLoading = true;
yield return _delay10;
if (myTurn)
{
//onMyTurnStart.Invoke();
//GameManager.Inst.Notification("내 턴");
}
else
{
//GameManager.Inst.Notification("적 턴");
onEnemyTurnStart.Invoke();
EndTurn();
}
yield return _delay10;
isLoading = false;
if (myTurn)
{
turnEndButton.interactable = true;
}
}
public void EndTurn()
{
turnCountFloat += 0.5f;
if (turnCountFloat % 1 == 0)
{
turnCount += 1;
}
myTurn = !myTurn;
StartCoroutine(StartTurnCo());
}
}
-사용시 장점과 주의사항
우선 장점으로는 인스턴스가 하나만 있기 때문에, 중복된 리소스 사용을 방지하고, 다른 클래스에서 쉽게 액세스할 수 있으므로 코드의 재사용성이 높아지며, 전반적인 리소스를 효율적으로 관리할 수 있다.
주의사항은 크게 2가지인데,
첫번째로는 Manager Class의 남발은 코드간의 지나친 의존성문제를 일으켜서, 코드를 오히려 복잡하게 만들 수 있다.
어디서나 쉽게 접근할 수 있다는 점때문에, Manager Class의 Method를 수정하면, 그 Method를 이용하는 다른 script를 모두 수정해줘야 하는 등의 단점이 있다.
두번째로는 전역 변수와 비슷하게, 다른 코드에서 인스턴스에 대한 수정을 수행한다면, 예기치 않은 결과를 초래할 수 있고, 메모리에 미리 올려두고 사용한다는 점에의해, 메모리 누수가 발생할 수 있다.
즉, Singleton class의 객체는 게임이 실행되는 동안 계속 실행되어 있기 때문에, 필요하지 않은 객체는 적시에 삭제하여 메모리 누수를 방지해야 한다. 또한, 긴 시간 동안 객체를 사용해야 하는 경우에는, 메모리 사용량을 최소화하기 위해 인스턴스를 적절하게 관리하고, 주기적으로 메모리를 해제해주는 것이 좋다.
그래서 처음 c#과 유니티를 배울때는 싱글톤 패턴(메니저 클래스)를 보고서 정말 편한 기능이구나 싶어서 남발을 했었는데, 결국에는 굳이 싱글톤으로 만들필요가 없는 부분들 조차 싱글톤으로 만들다가, 객체지향의 원리를 해쳐 애를 먹은적이 있다. 결국에는 모든 디자인 패턴이 그렇겠지만, 필요한 기능에따라 적절한 사용과 구현할 수 있는 능력을 키워야할 것 같다.
'Unity Engine' 카테고리의 다른 글
Flast White, Shader (0) | 2023.04.02 |
---|---|
구글 스프레드 시트 - 유니티 연동 Asset (0) | 2023.03.23 |
Input System (0) | 2023.03.19 |
접근제한자, Property (0) | 2023.03.16 |
Unity 최적화 (3) | 2023.03.09 |