using System; using System.IO; using UnityEngine; // 이야기 상태(StoryState)의 유일한 접근 창구. // 진행도/호감도/이력의 조회·변경과 저장/로드를 담당한다. // 씬에 미리 배치하지 않아도 첫 접근 때 자동 생성된다. public class StoryManager : MonoBehaviour { public static StoryManager Instance { get; private set; } private StoryState _state = new(); // 상태가 바뀔 때마다 발행 (호감도 게이지 등 UI 갱신용) public event Action Changed; private void Awake() { if (Instance == null) { Instance = this; //만들어진 자신을 인스턴스로 설정 } else { Destroy(gameObject); //이미 인스턴스가 있으면 자신을 파괴 } } // ── 메인 진행도 ────────────────────────────────────────────── public int MainProgress { get => _state.MainProgress; set { if (_state.MainProgress == value) return; _state.MainProgress = value; Changed?.Invoke(); } } // ── 호감도 ────────────────────────────────────────────────── public int GetAffection(CharacterData character) => character != null && _state.Affection.TryGetValue(IdOf(character), out var v) ? v : 0; public void AddAffection(CharacterData character, int delta) { if (character == null || delta == 0) return; _state.Affection[IdOf(character)] = GetAffection(character) + delta; Changed?.Invoke(); } // CharacterData의 Id를 키로 사용 (비어있으면 에셋 이름) private static string IdOf(CharacterData c) => string.IsNullOrEmpty(c.Id) ? c.name : c.Id; // ── 대화 이력 ──────────────────────────────────────────────── public bool IsDialogCompleted(string groupName) => _state.CompletedDialogs.Contains(groupName); // 완료 기록. 처음 완료한 경우에만 true (진행도 보상 중복 방지용) public bool MarkDialogCompleted(string groupName) { if (string.IsNullOrEmpty(groupName) || !_state.CompletedDialogs.Add(groupName)) return false; Changed?.Invoke(); return true; } // ── 선택 이력 ──────────────────────────────────────────────── public bool HasChosen(string code) => _state.ChosenCodes.Contains(code); public void RecordChoice(string code) { if (string.IsNullOrEmpty(code) || !_state.ChosenCodes.Add(code)) return; Changed?.Invoke(); } // ── 이벤트존 이력 ──────────────────────────────────────────── public bool HasTriggeredZone(string zoneId) => _state.TriggeredZones.Contains(zoneId); public void RecordZoneTriggered(string zoneId) { if (string.IsNullOrEmpty(zoneId) || !_state.TriggeredZones.Add(zoneId)) return; Changed?.Invoke(); } // ── 저장 / 로드 ────────────────────────────────────────────── // 플레이 시작 시엔 항상 빈 상태로 시작한다 (테스트 반복이 꼬이지 않게). // 이어하기를 만들 때 타이틀 화면 등에서 Load()를 호출하면 된다. private static string SavePath => Path.Combine(Application.persistentDataPath, "story_state.json"); public void Save() => File.WriteAllText(SavePath, _state.ToJson()); public void CallLoad() => Load(); // 저장 파일이 있으면 불러온다. 성공 여부 반환. public bool Load() { if (!File.Exists(SavePath)) { Debug.LogWarning("세이브 파일이 없습니다."); return false; } try { var loaded = StoryState.FromJson(File.ReadAllText(SavePath)); if (loaded == null) { Debug.LogWarning("세이브 파일이 잘못되었습니다."); return false; } _state = loaded; Changed?.Invoke(); return true; } catch (Exception e) { Debug.LogWarning($"[StoryManager] 저장 파일 로드 실패: {e.Message}"); return false; } } // 새 게임용 초기화 (저장 파일은 다음 Save 때 덮어써짐) public void ResetAll() { _state.Clear(); Changed?.Invoke(); } // Enter Play Mode에서 도메인 리로드를 꺼도 이전 인스턴스 참조가 안 남게 [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)] private static void ResetStatics() => Instance = null; }