This commit is contained in:
huangjun
2026-08-30 22:25:23 +08:00
parent 92bf3e9097
commit 93a10a89c5
180 changed files with 15257 additions and 11604 deletions
@@ -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);
}
}
}