아키텍처·패턴·에셋 관리·세이브

유니티 아키텍처·패턴·에셋 관리·세이브: 구조적 게임 설계의 핵심

목표: 본 장에서는 게임 아키텍처 설계, 디자인 패턴 적용, 에셋 관리(Addressables), 세이브/로드 시스템 구축을 통해 유니티 프로젝트를 유지보수 가능한 구조로 성장시키는 방법을 학습합니다. 즉, ‘작동하는 코드’에서 ‘확장 가능한 시스템’으로 발전하는 단계입니다.

1. 게임 아키텍처: 구조적 사고의 시작

대부분의 유니티 초보자는 ‘씬과 스크립트의 결합’으로 게임을 구성하지만, 프로젝트가 커질수록 명확한 아키텍처 없이는 유지보수가 거의 불가능해집니다. 유니티에서는 컴포넌트 기반 아키텍처(Component-Based Architecture)가 기본 구조이며, 이를 중심으로 모듈화데이터 분리를 진행해야 합니다.

1.1 기본 설계 철학

  • SRP (Single Responsibility Principle): 한 클래스는 하나의 책임만 가진다.
  • 의존성 주입(Dependency Injection): 직접 참조를 최소화하고, 상호 결합도를 낮춘다.
  • 데이터 주도 설계(Data-Driven Design): 로직과 데이터를 분리하여 유연성을 확보한다.

1.2 모듈식 구조 예시

Game/ ├─ Managers/ │ ├─ GameManager.cs │ ├─ AudioManager.cs │ └─ SaveManager.cs ├─ Systems/ │ ├─ InputSystem.cs │ ├─ InventorySystem.cs │ └─ DialogueSystem.cs ├─ Data/ │ ├─ PlayerData.asset │ └─ ItemDatabase.asset └─ UI/ ├─ HUD/ └─ Menus/ 
💡 Tip: GameManager는 “모든 것을 관리하는 클래스”가 아닙니다. 각 기능(오디오, 인벤토리, 세이브)은 독립적인 매니저로 구성하고, GameManager는 그들의 생명주기를 조정만 담당해야 합니다.

2. 유니티에서 자주 사용하는 디자인 패턴

패턴은 복잡한 시스템을 단순화하기 위한 설계의 언어입니다. 유니티는 “객체 간의 관계”가 빈번하므로, 아래 패턴들이 특히 자주 사용됩니다.

2.1 싱글톤 (Singleton)

전역에서 접근 가능한 인스턴스를 유지합니다. 다만, 남용은 유지보수를 어렵게 만듭니다.

public class AudioManager : MonoBehaviour { public static AudioManager Instance { get; private set; } void Awake() { if (Instance != null && Instance != this) Destroy(gameObject); else { Instance = this; DontDestroyOnLoad(gameObject); } } }

싱글톤은 매니저 객체에만 제한적으로 사용하는 것이 좋습니다. 예를 들어 AudioManager, InputManager, SceneLoader 등.

2.2 옵저버 (Observer)

객체 간 이벤트 기반 통신에 적합합니다. 예를 들어, 플레이어의 체력이 감소하면 UI가 자동으로 업데이트되도록 구성합니다.

public class PlayerHealth : MonoBehaviour { public event Action OnHealthChanged; private int health = 100;
public void TakeDamage(int amount) {
    health -= amount;
    OnHealthChanged?.Invoke(health);
}
}

2.3 상태(State) 패턴

게임의 행동 모드를 명확히 구분하는 구조입니다. 예: Idle → Move → Attack → Die

public abstract class PlayerState { public abstract void Enter(); public abstract void Update(); public abstract void Exit(); }

public class PlayerMoveState : PlayerState {
public override void Enter() => Debug.Log("Move 시작");
public override void Update() => Debug.Log("이동 중...");
public override void Exit() => Debug.Log("Move 종료");
}

이 구조를 통해 if문 기반 제어 대신 확장 가능한 상태 머신을 구축할 수 있습니다.

2.4 커맨드(Command) 패턴

플레이어 입력을 명령 객체로 저장하여, 실행/취소(Undo) 기능을 구현할 수 있습니다.

⚙️ 요약:
  • 싱글톤 — 관리.
  • 옵저버 — 통신.
  • 상태 패턴 — 행동 제어.
  • 커맨드 패턴 — 입력과 행동의 분리.

3. 에셋 관리: Addressables 시스템

유니티의 Addressables은 대규모 프로젝트에서 리소스를 효율적으로 로드, 관리, 배포하기 위한 시스템입니다. 이는 기존 Resources.Load()의 비효율성을 개선한 현대적 솔루션입니다.

3.1 핵심 개념

  • Address — 각 에셋에 부여되는 문자열 키.
  • Group — 에셋 묶음 단위. 빌드와 로드 단위로 동작.
  • Catalog — 그룹과 Address의 메타데이터를 담은 인덱스 파일.

3.2 기본 사용 예시

using UnityEngine; using UnityEngine.AddressableAssets;

public class AssetLoader : MonoBehaviour {
public string assetKey;
void Start() {
    Addressables.LoadAssetAsync(assetKey).Completed += handle => {
        Instantiate(handle.Result);
    };
}
}

이 구조는 로컬뿐 아니라 클라우드에서도 동일하게 작동하므로, 패치 시스템 구현에도 유용합니다.

💡 Tip: Addressables은 리소스 로드뿐 아니라 메모리 관리에도 중요합니다. Addressables.Release()를 호출하여 명시적으로 메모리를 해제하세요.

4. 세이브 시스템 (Save/Load System)

세이브 시스템은 플레이어의 진행 상태를 파일로 저장하고 불러오는 구조입니다. 유니티에서는 직렬화(Serialization)를 활용하여 데이터 객체를 JSON 또는 바이너리 형태로 저장합니다.

4.1 데이터 구조 설계

[System.Serializable] public class PlayerData { public int level; public float health; public Vector3 position; }

4.2 세이브/로드 구현

using System.IO; using UnityEngine;

public class SaveManager : MonoBehaviour {
private string path => Application.persistentDataPath + "/save.json";
public void Save(PlayerData data) {
    string json = JsonUtility.ToJson(data, true);
    File.WriteAllText(path, json);
    Debug.Log("저장 완료: " + path);
}

public PlayerData Load() {
    if (!File.Exists(path)) return null;
    string json = File.ReadAllText(path);
    return JsonUtility.FromJson(json);
}
}

이 방식은 간단하지만 강력하며, JSON 형식이기 때문에 디버깅이 용이합니다.

4.3 실무용 확장

  • 🔐 암호화: AES 또는 XOR 기반 간단 암호화 적용.
  • 📦 버전 관리: 세이브 파일에 버전 필드 추가 후 하위 호환 처리.
  • ☁️ 클라우드 연동: Firebase, Unity Cloud Save API 활용.
⚙️ 예시: 세이브 파일 버전 예시
{ "version": 2, "player": { "level": 5, "health": 73.5, "position": { "x": 10, "y": 0, "z": -5 } } }

정리: 구조는 ‘속도’가 아니라 ‘지속성’을 만든다

좋은 아키텍처는 실행 속도를 높이기보다, 프로젝트의 생명 주기를 연장합니다. 패턴은 문제를 단순화하고, Addressables은 자원을 관리하며, 세이브 시스템은 경험을 지속시킵니다.

결국, 좋은 게임은 기술이 아니라 ‘구조적 일관성’에서 태어납니다.

다음 학습 추천:

Comments