내일배움캠프 - TIL/내일배움캠프 - TIL

내일배움캠프 28일차 - 프로젝트 초기 설정(매니저)

rudals4469 2025. 5. 27. 18:20

🎮 매니저(Manager)란?

**매니저(Manager)**는 게임 개발에서 특정 시스템이나 기능을 조율하고 관리하는 역할을 수행하는 중앙 제어 클래스입니다.


🧭 매니저의 주요 역할

역할설명
중앙 조율자 여러 개별 객체나 시스템 간의 상호작용 조율
📦 데이터 관리 시스템의 상태를 추적하고 전달
🛠️ 생성/소멸 관리 객체의 생성, 소멸, 업데이트 등을 제어
 

📌 매니저의 역할과 책임

✅ 1. 관리의 역할만 수행

  • 의존성 관리
    예: UIManager가 모든 UI의 켜짐/꺼짐 상태를 조율
  • 리소스 관리
    필요한 리소스를 로드/언로드하거나 캐싱
  • 이벤트 중계
    시스템 간 이벤트 전달 및 수신

❌ 2. 하지 말아야 할 것들

하지 말아야 할 일이유

 

🚫 직접 기능 구현 비즈니스 로직은 해당 객체가 처리해야 함
🚫 세부 데이터 처리 세부 동작 결정은 매니저의 역할 아님
🚫 UI/객체 내부 동작 제어 매니저는 제어자가 아니라 관리자
 

💡 좋은 매니저 설계 원칙

🧱 1. 단일 책임 원칙 (SRP)

  • 매니저는 단 하나의 관리 책임만 가져야 합니다.
    예: GameManager는 게임 상태만 관리,
    UI는 UIManager, 사운드는 AudioManager가 별도 담당.

🧩 2. 중앙 집중화

  • 연관된 객체는 하나의 매니저에서 조율
    예:
    • EnemyManager: 적 생성/삭제 관리
    • AudioManager: 사운드 재생/정지 관리

🔄 3. 확장 가능성

  • 매니저는 유연하게 확장 가능해야 합니다.
    예:
    • 객체들을 Dictionary<string, Enemy> 형태로 관리
    • 새로운 객체도 런타임에 추가 가능

🚫 잘못된 매니저 예시

public class EnemyManager : MonoBehaviour
{
    public void MoveEnemies()
    {
        // 이동 로직
    }

    public void AttackEnemies()
    {
        // 공격 로직
    }
}
  • 문제점:
    • 관리 책임을 벗어남
    • 적 클래스의 자율성 침해

✅ 올바른 매니저 예시

✔️ 생성/소멸/상태만 담당

public class EnemyManager : MonoBehaviour
{
    private List<Enemy> enemies = new List<Enemy>();

    public void SpawnEnemy(Vector3 position)
    {
        var enemyPrefab = Resources.Load<GameObject>("EnemyPrefab");
        var enemyInstance = Instantiate(enemyPrefab, position, Quaternion.identity);
        enemies.Add(enemyInstance.GetComponent<Enemy>());
    }

    public void DestroyAllEnemies()
    {
        foreach (var enemy in enemies)
        {
            Destroy(enemy.gameObject);
        }
        enemies.Clear();
    }
}
 
  • 개별 적의 동작 (이동, 공격)은 Enemy 클래스 내부에서 처리.
public class Enemy : MonoBehaviour
{
    public void Move()
    {
        // 적 이동 로직
    }

    public void Attack()
    {
        // 적 공격 로직
    }
}

✅ 요약

항목매니저는 해야 함매니저는 하면 안 됨
관리
조율
로직
세부 동작

🧩 싱글턴 패턴(Singleton Pattern) 이란?

싱글턴 패턴은 클래스 인스턴스를 하나만 생성하고, 전역적으로 공유하는 디자인 패턴입니다.
주로 전역 상태 관리자원 관리에 사용됩니다.


✅ 싱글턴 하나만 만들 때의 장점

장점설명
🌐 전역적 접근 언제 어디서든 접근 가능
🧠 상태 관리 용이 하나의 인스턴스를 참조하므로 관리가 직관적
💾 자원 절약 메모리, 리소스를 한 인스턴스로 공유
 

✅ 싱글턴 여러 개를 만들 때의 장점

장점설명
🧹 책임 분리 각 매니저가 자신의 책임만 수행
⚙️ 유연성 시스템마다 맞춤 로직을 구현 가능
🚀 확장성 새로운 매니저를 손쉽게 추가 가능
 

⚠️ 싱글턴 하나만 만들 때의 문제점

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

    private void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
            return;
        }
        Instance = this;
        DontDestroyOnLoad(gameObject);
    }

    public void StartGame() { }
    public void EndGame() { }
    public void UpdateUI() { }
    public void ManageAudio() { }
    // 너무 많은 책임을 한 클래스가 처리함
}

 

