본문 바로가기
Unity 궁둥이 쪼물쪼물

011. 싱글톤 GameManager & 승리 조건

by daco2020 2026. 4. 15.

게임 전체 상태를 관리하는 싱글톤 GameManager를 만들고,
적을 n명 잡으면 승리하는 조건을 구현한 과정을 기록한다.

 

 

 

1. 싱글톤(Singleton) 패턴이란?

"게임에 딱 하나만 존재하는 오브젝트"를 만드는 패턴.

이번 작업에서는 GameManager를 싱글톤으로 만들어 보려고 한다.

비유하자면

학교에 교장선생님은 한 명.
누구든 "교장선생님"이라고 하면 같은 사람을 가리킴.

GameManager도 마찬가지.
어떤 스크립트에서든 GameManager.Instance 라고 하면 같은 객체를 가리킴.

코드 구조

public class GameManager : MonoBehaviour
{
    public static GameManager Instance { get; private set; }

    private void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);  // 이미 있으면 새로 만든 건 파괴
            return;
        }
        Instance = this;  // 자기 자신을 Instance에 등록
    }
}
  • static: 클래스 자체에 속하는 변수. 인스턴스 없이도 접근 가능
  • Instance: 자기 자신의 참조. 어디서든 GameManager.Instance로 접근
  • Awake()에서 중복 체크: 이미 존재하면 새로 만든 건 파괴 → 항상 하나만 유지

사용 예시

// Enemy.cs에서 (적이 죽었을 때)
GameManager.Instance.OnEnemyKilled();

// PlayerHealth.cs에서 (플레이어가 죽었을 때)
GameManager.Instance.SetGameOver();

 

어느 스크립트에서든 GameManager.Instance로 바로 접근. 참조를 따로 연결할 필요 없음!

 

 

 

2. 게임 상태 관리

public enum GameState
{
    Playing,    // 게임 진행 중
    Victory,    // 승리
    GameOver    // 패배
}

상태 흐름

Playing (게임 중)
  → 적을 n명 잡음 → Victory (승리!)
  → 플레이어 HP 0 → GameOver (패배!)

상태 체크로 중복 방지

public void OnEnemyKilled()
{
    if (State != GameState.Playing) return;  // 이미 끝났으면 무시
    // ...
}

 

게임이 끝난 후에도 적이 죽을 수 있으니까, Playing 상태일 때만 카운트한다.

 

 

 

3. 승리 조건: 적 n명 잡기

[SerializeField]
private int killsToWin = 1;  // Inspector에서 변경 가능

private int _killCount;

public void OnEnemyKilled()
{
    if (State != GameState.Playing) return;

    _killCount++;
    UpdateUI();

    if (_killCount >= killsToWin)
        Victory();
}

 

killsToWin을 Inspector에서 바꿀 수 있어서 난이도 조절이 쉽다.

 

 

 

4. 승리 연출

private void Victory()
{
    State = GameState.Victory;

    if (victoryPanel != null)
        victoryPanel.SetActive(true);

    Cursor.visible = true;
    Cursor.lockState = CursorLockMode.None;
}

 

처음에는 Time.timeScale = 0으로 게임을 멈췄는데, 개인적으로 배경이 계속 동작하는 게 더 자연스러워서 제거했다.

 

 

 

5. 전체 구조: 누가 누구에게 알려주나

Enemy 죽음
  → DieAndDisappear()
  → GameManager.Instance.OnEnemyKilled()
  → killCount 증가 → 승리 조건 체크

Player 죽음
  → PlayerHealth.GameOver()
  → GameManager.Instance.SetGameOver()
  → 게임 상태를 GameOver로 변경

 

핵심: Enemy와 Player는 GameManager에 "알려만" 준다.
승리/패배 판정은 GameManager가 중앙에서 한다.

 

 

        GameManager (중앙 관리자)
          ┌─────────────────┐
          │ State: Playing  │
          │ killCount: 0    │
          │ killsToWin: 1   │
          └────────┬────────┘
                   │
        ┌──────────┼──────────┐
        ↓          ↓          ↓
        Enemy    Player      UI
OnEnemyKilled() SetGameOver() UpdateUI()

 

 

 

6. Scene 재시작 (RestartGame)

public void RestartGame()
{
    Time.timeScale = 1f;
    SceneManager.LoadScene(SceneManager.GetActiveScene().buildIndex);
}

 

현재 씬을 다시 로드하면 모든 오브젝트가 초기 상태로 돌아간다.
나중에 UI 버튼에 연결하면 "재시작" 기능이 된다.

 

 

7. Collider와 애니메이션의 불일치

적이 점프(공격) 애니메이션 중에도 총을 쏘면 맞는 현상을 발견했다.

눈에 보이는 것:     오리가 위에 있음 🐤 (애니메이션이 보여주는 위치)
실제 Collider 위치:  바닥에 그대로    📦 (Transform 기준)

 

애니메이션은 보이는 모습만 바꾸고, Collider는 Transform 위치에 있어서 발생하는 현상.
근본적으로 고치려면 Root Motion이 필요하지만 현재 작업에서는 이대로 두었다.

 

 

 

8. 이번 STEP에서 배운 핵심 정리

1. 싱글톤 = 딱 하나만 존재 + 어디서든 Instance로 접근
2. Awake()에서 중복 체크 → 이미 있으면 Destroy
3. enum으로 게임 상태 관리 (Playing, Victory, GameOver)
4. 상태 체크로 중복 이벤트 방지 (if State != Playing return)
5. 중앙 관리자 패턴: 각자 알려만 주고, 판정은 Manager가
6. Time.timeScale = 0 vs 스크립트 비활성화 = 상황에 따라 선택
7. SceneManager.LoadScene()으로 씬 재시작 가능