싱글톤 패턴이란?
싱글톤 패턴은 클래스(class)의 인스턴스(instance)가 오직 하나만 존재하도록 보장하는 디자인 패턴입니다. 게임 매니저, 사운드 매니저 등 전역에서 접근해야 하는 오브젝트에 사용합니다.
기본 개념
- 단일 인스턴스(instance): 클래스(class)의 인스턴스(instance)가 하나만 존재
- 전역 접근: 어디서든
Instance로 접근 가능 - 자동 생성: 인스턴스(instance)가 없으면 자동으로 생성
- 씬 전환 유지:
DontDestroyOnLoad()로 씬 전환 시에도 유지
1. 기본 싱글톤 구조
기본 싱글톤 구현
using UnityEngine;
public class GameManager : MonoBehaviour
{
// 싱글톤 인스턴스 (정적 프로퍼티)
public static GameManager Instance { get; private set; }
void Awake()
{
// 싱글톤 초기화
InitializeSingleton();
}
private void InitializeSingleton()
{
if (Instance == null)
{
// 첫 번째 인스턴스이면 설정
Instance = this;
// 씬이 변경되어도 파괴되지 않도록 설정
DontDestroyOnLoad(gameObject);
}
else
{
// 이미 인스턴스가 있으면 중복 방지를 위해 파괴
Destroy(gameObject);
}
}
}
사용 예제
using UnityEngine;
public class FallingFireball : MonoBehaviour
{
void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Player"))
{
// GameManager 인스턴스에 접근
if (GameManager.Instance != null)
{
GameManager.Instance.GameOver();
}
}
}
}
2. DontDestroyOnLoad() - 씬 전환 유지
DontDestroyOnLoad()는 씬이 변경되어도 오브젝트가 파괴되지 않도록 합니다.
사용법
using UnityEngine;
public class GameManager : MonoBehaviour
{
public static GameManager Instance { get; private set; }
void Awake()
{
if (Instance == null)
{
Instance = this;
// 씬이 변경되어도 파괴되지 않도록 설정
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
}
주의사항
- 중복 방지: 같은 씬에 여러 개가 있으면 하나만 남기고 나머지는 파괴
- 씬 전환 시 유지: 게임 매니저, 사운드 매니저 등에 사용
- 메모리(memory) 관리: 필요 없을 때는 수동으로 파괴해야 함
3. 실제 사용 예제
예제 1: GameManager 싱글톤
using UnityEngine;
public class GameManager : MonoBehaviour
{
#region Singleton
public static GameManager Instance { get; private set; }
#endregion
#region Events
public System.Action OnGameOverEvent;
public System.Action OnGameRestartEvent;
#endregion
#region Fields
[Header("게임 상태")]
[SerializeField] private bool isGameOver = false;
#endregion
#region Properties
public bool IsGameOver => isGameOver;
#endregion
#region Unity Lifecycle
void Awake()
{
InitializeSingleton();
}
#endregion
#region Initialization
private void InitializeSingleton()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else
{
Destroy(gameObject);
}
}
#endregion
#region Game State Management
public void GameOver()
{
// 이미 게임 오버면 중복 실행 방지
if (isGameOver)
{
return;
}
isGameOver = true;
Debug.Log("Game Over!");
// 게임 오버 처리
HandleGameOver();
}
private void HandleGameOver()
{
// 시간 정지
Time.timeScale = 0f;
// 게임 오버 이벤트 발생
OnGameOverEvent?.Invoke();
}
public void RestartGame()
{
isGameOver = false;
Time.timeScale = 1f;
OnGameRestartEvent?.Invoke();
ReloadScene();
}
private void ReloadScene()
{
UnityEngine.SceneManagement.SceneManager.LoadScene(
UnityEngine.SceneManagement.SceneManager.GetActiveScene().name
);
}
#endregion
}
예제 2: 다른 스크립트에서 사용
using UnityEngine;
public class FallingFireball : MonoBehaviour
{
void OnTriggerEnter2D(Collider2D other)
{
if (other.CompareTag("Player"))
{
// 싱글톤 인스턴스에 접근
if (GameManager.Instance != null)
{
GameManager.Instance.GameOver();
}
}
}
}
using UnityEngine;
public class SheepController : MonoBehaviour
{
void Update()
{
// 게임 오버 상태면 입력 무시
if (GameManager.Instance != null && GameManager.Instance.IsGameOver)
{
return;
}
// 입력 처리...
}
}
4. 프로퍼티를 사용한 접근
읽기 전용 프로퍼티
using UnityEngine;
public class GameManager : MonoBehaviour
{
private static GameManager instance;
// 읽기 전용 프로퍼티
public static GameManager Instance
{
get
{
// 인스턴스가 없으면 자동으로 찾기
if (instance == null)
{
instance = FindObjectOfType<GameManager>();
}
return instance;
}
private set
{
instance = value;
}
}
void Awake()
{
if (Instance == null)
{
Instance = this;
DontDestroyOnLoad(gameObject);
}
else if (Instance != this)
{
Destroy(gameObject);
}
}
}
5. 자동 생성 싱글톤
인스턴스(instance)가 없으면 자동으로 생성하는 싱글톤입니다.
구현
using UnityEngine;
public class GameManager : MonoBehaviour
{
private static GameManager instance;
public static GameManager Instance
{
get
{
if (instance == null)
{
// 씬에서 찾기
instance = FindObjectOfType<GameManager>();
// 없으면 새로 생성
if (instance == null)
{
GameObject go = new GameObject("GameManager");
instance = go.AddComponent<GameManager>();
DontDestroyOnLoad(go);
}
}
return instance;
}
}
void Awake()
{
if (instance == null)
{
instance = this;
DontDestroyOnLoad(gameObject);
}
else if (instance != this)
{
Destroy(gameObject);
}
}
}
6. 주의사항
1. 중복 인스턴스(instance) 방지
void Awake()
{
if (Instance == null)
{
Instance = this;
}
else if (Instance != this)
{
// 중복 인스턴스 파괴
Destroy(gameObject);
}
}
2. null 체크
// ✅ 안전한 방법
if (GameManager.Instance != null)
{
GameManager.Instance.GameOver();
}
// ❌ 위험한 방법 (null 참조 에러 가능)
GameManager.Instance.GameOver();
3. 씬 전환 시 주의
// DontDestroyOnLoad를 사용하면 씬 전환 시에도 유지됨
// 필요 없을 때는 수동으로 파괴해야 함
void OnDestroy()
{
if (Instance == this)
{
Instance = null;
}
}
4. 초기화 순서
// Awake()에서 초기화하는 것이 안전
// Start()는 다른 오브젝트의 Start()보다 먼저 호출되지 않을 수 있음
void Awake()
{
InitializeSingleton();
}
7. 정리
싱글톤 패턴의 장점
- 전역 접근: 어디서든
Instance로 접근 가능 - 단일 인스턴스(instance): 하나의 인스턴스(instance)만 존재 보장
- 메모리(memory) 효율: 불필요한 중복 인스턴스(instance) 방지
싱글톤 패턴의 단점
- 전역 상태: 전역 상태는 테스트와 디버깅이 어려움
- 의존성: 다른 클래스(class)와 강하게 결합될 수 있음
- 멀티스레드(thread): 멀티스레드(thread) 환경에서 주의 필요
사용 시기
- 게임 매니저: 게임 상태 관리
- 사운드 매니저: 사운드 재생 관리
- 씬 매니저: 씬 전환 관리
- 설정 매니저: 게임 설정 관리
베스트 프랙티스
- Awake()에서 초기화: 다른 오브젝트보다 먼저 초기화
- null 체크: 항상 null 체크 후 사용
- 중복 방지: 중복 인스턴스(instance)는 즉시 파괴
- 적절한 사용: 꼭 필요한 경우에만 사용
연습 문제
-
SoundManager 싱글톤을 만들어서 어디서든 사운드를 재생할 수 있도록 하세요.
-
GameManager 싱글톤에 점수(score) 변수(variable)를 추가하고, 다른 스크립트에서 점수를 증가시킬 수 있도록 하세요.
-
싱글톤 패턴을 사용하여 씬 전환 시에도 유지되는 설정 매니저를 만드세요.