Files
MVP1/Assets/Scripts/Other/SaveManager.cs
T

246 lines
9.4 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using UnityEngine;
using System.Collections.Generic;
using System.IO;
using UnityEngine.SceneManagement;
// Сохраняет и загружает: все ресурсы, уровень Ратуши, список всех построенных зданий
// (какой тип, где стоит, какого уровня).
//
// Файл сохранения лежит в Application.persistentDataPath -- это специальная папка,
// которая переживает переустановку/обновление игры на телефоне (в отличие от Assets).
[System.Serializable]
public class ResourceSaveEntry
{
public ResourceType type;
public float amount;
}
[System.Serializable]
public class PlacedBuildingSaveEntry
{
public string buildingName; // совпадает с BuildingData.buildingName
public float posX;
public float posY;
public int level;
}
[System.Serializable]
public class SaveData
{
public List<ResourceSaveEntry> resources = new List<ResourceSaveEntry>();
public int townHallLevel = 1;
public List<PlacedBuildingSaveEntry> placedBuildings = new List<PlacedBuildingSaveEntry>();
}
public class SaveManager : MonoBehaviour
{
public static SaveManager Instance { get; private set; }
[Header("Автосохранение")]
[Tooltip("Как часто автоматически сохранять прогресс, в секундах")]
public float autoSaveInterval = 30f;
private string SavePath => Path.Combine(Application.persistentDataPath, "save.json");
void Awake()
{
if (Instance != null && Instance != this)
{
Destroy(gameObject);
return;
}
Instance = this;
DontDestroyOnLoad(gameObject);
SceneManager.sceneLoaded += OnSceneLoaded;
}
void OnDestroy()
{
if (Instance == this) SceneManager.sceneLoaded -= OnSceneLoaded;
}
// [объединение с картой Саши] Первый вход в город загружает GameBootstrap.
// При возврате в город со сцены WorldMap GameBootstrap уничтожается вместе с дубликатом SaveManager,
// поэтому здания восстанавливаем здесь (из сохранения, сделанного прямо перед уходом из города).
bool cityLoadedOnce;
void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
if (BuildManager.Instance == null) return; // это не город
// ресурсы НЕ перечитываем: они живут в памяти между сценами (добыча с карты мира не должна пропасть)
if (cityLoadedOnce && HasSaveFile()) LoadGame(false);
cityLoadedOnce = true;
}
void Start()
{
InvokeRepeating(nameof(SaveGame), autoSaveInterval, autoSaveInterval);
}
// Мобильные ОС часто "убивают" приложение без вызова OnApplicationQuit,
// поэтому OnApplicationPause -- самый надёжный момент для сохранения на телефоне
// (вызывается, когда игрок сворачивает игру или переключается на другое приложение).
void OnApplicationPause(bool pauseStatus)
{
if (pauseStatus)
{
SaveGame();
}
}
void OnApplicationQuit()
{
SaveGame();
}
// ---------------- СОХРАНЕНИЕ ----------------
public void SaveGame()
{
// На карте мира города нет -- сохранение затёрло бы все здания. Город сохраняется перед уходом.
if (BuildManager.Instance == null) return;
cityLoadedOnce = true;
SaveData data = new SaveData();
// Ресурсы
if (ResourceManager.Instance != null)
{
foreach (var kvp in ResourceManager.Instance.GetAllResources())
{
data.resources.Add(new ResourceSaveEntry { type = kvp.Key, amount = kvp.Value });
}
}
// Ратуша
if (TownHall.Instance != null)
{
data.townHallLevel = TownHall.Instance.currentLevel;
}
// Все построенные здания на сцене (найдёт каждый объект с маркером PlacedBuildingId)
PlacedBuildingId[] allPlaced = Object.FindObjectsByType<PlacedBuildingId>(FindObjectsSortMode.None);
foreach (var placed in allPlaced)
{
int level = 1;
ResourceCollector collector = placed.GetComponent<ResourceCollector>();
if (collector != null) level = collector.level;
data.placedBuildings.Add(new PlacedBuildingSaveEntry
{
buildingName = placed.buildingName,
posX = placed.transform.position.x,
posY = placed.transform.position.y,
level = level
});
}
string json = JsonUtility.ToJson(data, true);
File.WriteAllText(SavePath, json);
Debug.Log($"[СОХРАНЕНИЕ] Игра сохранена: {SavePath}");
}
// ---------------- ЗАГРУЗКА ----------------
public bool HasSaveFile()
{
return File.Exists(SavePath);
}
public void LoadGame() { LoadGame(true); }
public void LoadGame(bool includeResources)
{
BarracksBootstrap.Ensure(); // казармы должны быть в списке стройки до восстановления зданий
if (!HasSaveFile())
{
Debug.Log("[ЗАГРУЗКА] Файл сохранения не найден, начинаем новую игру.");
return;
}
string json = File.ReadAllText(SavePath);
SaveData data = JsonUtility.FromJson<SaveData>(json);
// Ресурсы
if (includeResources && ResourceManager.Instance != null)
{
foreach (var entry in data.resources)
{
ResourceManager.Instance.SetResource(entry.type, entry.amount);
}
}
// Ратуша
if (TownHall.Instance != null)
{
TownHall.Instance.currentLevel = data.townHallLevel;
}
// Сначала удаляем все здания, которые уже могли быть заспавнены на сцене
// (например, если LoadGame вызывается повторно, не хотим задваивать постройки)
PlacedBuildingId[] existing = Object.FindObjectsByType<PlacedBuildingId>(FindObjectsSortMode.None);
foreach (var e in existing)
{
Destroy(e.gameObject);
}
// Восстанавливаем здания из сохранения
foreach (var entry in data.placedBuildings)
{
BuildingData matchingData = FindBuildingDataByName(entry.buildingName);
if (matchingData == null)
{
Debug.LogWarning($"[ЗАГРУЗКА] Не найден BuildingData для '{entry.buildingName}', здание пропущено.");
continue;
}
Vector3 pos = new Vector3(entry.posX, entry.posY, 0f);
GameObject built = Instantiate(matchingData.prefab, pos, Quaternion.identity);
if (!built.activeSelf) built.SetActive(true); // шаблон казарм хранится выключенным
PlacedBuildingId idMarker = built.AddComponent<PlacedBuildingId>();
idMarker.buildingName = entry.buildingName;
ResourceCollector collector = built.GetComponent<ResourceCollector>();
if (collector != null) collector.level = entry.level;
// Помечаем соответствующий слот как занятый, чтобы на него нельзя было
// случайно поставить ещё одно здание поверх после загрузки
if (BuildManager.Instance != null)
{
BuildSlot slot = BuildManager.Instance.FindSlotAtPosition(pos);
if (slot != null)
{
slot.Occupy(built);
BuildingSlotLink link = built.AddComponent<BuildingSlotLink>();
link.linkedSlot = slot;
}
}
}
Debug.Log($"[ЗАГРУЗКА] Игра загружена. Построек восстановлено: {data.placedBuildings.Count}");
}
BuildingData FindBuildingDataByName(string buildingName)
{
if (BuildManager.Instance == null) return null;
foreach (var b in BuildManager.Instance.allBuildings)
{
if (b.buildingName == buildingName) return b;
}
return null;
}
// Полностью стирает сохранение (например, для кнопки "Начать заново" в настройках)
public void DeleteSave()
{
if (File.Exists(SavePath))
{
File.Delete(SavePath);
Debug.Log("[СОХРАНЕНИЕ] Файл сохранения удалён.");
}
}
}