using System; using System.Collections.Generic; using System.IO; using System.Linq; using JetBrains.Annotations; using Sirenix.OdinInspector; using UnityEngine; namespace VOID.Generic.SaveLoad { /// /// Manages profiles (save files) and caches currently active one. /// public class PlayerProfileManager : MonoBehaviour { public static PlayerProfileManager current { get; private set; } // Paths private static string directoryPath => Path.Combine(Application.persistentDataPath, "profiles"); private static string fileNameTemplate => "{0}_{1}.profile"; private static string fileNameMask => "*.profile"; private static string fileNameWithDefaultProfile => "default"; // Cache public PlayerProfile activeProfile { get; private set; } // Events public event Action OnProfileChange; private void Awake() { if (current) { Debug.LogWarning("Instance of " + nameof(PlayerProfileManager) + " already exists!"); Destroy(this); return; } // Only one instance for whole game, this should manage player profiles // DontDestroyOnLoad works only on root game object so we need to unparent it current = this; current.transform.parent = null; DontDestroyOnLoad(this); } public PlayerProfile GetDefaultProfile() { var defaultPath = Path.Combine(directoryPath, fileNameWithDefaultProfile); // Default file not found if (!File.Exists(defaultPath)) return null; var guid = File.ReadAllText(defaultPath); // Profile not found, by guid from default file var file = Directory.GetFiles(directoryPath, string.Format(fileNameTemplate, "*", guid)).FirstOrDefault(); if (file == null) return null; return JsonUtility.FromJson(File.ReadAllText(file)); } /// /// Parse all profiles from files and returns them. /// There is no caching, it will scan files everytime it is called. /// [Button] public List GetProfiles() { if (!Directory.Exists(directoryPath)) Directory.CreateDirectory(directoryPath); return Directory.GetFiles(directoryPath, fileNameMask) .Select(File.ReadAllText) .Select(JsonUtility.FromJson) .ToList(); } /// /// Creates new profile with given name and saves it as file. /// [Button] public PlayerProfile CreateProfile(string name) { if (!Directory.Exists(directoryPath)) Directory.CreateDirectory(directoryPath); var newProfile = new PlayerProfile(name); SaveProfile(newProfile); return newProfile; } /// /// Removes given profile from files and if its currently active unselects it. /// /// [Button] public void DeleteProfile([NotNull] PlayerProfile playerProfile) { if (playerProfile == activeProfile) SelectProfile(null); if (!Directory.Exists(directoryPath)) Directory.CreateDirectory(directoryPath); File.Delete(GetFullPathFor(playerProfile)); } /// /// Saves currently active profile as file. /// public void SaveProfile() { SaveProfile(activeProfile); } /// /// Saves given profile as file. /// [Button] private void SaveProfile([NotNull] PlayerProfile playerProfile) { if (!Directory.Exists(directoryPath)) Directory.CreateDirectory(directoryPath); File.WriteAllText(GetFullPathFor(playerProfile), JsonUtility.ToJson(playerProfile, true)); } /// /// Set active given profile. Will be selected automatically next time. /// [Button] public void SelectProfile([CanBeNull] PlayerProfile playerProfile) { activeProfile = playerProfile; OnProfileChange?.Invoke(activeProfile); // No profile found - create it for user activeProfile ??= CreateProfile("New Player Profile"); // Our selected profile will be default one when starting game again File.WriteAllText(Path.Combine(directoryPath, fileNameWithDefaultProfile), activeProfile.guid); } private string GetFullPathFor([NotNull] PlayerProfile playerProfile) { var fileName = string.Format(fileNameTemplate, playerProfile.name, playerProfile.guid); return Path.Combine(directoryPath, fileName); } } }