Files
Dino_Love_Simulation/Assets/02_Scripts/Managers/StoryManager.cs
2026-07-08 18:15:44 +09:00

135 lines
5.1 KiB
C#

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;
}