8.30
This commit is contained in:
@@ -10,9 +10,12 @@ public partial class GameEntry : MonoBehaviour
|
||||
|
||||
public static LubanComponent Luban { get; private set; }
|
||||
|
||||
public static SaveComponent Save { get; private set; }
|
||||
|
||||
private static void InitCustomComponents()
|
||||
{
|
||||
BuiltinData = UnityGameFramework.Runtime.GameEntry.GetComponent<BuiltinDataComponent>();
|
||||
Luban = UnityGameFramework.Runtime.GameEntry.GetComponent<LubanComponent>();
|
||||
Save = UnityGameFramework.Runtime.GameEntry.GetComponent<SaveComponent>();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using Cysharp.Threading.Tasks;
|
||||
using GameFramework.Resource;
|
||||
using UnityGameFramework.Runtime;
|
||||
using ResourceApplySuccessEventArgs = UnityGameFramework.Runtime.ResourceApplySuccessEventArgs;
|
||||
using ResourceUpdateAllCompleteEventArgs = UnityGameFramework.Runtime.ResourceUpdateAllCompleteEventArgs;
|
||||
@@ -58,5 +61,42 @@ namespace SepCore.AsyncTask
|
||||
timeout
|
||||
);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 异步加载资源
|
||||
/// </summary>
|
||||
/// <typeparam name="T">资源类型</typeparam>
|
||||
/// <param name="resourceComponent">资源组件</param>
|
||||
/// <param name="assetName">资源名称</param>
|
||||
/// <param name="priority">加载优先级</param>
|
||||
/// <param name="userData">用户自定义数据</param>
|
||||
/// <param name="progress">加载进度</param>
|
||||
/// <param name="timeout">超时时间(秒),0表示不超时</param>
|
||||
/// <returns>加载的资源</returns>
|
||||
public static UniTask<T> LoadAssetAsync<T>(this ResourceComponent resourceComponent,
|
||||
string assetName,
|
||||
int priority = 0,
|
||||
object userData = null,
|
||||
IProgress<float> progress = null,
|
||||
float timeout = 0f) where T : class
|
||||
{
|
||||
var tcs = new UniTaskCompletionSource<T>();
|
||||
|
||||
var callbacks = new LoadAssetCallbacks(
|
||||
(name, asset, duration, ud) => tcs.TrySetResult((T)asset),
|
||||
(name, status, errorMessage, ud) => tcs.TrySetException(new Exception($"加载资源失败: {name}, status: {status}, error: {errorMessage}")),
|
||||
(name, p, ud) => progress?.Report(p)
|
||||
);
|
||||
|
||||
resourceComponent.LoadAsset(assetName, typeof(T), priority, callbacks, userData);
|
||||
|
||||
if (timeout > 0f)
|
||||
{
|
||||
UniTask.Delay(TimeSpan.FromSeconds(timeout), cancellationToken: CancellationToken.None)
|
||||
.ContinueWith(() => tcs.TrySetException(new TimeoutException($"加载资源超时: {assetName}, {timeout}秒")));
|
||||
}
|
||||
|
||||
return tcs.Task;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ namespace SepCore.CustomComponent
|
||||
"tbsoundconfig",
|
||||
"tbuiformconfig",
|
||||
"tbuisoundconfig",
|
||||
"tbformattext"
|
||||
};
|
||||
|
||||
private sealed class TableAccessor
|
||||
@@ -44,24 +45,88 @@ namespace SepCore.CustomComponent
|
||||
}
|
||||
|
||||
private static readonly Dictionary<Type, TableAccessor> TableAccessors =
|
||||
new Dictionary<Type, TableAccessor>
|
||||
new()
|
||||
{
|
||||
{ typeof(CharacterConfig), Accessor(tables => id => tables.TbCharacterConfig.GetOrDefault(id), tables => tables.TbCharacterConfig.DataList) },
|
||||
{ typeof(BattleActionConfig), Accessor(tables => id => tables.TbBattleActionConfig.GetOrDefault(id), tables => tables.TbBattleActionConfig.DataList) },
|
||||
{ typeof(ThreatLevelConfig), Accessor(tables => id => tables.TbThreatLevelConfig.GetOrDefault(id), tables => tables.TbThreatLevelConfig.DataList) },
|
||||
{ typeof(EnemyConfig), Accessor(tables => id => tables.TbEnemyConfig.GetOrDefault(id), tables => tables.TbEnemyConfig.DataList) },
|
||||
{ typeof(EnemyPartyConfig), Accessor(tables => id => tables.TbEnemyPartyConfig.GetOrDefault(id), tables => tables.TbEnemyPartyConfig.DataList) },
|
||||
{ typeof(ItemConfig), Accessor(tables => id => tables.TbItemConfig.GetOrDefault(id), tables => tables.TbItemConfig.DataList) },
|
||||
{ typeof(ResourcePointConfig), Accessor(tables => id => tables.TbResourcePointConfig.GetOrDefault(id), tables => tables.TbResourcePointConfig.DataList) },
|
||||
{ typeof(EnemyDropConfig), Accessor(tables => id => tables.TbEnemyDropConfig.GetOrDefault(id), tables => tables.TbEnemyDropConfig.DataList) },
|
||||
{ typeof(RarityConfig), Accessor(tables => id => tables.TbRarityConfig.GetOrDefault((Rarity)id), tables => tables.TbRarityConfig.DataList) },
|
||||
{ typeof(DifficultyConfig), Accessor(tables => id => tables.TbDifficultyConfig.GetOrDefault((DifficultyTier)id), tables => tables.TbDifficultyConfig.DataList) },
|
||||
{ typeof(EntityConfig), Accessor(tables => id => tables.TbEntityConfig.GetOrDefault(id), tables => tables.TbEntityConfig.DataList) },
|
||||
{ typeof(MusicConfig), Accessor(tables => id => tables.TbMusicConfig.GetOrDefault(id), tables => tables.TbMusicConfig.DataList) },
|
||||
{ typeof(SceneConfig), Accessor(tables => id => tables.TbSceneConfig.GetOrDefault(id), tables => tables.TbSceneConfig.DataList) },
|
||||
{ typeof(SoundConfig), Accessor(tables => id => tables.TbSoundConfig.GetOrDefault(id), tables => tables.TbSoundConfig.DataList) },
|
||||
{ typeof(UIFormConfig), Accessor(tables => id => tables.TbUIFormConfig.GetOrDefault(id), tables => tables.TbUIFormConfig.DataList) },
|
||||
{ typeof(UISoundConfig), Accessor(tables => id => tables.TbUISoundConfig.GetOrDefault(id), tables => tables.TbUISoundConfig.DataList) },
|
||||
{
|
||||
typeof(CharacterConfig),
|
||||
Accessor(tables => id => tables.TbCharacterConfig.GetOrDefault(id),
|
||||
tables => tables.TbCharacterConfig.DataList)
|
||||
},
|
||||
{
|
||||
typeof(BattleActionConfig),
|
||||
Accessor(tables => id => tables.TbBattleActionConfig.GetOrDefault(id),
|
||||
tables => tables.TbBattleActionConfig.DataList)
|
||||
},
|
||||
{
|
||||
typeof(ThreatLevelConfig),
|
||||
Accessor(tables => id => tables.TbThreatLevelConfig.GetOrDefault(id),
|
||||
tables => tables.TbThreatLevelConfig.DataList)
|
||||
},
|
||||
{
|
||||
typeof(EnemyConfig),
|
||||
Accessor(tables => id => tables.TbEnemyConfig.GetOrDefault(id),
|
||||
tables => tables.TbEnemyConfig.DataList)
|
||||
},
|
||||
{
|
||||
typeof(EnemyPartyConfig),
|
||||
Accessor(tables => id => tables.TbEnemyPartyConfig.GetOrDefault(id),
|
||||
tables => tables.TbEnemyPartyConfig.DataList)
|
||||
},
|
||||
{
|
||||
typeof(ItemConfig),
|
||||
Accessor(tables => id => tables.TbItemConfig.GetOrDefault(id),
|
||||
tables => tables.TbItemConfig.DataList)
|
||||
},
|
||||
{
|
||||
typeof(ResourcePointConfig),
|
||||
Accessor(tables => id => tables.TbResourcePointConfig.GetOrDefault(id),
|
||||
tables => tables.TbResourcePointConfig.DataList)
|
||||
},
|
||||
{
|
||||
typeof(EnemyDropConfig),
|
||||
Accessor(tables => id => tables.TbEnemyDropConfig.GetOrDefault(id),
|
||||
tables => tables.TbEnemyDropConfig.DataList)
|
||||
},
|
||||
{
|
||||
typeof(RarityConfig),
|
||||
Accessor(tables => id => tables.TbRarityConfig.GetOrDefault((Rarity)id),
|
||||
tables => tables.TbRarityConfig.DataList)
|
||||
},
|
||||
{
|
||||
typeof(DifficultyConfig),
|
||||
Accessor(tables => id => tables.TbDifficultyConfig.GetOrDefault((DifficultyTier)id),
|
||||
tables => tables.TbDifficultyConfig.DataList)
|
||||
},
|
||||
{
|
||||
typeof(EntityConfig),
|
||||
Accessor(tables => id => tables.TbEntityConfig.GetOrDefault(id),
|
||||
tables => tables.TbEntityConfig.DataList)
|
||||
},
|
||||
{
|
||||
typeof(MusicConfig),
|
||||
Accessor(tables => id => tables.TbMusicConfig.GetOrDefault(id),
|
||||
tables => tables.TbMusicConfig.DataList)
|
||||
},
|
||||
{
|
||||
typeof(SceneConfig),
|
||||
Accessor(tables => id => tables.TbSceneConfig.GetOrDefault((SceneType)id),
|
||||
tables => tables.TbSceneConfig.DataList)
|
||||
},
|
||||
{
|
||||
typeof(SoundConfig),
|
||||
Accessor(tables => id => tables.TbSoundConfig.GetOrDefault(id),
|
||||
tables => tables.TbSoundConfig.DataList)
|
||||
},
|
||||
{
|
||||
typeof(UIFormConfig),
|
||||
Accessor(tables => id => tables.TbUIFormConfig.GetOrDefault((UIFormType)id),
|
||||
tables => tables.TbUIFormConfig.DataList)
|
||||
},
|
||||
{
|
||||
typeof(UISoundConfig),
|
||||
Accessor(tables => id => tables.TbUISoundConfig.GetOrDefault(id),
|
||||
tables => tables.TbUISoundConfig.DataList)
|
||||
},
|
||||
};
|
||||
|
||||
private static TableAccessor Accessor<TValue>(Func<Tables, Func<int, object>> rowGetterFactory,
|
||||
@@ -82,13 +147,15 @@ namespace SepCore.CustomComponent
|
||||
/// <summary>
|
||||
/// 获取 Luban 数据表管理器,加载完成后可用。
|
||||
/// </summary>
|
||||
public SepCore.Definition.Tables Tables { get; private set; }
|
||||
public Tables Tables { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 获取数据表是否已加载完成。
|
||||
/// </summary>
|
||||
public bool IsReady => Tables != null;
|
||||
|
||||
public TbGlobalConfig Global => Tables.TbGlobalConfig;
|
||||
|
||||
/// <summary>
|
||||
/// 按 id 获取数据行,未找到时返回 null。
|
||||
/// </summary>
|
||||
@@ -193,7 +260,8 @@ namespace SepCore.CustomComponent
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLoadTableFailure(string assetName, LoadResourceStatus status, string errorMessage, object userData)
|
||||
private void OnLoadTableFailure(string assetName, LoadResourceStatus status, string errorMessage,
|
||||
object userData)
|
||||
{
|
||||
Log.Error("Can not load data table '{0}' from '{1}' with error message '{2}'.", (string)userData, assetName,
|
||||
errorMessage);
|
||||
@@ -208,4 +276,4 @@ namespace SepCore.CustomComponent
|
||||
return GameFramework.Utility.Text.Format("Assets/GameMain/DataTables/{0}.bytes", tableName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 8247612c245cc47be82009199a823d68
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,192 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using SepCore.Definition;
|
||||
using UnityEngine;
|
||||
using UnityGameFramework.Runtime;
|
||||
|
||||
namespace SepCore.CustomComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// 存档组件。
|
||||
/// 负责游戏存档的读取与写入,数据结构见 Docs/Tech/02_SaveData.md。
|
||||
/// 存档只包含局外状态与已结束单局的结算记录;进行中的单局使用临时状态,不写入存档。
|
||||
/// </summary>
|
||||
public class SaveComponent : GameFrameworkComponent
|
||||
{
|
||||
/// <summary>
|
||||
/// 存档文件名。
|
||||
/// </summary>
|
||||
[SerializeField] private string _fileName = "save.json";
|
||||
|
||||
private SaveData _data = null;
|
||||
private bool _hasSave = false;
|
||||
|
||||
/// <summary>
|
||||
/// 获取当前内存中的存档数据;未加载或未创建时为 null。
|
||||
/// </summary>
|
||||
public SaveData Data => _data;
|
||||
|
||||
/// <summary>
|
||||
/// 获取磁盘上是否已存在存档文件。
|
||||
/// </summary>
|
||||
public bool HasSave => _hasSave;
|
||||
|
||||
/// <summary>
|
||||
/// 获取存档数据是否可用。
|
||||
/// </summary>
|
||||
public bool IsReady => _data != null;
|
||||
|
||||
/// <summary>
|
||||
/// 获取存档文件的完整路径。
|
||||
/// </summary>
|
||||
public string SaveFilePath => GameFramework.Utility.Path.GetRegularPath(
|
||||
Path.Combine(Application.persistentDataPath, _fileName));
|
||||
|
||||
/// <summary>
|
||||
/// 从磁盘加载存档。
|
||||
/// 文件不存在时返回 true 且 HasSave 为 false;解析失败时返回 false。
|
||||
/// </summary>
|
||||
public bool Load()
|
||||
{
|
||||
string path = SaveFilePath;
|
||||
if (!File.Exists(path))
|
||||
{
|
||||
_hasSave = false;
|
||||
_data = null;
|
||||
Log.Info("Save file '{0}' does not exist.", path);
|
||||
return true;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
string json = File.ReadAllText(path);
|
||||
SaveData data = SaveData.FromJson(json);
|
||||
if (data == null)
|
||||
{
|
||||
Log.Error("Parse save file '{0}' failure.", path);
|
||||
_hasSave = true;
|
||||
_data = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data.version != SaveData.CurrentVersion)
|
||||
{
|
||||
Log.Warning("Save file '{0}' version '{1}' does not match current version '{2}'.",
|
||||
path, data.version, SaveData.CurrentVersion);
|
||||
}
|
||||
|
||||
_data = data;
|
||||
_hasSave = true;
|
||||
Log.Info("Load save file '{0}' OK.", path);
|
||||
return true;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error("Load save file '{0}' failure with exception '{1}'.", path, exception);
|
||||
_hasSave = true;
|
||||
_data = null;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建新存档数据(不写盘)。
|
||||
/// 角色与初始装备来自配表:GlobalConfig.NewGameCharacterIds 与 CharacterConfig 的初始装备栏。
|
||||
/// </summary>
|
||||
public bool CreateNewGame()
|
||||
{
|
||||
if (!GameEntry.Luban.IsReady)
|
||||
{
|
||||
Log.Error("Can not create new game before Luban data tables are loaded.");
|
||||
return false;
|
||||
}
|
||||
|
||||
GlobalConfig global = GameEntry.Luban.Global.Data;
|
||||
SaveData data = new SaveData
|
||||
{
|
||||
version = SaveData.CurrentVersion,
|
||||
updatedAt = GetTimestamp(),
|
||||
mainWarehouse = CreateInitialWarehouse(),
|
||||
characters = new List<CharacterSave>(),
|
||||
loadout = new LoadoutSave(),
|
||||
runHistory = new List<RunRecord>(),
|
||||
};
|
||||
|
||||
foreach (int characterId in global.NewGameCharacterIds)
|
||||
{
|
||||
CharacterConfig config = GameEntry.Luban.Get<CharacterConfig>(characterId);
|
||||
int weaponItemId = config != null ? config.WeaponItemId : 0;
|
||||
int armorItemId = config != null ? config.ArmorItemId : 0;
|
||||
data.characters.Add(new CharacterSave(characterId, weaponItemId, armorItemId));
|
||||
}
|
||||
|
||||
data.loadout.Normalize();
|
||||
_data = data;
|
||||
_hasSave = false;
|
||||
Log.Info("Create new game save data OK.");
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将当前存档数据写入磁盘。
|
||||
/// 写入前自动更新 updatedAt;使用临时文件替换,避免写入中断损坏存档。
|
||||
/// </summary>
|
||||
public bool Save()
|
||||
{
|
||||
if (_data == null)
|
||||
{
|
||||
Log.Warning("Can not save because save data does not exist.");
|
||||
return false;
|
||||
}
|
||||
|
||||
_data.updatedAt = GetTimestamp();
|
||||
string json = _data.ToJson();
|
||||
|
||||
string path = SaveFilePath;
|
||||
string tempPath = path + ".tmp";
|
||||
try
|
||||
{
|
||||
File.WriteAllText(tempPath, json);
|
||||
if (File.Exists(path))
|
||||
{
|
||||
File.Delete(path);
|
||||
}
|
||||
|
||||
File.Move(tempPath, path);
|
||||
_hasSave = true;
|
||||
Log.Info("Save save file '{0}' OK.", path);
|
||||
return true;
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
Log.Error("Save save file '{0}' failure with exception '{1}'.", path, exception);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<ItemStack> CreateInitialWarehouse()
|
||||
{
|
||||
// 原型调试种子:覆盖全部物品类型与稀有度,用于筛选等功能开发。
|
||||
// 后续由配表或正式新存档规则替代。
|
||||
return new List<ItemStack>
|
||||
{
|
||||
new ItemStack(5001, 20), // 旧硬币 战利品/白
|
||||
new ItemStack(5002, 7), // 宝石碎片 战利品/绿
|
||||
new ItemStack(5003, 3), // 古代零件 战利品/蓝
|
||||
new ItemStack(5004, 1), // 精致核心 战利品/金
|
||||
new ItemStack(5005, 1), // 神秘遗物 战利品/红
|
||||
new ItemStack(5101, 1), // 训练剑 装备/白
|
||||
new ItemStack(5102, 1), // 法杖 装备/蓝
|
||||
new ItemStack(5201, 1), // 布甲 装备/绿
|
||||
new ItemStack(5202, 1), // 术士长袍 装备/金
|
||||
new ItemStack(5301, 5), // 恢复药 消耗品/白
|
||||
};
|
||||
}
|
||||
|
||||
private static long GetTimestamp()
|
||||
{
|
||||
return DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: e65ab245d7674846b4409282a610b3a9
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -101,10 +101,6 @@ namespace SepCore.UI
|
||||
foreach (var text in tmp_texts)
|
||||
{
|
||||
text.font = _mainTMPFont;
|
||||
if (!string.IsNullOrEmpty(text.text))
|
||||
{
|
||||
text.text = GameEntry.Localization.GetString(text.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user