문제점설명
🏋️‍♂️ 과도한 책임 하나의 매니저가 너무 많은 것을 처리
🔗 결합도 증가 여러 시스템이 하나의 객체에 의존
단일 장애 지점 하나의 인스턴스가 실패하면 전체에 영향
 

❗ 전역적으로 하나의 매니저를 관리할 때의 문제

📌 문제점:
GameManager가 너무 많은 기능(UI, 오디오, 게임 상태 등)을 처리하게 되어 역할 분리가 불명확하고 확장성 저하.

(내용추가)


⚠️ 싱글턴 여러 개를 만들 때의 문제점

문제점설명
초기화 순서 문제 의존 관계가 꼬일 수 있음
🌀 관리 복잡성 증가 책임이 겹치거나 협업이 어려워질 수 있음
public class GameManager : MonoBehaviour
{
    public static GameManager Instance { get; private set; }

    private void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
            return;
        }
        Instance = this;
    }

    public void StartGame() { }
}

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

    private void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
            return;
        }
        Instance = this;
    }

    public void UpdateUI() { }
}

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

    private void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
            return;
        }
        Instance = this;
    }

    public void PlaySound() { }
}
 
 

📌 문제점:
여러 매니저가 싱글턴으로 분리되면서 초기화 순서 문제, 상호작용 조율의 복잡성 발생


🔍 언제 어떤 방식이 적합한가?

상황싱글턴 하나싱글턴 여러 개
시스템 단순 ✅ 적합 ❌ 불필요
리소스 절약 필요 ✅ 적합 ❌ 오버킬
복잡한 시스템 구성 ❌ 비효율 ✅ 적합
유지보수, 확장 ❌ 어려움 ✅ 유리
디버깅, 최적화 ❌ 번거로움 ✅ 개별 처리 용이
 

🧠 결론: 어떤 구조가 더 적합한가?

  • 작고 단순한 프로젝트
    → 하나의 싱글턴으로도 충분하며 효율적.
  • 복잡하고 구조적인 프로젝트
    여러 개의 싱글턴으로 각 시스템을 분리해
    유지보수성확장성을 높이는 것이 적절.

🧱 GameManager와 하위 매니저의 역할 분리

🔹 1. 개념

  • GameManager는 중앙 조율자.
  • 실제 기능은 하위 매니저에게 위임.

🎯 이 구조의 이점:

  • 책임 분리: 각 매니저는 자신의 역할에 집중
  • 중앙 관리: 시스템 흐름은 GameManager가 조율

🔹 2. GameManager의 역할

  • 시스템 초기화설정 관리
  • 하위 매니저의 상태 확인 / 명령 전달
  • 게임의 전반적 상태 관리
    (예: 시작, 일시정지, 종료 등)

🔹 3. 구조 설계

 

  • GameManager는 다른 매니저들을 보유하고 조율
  • 하위 매니저는 독립적인 기능 담당

🔹 4. 코드 구현

✅ 1) GameManager

GameManager는 하위 매니저들을 포함하고 전체 흐름을 관리합니다.

using UnityEngine;

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

    public UIManager UIManager { get; private set; }
    public AudioManager AudioManager { get; private set; }
    public EnemyManager EnemyManager { get; private set; }

    private void Awake()
    {
        if (Instance != null && Instance != this)
        {
            Destroy(gameObject);
            return;
        }
        Instance = this;
        DontDestroyOnLoad(gameObject);

        InitializeManagers();
    }

    /// <summary>
    /// 하위 매니저 초기화
    /// </summary>
    private void InitializeManagers()
    {
        UIManager = new UIManager();
        AudioManager = new AudioManager();
        EnemyManager = new EnemyManager();

        UIManager.Initialize();
        AudioManager.Initialize();
        EnemyManager.Initialize();
    }

    /// <summary>
    /// 게임 시작
    /// </summary>
    public void StartGame()
    {
        Debug.Log("Game Started");
        AudioManager.PlayBackgroundMusic();
        UIManager.ShowMainMenu();
    }

    /// <summary>
    /// 게임 종료
    /// </summary>
    public void EndGame()
    {
        Debug.Log("Game Over");
        UIManager.ShowGameOverScreen();
    }
}

✅ 2) 하위 매니저

각 매니저는 자기 역할에만 집중하고 독립적으로 초기화됩니다.

🎛 UIManager

public class UIManager
{
    public void Initialize()
    {
        Debug.Log("UIManager Initialized");
    }

    public void ShowMainMenu()
    {
        Debug.Log("Main Menu Shown");
    }

    public void ShowGameOverScreen()
    {
        Debug.Log("Game Over Screen Shown");
    }
}

🔊 AudioManager

public class AudioManager
{
    public void Initialize()
    {
        Debug.Log("AudioManager Initialized");
    }

    public void PlayBackgroundMusic()
    {
        Debug.Log("Background Music Playing");
    }
}

👾 EnemyManager

public class EnemyManager
{
    public void Initialize()
    {
        Debug.Log("EnemyManager Initialized");
    }

    public void SpawnEnemy()
    {
        Debug.Log("Enemy Spawned");
    }
}