This commit is contained in:
SoulliesOfficial
2026-04-01 12:23:27 -04:00
parent aff7ac0e03
commit c3b1561375
933 changed files with 114333 additions and 119360 deletions

View File

@@ -0,0 +1,40 @@
using System;
namespace Continentis.MainGame.Saving
{
/// <summary>
/// 游戏存档的根节点,作为单一顶层对象写入 ES3 文件。
/// ES3 用法ES3.Save("GameSave", gameSave, "save.es3")
/// ES3.Load&lt;GameSave&gt;("GameSave", "save.es3")
/// </summary>
public class GameSave
{
/// <summary>
/// 存档格式版本号。当数据结构发生破坏性变更时递增,
/// SaveManager 读取时据此执行迁移逻辑。
/// </summary>
public int saveVersion = 1;
/// <summary>最后一次保存的时间戳UTC。</summary>
public DateTime saveTime;
/// <summary>
/// 玩家账号级别数据(解锁内容、统计数据),跨跑局持久。
/// </summary>
public PlayerSave playerData;
/// <summary>
/// 当前进行中的跑局快照。null 表示无进行中的跑局(主菜单状态)。
/// </summary>
public RunSave currentRun;
public GameSave()
{
playerData = new PlayerSave();
currentRun = null;
}
/// <summary>判断当前是否存在未完成的跑局。</summary>
public bool HasActiveRun => currentRun != null;
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a6d5fe0b6ae9a064b839dd40c4ad91d8

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 30a96afbfcef5ed46b1813d61f7825ef
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,31 @@
using System.Collections.Generic;
namespace Continentis.MainGame.Saving
{
/// <summary>
/// 玩家账号级别的存档数据。
/// 跨跑局持久,记录解锁内容和统计数据。
/// </summary>
public class PlayerSave
{
/// <summary>总跑局次数(含失败)。</summary>
public int totalRuns;
/// <summary>总胜利次数(成功通关次数)。</summary>
public int totalVictories;
/// <summary>
/// 已解锁内容的 DataID 列表。
/// 供菜单界面、职业选择、卡牌图鉴等系统查询。
/// 格式与 ModManager 资产 DataID 保持一致Type_ModName_AssetName
/// </summary>
public List<string> unlockedContentIDs;
// TODO: 后续扩展 — 成就记录、统计数据(最高伤害、最长存活回合等)
public PlayerSave()
{
unlockedContentIDs = new List<string>();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: b12d4d21c439d5c41ae0c797248037d8

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: c9283203f9ca79f4e96919a472f55d65
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,28 @@
namespace Continentis.MainGame.Saving
{
/// <summary>
/// 单张卡牌的存档快照。
/// 只记录能够重建运行时 CardInstance 所需的最小信息。
/// </summary>
public class CardSave
{
/// <summary>
/// 卡牌数据 DataID格式CardData_ModName_CardName
/// 通过 ModManager.GetData&lt;CardData&gt;(cardDataID) 还原。
/// </summary>
public string cardDataID;
/// <summary>
/// 升级层数0 = 未升级)。
/// </summary>
public int upgradeLevel;
public CardSave() { }
public CardSave(string cardDataID, int upgradeLevel = 0)
{
this.cardDataID = cardDataID;
this.upgradeLevel = upgradeLevel;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e280529f33e40e4408aeaff395007ad6

View File

@@ -0,0 +1,53 @@
using System.Collections.Generic;
namespace Continentis.MainGame.Saving
{
/// <summary>
/// 单个玩家英雄的存档快照。
/// 记录英雄在跑局中跨战斗保持的所有可变状态。
/// </summary>
public class HeroSave
{
/// <summary>
/// 英雄角色数据 DataID格式CharacterData_ModName_HeroName
/// 通过 ModManager.GetData&lt;CharacterData&gt;(characterDataID) 还原。
/// </summary>
public string characterDataID;
/// <summary>
/// 当前 HP跨战斗保持受到伤害后降低不自动回满
/// </summary>
public int currentHP;
/// <summary>
/// 当前最大 HP初始值来自 CharacterData装备或跑局事件可能修改
/// </summary>
public int maxHP;
/// <summary>
/// 当前完整牌组快照,包括初始牌、奖励牌和装备附带牌。
/// </summary>
public List<CardSave> deck;
/// <summary>
/// 当前装备 DataID 列表格式EquipmentData_ModName_EquipmentName
/// 通过 ModManager.GetData&lt;EquipmentData&gt;(id) 还原。
/// </summary>
public List<string> equipmentIDs;
public HeroSave()
{
deck = new List<CardSave>();
equipmentIDs = new List<string>();
}
public HeroSave(string characterDataID, int currentHP, int maxHP)
{
this.characterDataID = characterDataID;
this.currentHP = currentHP;
this.maxHP = maxHP;
deck = new List<CardSave>();
equipmentIDs = new List<string>();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f70f65f2802e0e942bc433d15442e5af

View File

@@ -0,0 +1,59 @@
using System.Collections.Generic;
namespace Continentis.MainGame.Saving
{
/// <summary>
/// 当次跑局的完整存档快照。
/// 记录跑局开始配置、当前进度、资源,以及所有英雄的状态。
/// </summary>
public class RunSave
{
/// <summary>
/// 当次跑局使用的配置 DataID格式RunConfig_ModName_ConfigName
/// 通过 ModManager.GetData&lt;RunConfig&gt;(runConfigID) 还原关卡序列等初始设置。
/// </summary>
/// <summary>
/// 当前关卡序列进度,即 combatNodeIDs 的当前索引。
/// </summary>
public int currentNodeIndex;
/// <summary>
/// 关卡序列快照:开局时从 RunConfig.encounterSequenceIDs 拷贝而来。
/// 格式CombatNodeData_ModName_NodeName
/// EnterCombat 直接读取此列表,跑局中无需再查询 RunConfig。
/// </summary>
public List<string> combatNodeIDs;
/// <summary>
/// 当前金币数量。
/// </summary>
public int gold;
/// <summary>
/// 跑局随机种子,用于保证重新进入游戏后奖励池与随机事件一致。
/// </summary>
public int randomSeed;
/// <summary>
/// 所有玩家英雄的状态快照列表,顺序与 RunConfig.initialHeroIDs 一致。
/// </summary>
public List<HeroSave> heroes;
// TODO: Phase 4+ — 添加 relics遗物列表和 mapNodeRecord地图选择记录
public RunSave()
{
combatNodeIDs = new List<string>();
heroes = new List<HeroSave>();
}
public RunSave(int randomSeed, int startingGold, List<string> combatNodeIDs)
{
this.randomSeed = randomSeed;
this.gold = startingGold;
this.combatNodeIDs = combatNodeIDs ?? new List<string>();
this.currentNodeIndex = 0;
heroes = new List<HeroSave>();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 57ef00a1f49761341923dcb281587be4

View File

@@ -0,0 +1,123 @@
using System;
using Continentis.MainGame.Saving;
using SLSFramework.General;
using UnityEngine;
namespace Continentis.MainGame
{
/// <summary>
/// 负责游戏存档的读写、校验和删除。
/// 跨场景持久,挂载在初始场景的 SaveManager GameObject 上。
/// 内存中缓存一份 GameSave所有操作均通过缓存进行
/// 保证 PlayerSave成就、统计不会被跑局操作意外覆盖。
/// </summary>
public class SaveManager : Singleton<SaveManager>
{
private const string SaveKey = "GameSave";
private const string SaveFilePath = "save.es3";
/// <summary>内存中的存档缓存Awake 时从文件加载,不存在则新建。</summary>
private GameSave _cache;
// ── 生命周期 ──────────────────────────────────────────────────────
protected override void Awake()
{
Initialize(dontDestroy: true);
LoadOrCreate();
}
// ── 查询 API ──────────────────────────────────────────────────────
/// <summary>返回缓存中的 PlayerSave账号级数据。</summary>
public PlayerSave PlayerData => _cache.playerData;
/// <summary>判断当前是否存在未完成的跑局。</summary>
public bool HasActiveRun() => _cache.HasActiveRun;
// ── 跑局存档 API ──────────────────────────────────────────────────
/// <summary>
/// 将 RunSave 写入缓存并持久化。
/// 由 MainGameManager 在开局和节点边界(战斗胜利后)调用。
/// </summary>
public void SaveRunSave(RunSave runSave)
{
_cache.currentRun = runSave;
Flush();
}
/// <summary>
/// 清除当前跑局(战斗失败 / 放弃跑局),保留 PlayerSave。
/// </summary>
public void DeleteRunSave()
{
_cache.currentRun = null;
Flush();
Debug.Log("[Save] 跑局存档已清除PlayerSave 保留)。");
}
/// <summary>返回缓存中的 RunSave若无进行中的跑局则返回 null。</summary>
public RunSave GetRunSave() => _cache.currentRun;
// ── 账号存档 API ──────────────────────────────────────────────────
/// <summary>
/// 更新 PlayerSave 并持久化(解锁内容、统计数据变更时调用)。
/// </summary>
public void SavePlayerData(PlayerSave playerSave)
{
_cache.playerData = playerSave;
Flush();
}
// ── 全局存档 API ──────────────────────────────────────────────────
/// <summary>
/// 清除所有存档数据,包括 PlayerSave账号重置时调用
/// </summary>
public void DeleteAllData()
{
if (ES3.FileExists(SaveFilePath))
{
ES3.DeleteFile(SaveFilePath);
Debug.Log("[Save] 所有存档已删除。");
}
_cache = new GameSave();
}
// ── 内部方法 ──────────────────────────────────────────────────────
/// <summary>从文件加载 GameSave文件不存在则新建并立即持久化。</summary>
private void LoadOrCreate()
{
if (!ES3.FileExists(SaveFilePath))
{
_cache = new GameSave();
Flush();
Debug.Log("[Save] 未找到存档文件,已新建。");
return;
}
try
{
_cache = ES3.Load<GameSave>(SaveKey, SaveFilePath, defaultValue: null) ?? new GameSave();
Debug.Log($"[Save] 存档加载成功(版本 {_cache.saveVersion})。");
}
catch (Exception e)
{
Debug.LogError($"[Save] 存档加载异常,将新建:{e.Message}");
_cache = new GameSave();
Flush();
}
}
/// <summary>将当前缓存写入文件。</summary>
private void Flush()
{
_cache.saveTime = DateTime.UtcNow;
ES3.Save<GameSave>(SaveKey, _cache, SaveFilePath);
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: ac19ca7000e542e4390e00760eda2fad