前言
针对游戏中Luban生成的表过多时,直接加载可能卡住主线程的问题,使用异步加载显得很有必要,可以避免在游戏初始的读表过程卡住主线程。
针对Luban Next版本,在Unity GameFrameWork下新增一个LubanComponent实现异步读表的过程。
同时,对于GameFrameWork打包后获取资源路径的问题,不能直接获取到Luban导出表的路径,需要借助GameFrameWork的GameEntry.Resource.LoadAsset函数拿到Luban导出的表。
本篇博客主要用于解决上述两个问题。
具体流程
1. 修改Luban的代码生成模板
本来想新增一个自定义模板的,但是因为Luban的官方文档不清晰,经过尝试也没啥用,所以直接修改的原来的代码生成模板。 (吐槽一句,Luban是个很好用的工具,但是写的文档策划看不懂,程序员也看不懂,或许是我太菜了的缘故)
首先,找到Luban的自定义代码生成模板所在,用作演示的路径如下,因为每个人的Luban工具代码不一样,所以需要找到自己的。
然后,替换tables.sbn中的代码为如下代码
using Luban;
using SimpleJSON;
using System.Threading.Tasks;
{{namespace_with_grace_begin __namespace}}
public partial class {{__name}}
{
{{~for table in __tables ~}}
{{~if table.comment != '' ~}}
/// <summary>
/// {{escape_comment table.comment}}
/// </summary>
{{~end~}}
public {{table.full_name}} {{format_property_name __code_style table.name}} {get; private set;}
{{~end~}}
public async Task LoadAsync(System.Func<string, Task<JSONNode>> loader)
{
{{~for table in __tables ~}}
{{format_property_name __code_style table.name}} = new {{table.full_name}}(await loader("{{table.output_data_file}}"));
{{~end~}}
ResolveRef();
}
private void ResolveRef()
{
{{~for table in __tables ~}}
{{format_property_name __code_style table.name}}.ResolveRef(this);
{{~end~}}
}
}
{{namespace_with_grace_end __namespace}}
使用gen.bat重新生成代码。
2. LubanComponent的实现
具体代码如下
public class LubanComponent : GameFrameworkComponent
{
private cfg.Tables m_LubanTable = new cfg.Tables();
public cfg.Tables LubanTable => m_LubanTable;
private Dictionary<string, bool> m_LoadedFlag = new Dictionary<string, bool>();
private Dictionary<string, TaskCompletionSource<TextAsset>> m_LubanTableTcs = new Dictionary<string, TaskCompletionSource<TextAsset>>();
public async void LoadLubanTable(string loadFlagKey, object userData)
{
m_LoadedFlag.Clear();
m_LubanTableTcs.Clear();
await m_LubanTable.LoadAsync(LoadLuban);
if (IsLoadSuccessful())
{
// 触发Luban表加载成功事件
GameEntry.Event.Fire(this, LoadLubanTableSuccessEventArgs.Create(loadFlagKey, userData));
}
else
{
// 触发Luban表加载失败事件
GameEntry.Event.Fire(this, LoadLubanTableFailureEventArgs.Create(loadFlagKey, userData));
}
}
private bool IsLoadSuccessful()
{
foreach (var flag in m_LoadedFlag)
{
if (!flag.Value)
{
return false;
}
}
return true;
}
private async Task<JSONNode> LoadLuban(string file)
{
return JSON.Parse((await LoadLubanTableAssetAsync(file)).text);
}
private Task<TextAsset> LoadLubanTableAssetAsync(string assetName)
{
var tcs = new TaskCompletionSource<TextAsset>();
string assetFullName = AssetUtility.GetLubanTableAsset(assetName);
m_LubanTableTcs.Add(assetFullName, tcs);
m_LoadedFlag.Add(assetFullName, false);
GameEntry.Resource.LoadAsset(assetFullName,
new LoadAssetCallbacks(
(string assetName, object asset, float duration, object userData) =>
{
m_LubanTableTcs.TryGetValue(assetName, out tcs);
if (tcs != null)
{
m_LoadedFlag[assetName] = true;
tcs.SetResult((TextAsset)asset);
m_LubanTableTcs.Remove(assetName);
Log.Info($"加载Luban表{assetName}成功");
}
},
(string assetName, LoadResourceStatus status, string errorMessage, object userData) =>
{
m_LubanTableTcs.TryGetValue(assetName, out tcs);
if (tcs != null)
{
tcs.SetCanceled();
m_LubanTableTcs.Remove(assetName);
}
Log.Error($"从{assetName}加载Luban表失败,失败信息:{errorMessage}");
}
));
return tcs.Task;
}
}
其中LoadLubanTableSuccessEventArgs和LoadLubanTableFailureEventArgs均继承自GameEventArgs,具体实现这里按下不表,逻辑可以参考框架原来的一些GameEventArgs,以及烟雨大佬的博客
实现之后,可以在GameEntry加入LubanComponent方便后续拿取。
3. 流程中加载Luban全表的代码
我们在ProcedurePreload完成Luban的全表加载,加载代码如下
private void PreloadResources()
{
LoadLubanTable("LubanGeneratedTables");
}
private void LoadLubanTable(string directory)
{
string directoryName = AssetUtility.GetAssetRootDirectory(directory);
m_LoadedFlag.Add(directoryName, false);
GameEntry.Luban.LoadLubanTable(directoryName, this);
}
private void OnLoadLubanTableSuccess(object sender, GameEventArgs e)
{
LoadLubanTableSuccessEventArgs ne = (LoadLubanTableSuccessEventArgs) e;
if (ne.UserData != this)
{
return;
}
m_LoadedFlag[ne.LoadFlagKey] = true;
Log.Info("Preload luban success" + ne.LoadFlagKey);
}
private void OnLoadLubanTableFailure(object sender, GameEventArgs e)
{
LoadLubanTableFailureEventArgs ne = (LoadLubanTableFailureEventArgs) e;
if (ne.UserData != this)
{
return;
}
Log.Error("Can not preload luban");
}
通过下面的代码拿取想要的表的值
GameEntry.Luban.LubanTable.TbdataTest.DataList[0].Key
结语
整个代码思路参考此开源项目中的Luban部分:github.com/DangoRyn/Un…,感谢大佬的开源
因为此开源项目中的Luban是classic版本,因此针对这个情况做出一定的更新