| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
- c#
- PS
- Toy Project
- BOJ
- C++
- Network Programming
- 독서
- Data Structure
- git
- multi-thread
- Online Judge
- System Programming
- Unity
- Today
- Total
I'm FanJae.
[20260529] Unity 정리 ( 싱글톤 패턴, Singleton Pattern ) 본문
1. 정의
- 싱글톤 패턴은 특정 클래스의 객체가 프로그램 전체에서 단 하나만 존재하도록 보장하고, 어디서든 그 객체에 접근할 수 있게 만드는 디자인 패턴이다.
- Unity에서는 보통 GamaManager , SoundManager , UIManager 처럼 게임 전체에서 하나만 존재해야 하는 관리자 객체에서 자주 사용한다.
public static GameManager Instance { get; private set; }
- 위 코드는 외부에서 GameManager.Instance 로 접근할 수 있지만, Instance 를 바꾸는 것은 클래스 내부에서만 가능하게 만든다.
2. 필요한 이유
- 게임에서는 여러 오브젝트가 같은 관리자 객체에 접근해야 하는 경우가 많다.
- 예를 들어 점수를 올릴 때마다 매번 GameManager를 찾아야 한다면 번거롭다.
GameManger.Instance.AddScore(10);
- 싱글톤을 사용하면 위처럼 전역 접근 지점을 통해 쉽게 호출이 가능하다.
3. 적용 시 유의 사항
- GameManager 가 여러 개 생기면 점수, 상태, UI 갱신 기준이 꼬일 수 있다.
- 예를 들어, A GameManager 는 점수 10점, B GameManager 는 점수 0점을 가지고 있다면, 게임 점수가 무엇인지 애매해진다.
- 그래서 Awake()에서 이미 인스턴스가 존재하는지 검사한다.
private void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
- Unity에서 Awake는 Start보다 먼저 호출되므로, 싱글톤 인스턴스 초기화 위치로 자주 사용된다.
4. 적용 예시
using TMPro;
using UnityEngine;
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
[SerializeField] private TextMeshProUGUI scoreText;
private int score;
private void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject); // 씬이 전환되어도 파괴되지 않게 만듦. (삭제되지 않음)
}
else
{
Destroy(gameObject);
}
}
private void Start()
{
score = 0;
UpdateScoreUI();
}
public void AddScore(int amount)
{
score += amount;
UpdateScoreUI();
}
private void UpdateScoreUI()
{
if (scoreText != null)
{
scoreText.text = $"Score : {score}";
}
}
}
- 일반적으로 Unity에서 씬을 새롭게 로드하는 경우 기존 씬 오브젝트는 모두 파괴되지만, DontDestoryOnLoad 를 호출하면 유지된다.
5. 제네릭 싱글톤 패턴
- 매니저 클래스마다 같은 싱글톤 코드를 반복 작성하면 중복 작성이 많아진다.
public static GameManager Instance { get; private set; }
private void Awake()
{
if (Instance == null)
{
Instance = this;
}
else
{
Destroy(gameObject);
}
}
- 이런 공통 로직을 부모 클래스로 빼면 제네릭 싱글톤을 만들 수 있다.
using UnityEngine;
public class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
public static T Instance { get; private set; }
protected virtual void Awake()
{
if (Instance == null)
{
Instance = this as T;
}
else
{
Destroy(gameObject);
}
}
}
- 사용하는 쪽에서는 이와 같이 사용할 수 있다.
public class GameManager : Singleton<GameManager>
{
public void AddScore(int amount)
{
Debug.Log($"Score + {amount}");
}
}
6. 장점
- 싱글톤의 장점은 하나의 객체만 사용하므로 게임 상태를 중앙에서 관리하기 쉽다.
GameManager.Instance.AddScore(100);
- 또한 다른 스크립트에서 참조를 일일이 연결하지 않아도 접근할 수 있어 코드 작성이 편하다.
7. 주의점 또는 단점
- 싱글톤은 편하지만 남용하면 문제가 생긴다.
- 가장 큰 문제는 의존성이 강해진다.
GameManager.Instance.AddScore(10);
- 이 코드가 여러 클래스에 퍼져있으면 각 클래스가 GameManager 에 의존하게 되고, 나중에 GameManager 구조가 바뀌면, 관련 코드도 수정이 필요하다.
8. 싱글톤을 대체할 수 있는 방법
- 싱글톤은 편하지만 모든 객체를 Instance로 접근하게 만들면 의존성이 강해진다. 그래서 상황에 따라 다른 방법을 쓰는 것이 더 낫다.
① Inspector 참조
- 가장 단순한 대체 방법은 필요한 객체를 직접 연결하는 것이다.
public class Player : MonoBehaviour
{
[SerializeField] private GameManager gameManager;
private void OnKillEnemy()
{
gameManager.AddScore(10);
}
}
- 이 방식은 Player가 어떤 GameManager를 사용하는지 Inspector에서 바로 확인할 수 있다.
- 단점은 오브젝트가 많아지면 참조 연결이 번거롭다는 점이다.
② ScriptableObject 사용
- 이런 기법도 있다고 하는데… 이건 나중에 좀 더 알아봐야 할 것 같다.
③ 이벤트 기반 구조
- 객체가 서로 참조하지 않아도 되게 만들고 싶다면 이벤트를 사용할 수 있다.
public class Enemy : MonoBehaviour
{
public static event System.Action<int> OnEnemyKilled;
private void Die()
{
OnEnemyKilled?.Invoke(10);
Destroy(gameObject);
}
}
public class GameManager : MonoBehaviour
{
private int score;
private void OnEnable()
{
Enemy.OnEnemyKilled += AddScore;
}
private void OnDisable()
{
Enemy.OnEnemyKilled -= AddScore;
}
private void AddScore(int amount)
{
score += amount;
}
}
- 이 방식을 사용하면 Enemy가 GameManger 를 직접 알 필요가 없다.
- 대신 이벤트 구독 해제를 제대로 해줘야 하고, 예상하지 못한 호출이나 메모리 참조 문제가 생기지 않게 주의해야 한다.
④ 의존성 주입
- 의존성 주입은 객체가 필요한 의존 객체를 직접 찾지 않고, 외부에서 전달받는 방식이다.
- Unity에서는 간단히 생성자 주입보다는 메서드나 초기화 함수로 전달하는 형태를 많이 쓴다.
public class Player
{
private readonly IScoreService scoreService;
public Player(IScoreService scoreService)
{
this.scoreService = scoreService;
}
public void KillEnemy()
{
scoreService.AddScore(10);
}
}
- 인터페이스를 사용하면 실제 점수 시스템을 교체하기가 쉬워질 수 있다.
public interface IScoreService
{
void AddScore(int amount);
}
- 다만, Unity의 MnonoBehaviour 는 new 로 직접 생성하는 구조가 아니다. 따라서 일반 C# 객체보다 DI 적용이 번거로워질 수 있다.
9. 정리
- 싱글톤 패턴은 객체를 하나만 만들고, 전역적으로 접근할 수 있게 하는 패턴이다.
- Unity에서는 GameManager, SoundManager, DataManager처럼 게임 전체에서 하나만 존재해야 하는 객체에 자주 사용된다.
- 다만, 모든 관리자를 무조건 싱글톤으로 만들면 클래스 간 의존성이 강해지고 유지보수가 어려워질 수 있다.
- 따라서 게임 전체 상태를 관리해야 하는 객체에만 제한적으로 사용하는 것이 좋다.
'Unity > Unity 초격차캠프' 카테고리의 다른 글
| [20260528] Unity 정리 ( Coroutine ) (0) | 2026.05.28 |
|---|---|
| [20260528] Unity 정리 ( Find 계열 ) (0) | 2026.05.28 |
| [20260527] Unity 정리 ( UI Canvas ) (0) | 2026.05.27 |
| [20260527] Unity 정리 ( Overlap 계열, Check 계열 ) (0) | 2026.05.27 |
| [20260527] Unity 정리 ( Unity Raycast 응용 : LayerMask, Tag, Raycast를 이용한 상호작용 ) (0) | 2026.05.27 |