Files
2026-07-09 21:33:33 +02:00

144 lines
5.1 KiB
C#

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using JetBrains.Annotations;
using Sirenix.OdinInspector;
using UnityEngine;
namespace VOID.Generic.SaveLoad
{
/// <summary>
/// Manages profiles (save files) and caches currently active one.
/// </summary>
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<PlayerProfile> 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<PlayerProfile>(File.ReadAllText(file));
}
/// <summary>
/// Parse all profiles from files and returns them.
/// There is no caching, it will scan files everytime it is called.
/// </summary>
[Button]
public List<PlayerProfile> GetProfiles()
{
if (!Directory.Exists(directoryPath)) Directory.CreateDirectory(directoryPath);
return Directory.GetFiles(directoryPath, fileNameMask)
.Select(File.ReadAllText)
.Select(JsonUtility.FromJson<PlayerProfile>)
.ToList();
}
/// <summary>
/// Creates new profile with given name and saves it as file.
/// </summary>
[Button]
public PlayerProfile CreateProfile(string name)
{
if (!Directory.Exists(directoryPath)) Directory.CreateDirectory(directoryPath);
var newProfile = new PlayerProfile(name);
SaveProfile(newProfile);
return newProfile;
}
/// <summary>
/// Removes given profile from files and if its currently active unselects it.
/// </summary>
/// <param name="playerProfile"></param>
[Button]
public void DeleteProfile([NotNull] PlayerProfile playerProfile)
{
if (playerProfile == activeProfile) SelectProfile(null);
if (!Directory.Exists(directoryPath)) Directory.CreateDirectory(directoryPath);
File.Delete(GetFullPathFor(playerProfile));
}
/// <summary>
/// Saves currently active profile as file.
/// </summary>
public void SaveProfile()
{
SaveProfile(activeProfile);
}
/// <summary>
/// Saves given profile as file.
/// </summary>
[Button]
private void SaveProfile([NotNull] PlayerProfile playerProfile)
{
if (!Directory.Exists(directoryPath)) Directory.CreateDirectory(directoryPath);
File.WriteAllText(GetFullPathFor(playerProfile), JsonUtility.ToJson(playerProfile, true));
}
/// <summary>
/// Set active given profile. Will be selected automatically next time.
/// </summary>
[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);
}
}
}