using System; using System.Collections.Generic; using System.IO; using UnityEngine; /// /// Manages player profiles backed by Easy Save files. /// Stores a small index file at: persistentDataPath/profiles/_index.es3 /// Each profile gets its own folder: profiles/{profileId}/slot_{n}.es3 /// public class ES3ProfileManager : MonoBehaviour { public static ES3ProfileManager Instance { get; private set; } [Serializable] public class ProfileInfo { public string id; // stable slug (folder name) public string displayName; // shown in UI public string createdAt; // yyyy-MM-dd HH:mm:ss public string lastPlayedAt; // yyyy-MM-dd HH:mm:ss } private const string INDEX_FILE = "profiles/_index.es3"; private const string K_PROFILES = "profiles"; private const string K_ACTIVE = "active"; [SerializeField] private List profiles = new(); [SerializeField] private string activeProfileId = null; public IReadOnlyList Profiles => profiles; public string ActiveProfileId => activeProfileId; private void Awake() { if (Instance != null && Instance != this) { Destroy(gameObject); return; } Instance = this; DontDestroyOnLoad(gameObject); LoadIndex(); if (string.IsNullOrEmpty(activeProfileId)) { // ensure at least one profile exists var p = CreateProfile("Player"); SetActiveProfile(p.id); } } // ---------- Public API ---------- public ProfileInfo CreateProfile(string displayName) { var id = MakeSlug(displayName); if (profiles.Exists(p => p.id == id)) { // ensure uniqueness by appending a number int n = 2; var baseId = id; while (profiles.Exists(p => p.id == id)) id = $"{baseId}{n++}"; } var now = Now(); var info = new ProfileInfo { id = id, displayName = displayName, createdAt = now, lastPlayedAt = now }; profiles.Add(info); Directory.CreateDirectory(GetProfileDirAbsolute(id)); SaveIndex(); return info; } public void DeleteProfile(string id) { var idx = profiles.FindIndex(p => p.id == id); if (idx < 0) return; // delete files on disk var dir = GetProfileDirAbsolute(id); if (Directory.Exists(dir)) Directory.Delete(dir, true); profiles.RemoveAt(idx); if (activeProfileId == id) activeProfileId = profiles.Count > 0 ? profiles[0].id : null; SaveIndex(); } public void RenameProfile(string id, string newDisplayName) { var p = profiles.Find(x => x.id == id); if (p == null) return; p.displayName = newDisplayName; SaveIndex(); } public void SetActiveProfile(string id) { if (!profiles.Exists(p => p.id == id)) return; activeProfileId = id; SaveIndex(); } public ProfileInfo GetActiveProfile() => profiles.Find(p => p.id == activeProfileId); public void TouchActiveProfileLastPlayed() { var p = GetActiveProfile(); if (p == null) return; p.lastPlayedAt = Now(); SaveIndex(); } // File/Path helpers public string GetProfileDirRelative(string id) => $"profiles/{id}"; public string GetProfileDirAbsolute(string id) => Path.Combine(Application.persistentDataPath, "profiles", id); public string GetSlotPathRelative(int slot) => $"{GetProfileDirRelative(activeProfileId)}/slot_{slot}.es3"; public string GetIndexPathAbsolute() => Path.Combine(Application.persistentDataPath, INDEX_FILE); // ---------- Persistence of the index ---------- private void LoadIndex() { var abs = GetIndexPathAbsolute(); if (!File.Exists(abs)) { profiles = new List(); activeProfileId = null; return; } var rel = INDEX_FILE; // ES3 expects relative to persistentDataPath profiles = ES3.KeyExists(K_PROFILES, rel) ? ES3.Load>(K_PROFILES, rel) : new List(); activeProfileId = ES3.KeyExists(K_ACTIVE, rel) ? ES3.Load(K_ACTIVE, rel) : null; } private void SaveIndex() { var rel = INDEX_FILE; Directory.CreateDirectory(Path.Combine(Application.persistentDataPath, "profiles")); ES3.Save(K_PROFILES, profiles, rel); ES3.Save(K_ACTIVE, activeProfileId, rel); } // ---------- Utils ---------- private static string MakeSlug(string s) { if (string.IsNullOrWhiteSpace(s)) s = "Player"; s = s.Trim(); foreach (var c in Path.GetInvalidFileNameChars()) s = s.Replace(c, '_'); return s.Replace(' ', '_').ToLowerInvariant(); } private static string Now() => DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); }