246 lines
9.4 KiB
C#
246 lines
9.4 KiB
C#
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("[СОХРАНЕНИЕ] Файл сохранения удалён.");
|
||
}
|
||
}
|
||
} |