资源目录划分
框架内容
Lua
UI
实体(模型,特效)
场景
声音
网络
事件
对象池
框架开发流程
第一阶段:Bundle处理
构建
查找BuildResources下的资源文件
使用Unity提供的BuildPipeline进行构建
meta文件不需要打入bundle包
Bundle Build策略
- 按文件夹打包 :每一个最小文件夹打包
- bundle数量少,小包模式:首次下载块
- 后期更新补丁大
- 按文件打包:每一个文件一个包
- 更新补丁很小
- 小包模式下:首次下载较慢
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34// buildTarget:构建目标平台
public static void Build(BuildTarget buildTarget)
{
List<AssetBundleBuild> assetBundleBuilds = new List<AssetBundleBuild>();
//查找目标文件夹下的所有文件
string[] files = Directory.GetFiles(PathUtil.BuildResourcesPath, "*", SearchOption.AllDirectories);
foreach(var file in files)
{
//排除meta文件
if (file.EndsWith("meta"))
continue;
AssetBundleBuild assetBundle = new AssetBundleBuild();
string path = PathUtil.GetStandardPath(file);
string assetName = PathUtil.GetUnityPath(path);
string bundleName = path.Replace(PathUtil.BuildResourcesPath, "");
//设置资源名字和包的名字,包的名字路径需要为相对路径
assetBundle.assetNames = new string[] { assetName };
assetBundle.assetBundleName = bundleName + ".ab";
assetBundleBuilds.Add(assetBundle);
}
if(Directory.Exists(PathUtil.BuildOutPath))
Directory.Delete(PathUtil.BuildOutPath, true);
Directory.CreateDirectory(PathUtil.BuildOutPath);
//使用Buildpeline导出bundle包
BuildPipeline.BuildAssetBundles(PathUtil.BuildOutPath, assetBundleBuilds.ToArray(), BuildAssetBundleOptions.None, buildTarget);
}- 按文件夹打包 :每一个最小文件夹打包
加载
加载对应的包,然后实例化
代码为从文件中异步加载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20//加载一个预制体需要加载其依赖资源
IEnumerator Start()
{
//加载预制体ab包
AssetBundleCreateRequest assetBundleCreateRequest = AssetBundle.LoadFromFileAsync(PathUtil.BuildOutPath + "/prefab/testbutton.prefab.ab");
yield return assetBundleCreateRequest;
//加载素材ab包
AssetBundleCreateRequest assetBundleCreateRequest1 = AssetBundle.LoadFromFileAsync(PathUtil.BuildOutPath + "/ui/background.png.ab");
yield return assetBundleCreateRequest1;
//加载对应预制体
AssetBundleRequest assetBundleRequest = assetBundleCreateRequest.assetBundle.LoadAssetAsync(PathUtil.GetUnityPath(PathUtil.BuildResourcesPath + "Prefab/TestButton.prefab"));
yield return assetBundleRequest;
//实例化
GameObject go = Instantiate(assetBundleRequest.asset) as GameObject;
go.transform.SetParent(transform);
go.SetActive(true);
go.transform.localPosition = Vector3.zero;
}实际过程中不可能查看预制体来知晓依赖文件,因此需要用一个文件来对依赖文件信息进行存储。即版本文件,文件信息为:文件路径名|bundle名|依赖文件列表。
1
2
3
4
5
6
7
8
9
10
11/// <summary>
/// 获取文件的依赖文件列表
/// </summary>
/// <param name="curFile">当前文件</param>
public static List<string> GetDependence(string curFile)
{
List<string> dependence = new List<string>();
string[] files = AssetDatabase.GetDependencies(curFile);
dependence = files.Where(file => !file.EndsWith(".cs") && !file.Equals(curFile)).ToList();
return dependence;
}1
2
3
4
5
6
7//在打包时对依赖文件路径进行记录
List<string> bundleInfos = new List<string>();
List<string> dependenceInfo = GetDependence(assetName);
string bundleInfo = assetName + "|" + bundleName + ".ab";
if(dependenceInfo.Count > 0)
bundleInfo = bundleInfo + "|" + string.Join("|", dependenceInfo);
bundleInfos.Add(bundleInfo);
解析版本文件,加载依赖,加载自身,加载资源
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32/// <summary>
/// 异步加载资源
/// 读取字典中存储的依赖资源路径
/// 递归加载所有依赖资源,当所有以来资源加载完之后加载自身
/// TODO:需要解决重复加载bundle包的问题,且对于场景,不需要对资源惊醒加载
/// 最后回调
/// </summary>
/// <param name="assetName">资源名</param>
/// <param name="action">完成加载后的回调</param>
/// <returns></returns>
IEnumerator LoadBundleAsync(string assetName, Action<Object> action = null)
{
string bundleName = BundleInfosDic[assetName].BundleName;
string bundlePath = Path.Combine(PathUtil.BundleResourcePath, bundleName);
List<string> dependences = BundleInfosDic[assetName].Dependences;
if(dependences != null && dependences.Count > 0)
{
for (int i = 0; i < dependences.Count; i++)
{
yield return LoadBundleAsync(dependences[i]);
}
}
AssetBundleCreateRequest assetBundleCreateRequest = AssetBundle.LoadFromFileAsync(bundlePath);
yield return assetBundleCreateRequest;
AssetBundleRequest assetBundleRequest = assetBundleCreateRequest.assetBundle.LoadAssetAsync(assetName);
yield return assetBundleRequest;
//问号判断前面的变量是否为空
action?.Invoke(assetBundleRequest?.asset);
}开放接口,方便用户调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40public enum AssetType
{
Lua,
Music,
Sound,
Effect,
UI,
Sprite,
Scene
}
public void LoadAsset(AssetType assetType, string assetName, Action<Object> action)
{
switch (assetType)
{
case AssetType.Lua:
LoadAsset(PathUtil.GetLuaPath(assetName), action);
break;
case AssetType.Music:
LoadAsset(PathUtil.GetMusicPath(assetName), action);
break;
case AssetType.Sound:
LoadAsset(PathUtil.GetSoundPath(assetName), action);
break;
case AssetType.Effect:
LoadAsset(PathUtil.GetEffectPath(assetName), action);
break;
case AssetType.UI:
LoadAsset(PathUtil.GetUIPath(assetName), action);
break;
case AssetType.Sprite:
LoadAsset(PathUtil.GetSpritePath(assetName), action);
break;
case AssetType.Scene:
LoadAsset(PathUtil.GetScenePath(assetName), action);
break;
default:
break;
}
}
更新
热更新方案
- 整包
- 策略:完整更新资源放在包内
- 优点:首次更新少
- 缺点:安装包下载时间长,首次安装久
- 分包
- 策略:包内放少量或不妨更新资源
- 优点:安装包小,下载快,安装块
- 缺点:首次更新时间久
- 整包
热更资源流程
Application.streamingAssets:只读
Application.persisitentDataPath:可写
热更新代码流程
下载文件
写入文件
解析filelist
检测初次安装
- 只读目录有热更资源
- 可读写目录没有热更资源
- 判断filelist文件是否存在
- 最后写入filelist
检查更新
- 下载资源服务器上的filelist文件
- 对比文件信息和本地是否一致
文件路径
- 只读目录:Application.streamingAssetsPath/ + path
- 可读写目录:Application.persistentDataPath/ + path
- 资源服务器地址:http://127.0.0.1/AssetBundles/ + path
第二阶段:C#调用Lua
Lua加载与管理
Lua文件加载
执行字符串:直接使用DoString执行一个字符串
luaenv.DoString(“print(‘hello world’)”)
加载Lua文件:使用lua的require函数,lua文件需要在Resource路径下,或者需要绝对路径
DoString(“require ‘byfile’”);
自定义Loader
涉及一个接口
public delegate byte[] CustomLoader(ref string filepath);
public void LuaEnv.AddLoader(CustomLoader loader);
filepath为文件路径,返回值是lua文件的内容
Lua文件管理
- 异步加载
- 同步使用
- 预加载
C#访问LUA
luaenv.Global.Get<int>("a")
luaenv.Global.Get<string>("b")
luaenv.Global.Get<bool>("c")
Lua绑定与执行
绑定:XLua插件的案例2为例
绑定脚本和方法后,可以在C#中进行lua方法的调用
结束运行前需要取消绑定
执行
Lua中调用C#代码
所有的类和命名空间都在CS命名空间下,方法通过 ‘ : ‘ 调用
1
2
3
4
5
6Manager = CS.FrameWork.Managers.Manager
function main()
print("hello world");
Manager.UIManager:OpenUI("TestButton", "UI/UITest");
end
第三阶段:向Lua提供接口
第四阶段:完善和优化
UI管理
界面类型
一极界面
二级弹窗
三级弹窗
特殊界面
UGUI层级特点
- 根据节点顺序渲染
为了解决UI的层级显示问题,根据Unity中的ui渲染顺序,可以设置多个父节点来对所有的UI进行一个分类的管理。每一个层级对应一个父节点。
UI的层级设置应该放在lua热更中,方便之后对ui层级的添加和修改
UIManger中提供设置层级的方法供lua调用
1
2
3
4
5
6
7
8public void SetUIGroup(List<string> groupNames)
{
foreach (string groupName in groupNames)
{
//实例化所有节点
//Unity中先实例化的在上,即先渲染,因此在lua中的顺序就决定了其在unity中层级渲染顺序
}
}1
2--调用c#中的方法
Manager.UIManager:SetUIGroup(group);
实体管理
- 管理方法与UI一致
场景管理
场景加载
通过协程调用SceneManager的的异步场景加载方法
LoadSceneAsync(name, mode);
场景不能直接添加脚本,所以在场景中添加一个物体,用于添加场景的管理脚本,并调用其相关方法。
为了方便,提供一个方法用于获取场景中的管理对象上的管理脚本,即查找场景中的所有物体,然后获得名字为管理对象的脚本
场景叠加
- 即
LoadSceneMode.Additive
模式加载场景,即打开一个新的场景至当前场景
- 即
场景切换
- 即
LoadSceneMode.Additive
模式加载场景,即关闭当前所有场景,打开新的场景
- 即
场景激活
即
SetActiveScene(name)
场景的setActive和InActive会有相关方法调用,因此需要添加场景激活的函数回调
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16//activeSceneChanged为unity提供的事件,用于场景激活后的函数回调
//此函数提供两个参数分别表示激活前和激活后的场景
SceneManager.activeSceneChanged += OnActiveSceneChanged;
private void OnActiveSceneChanged(Scene s1, Scene s2)
{
if(!s1.isLoaded || !s2.isLoaded)
{
return;
}
SceneLogic sl1 = GetSceneLogic(s1);
SceneLogic sl2 = GetSceneLogic(s2);
sl1?.OnInActive();
sl2?.OnActive();
}
场景卸载
和加载类似,通过协程调用异步卸载场景的方法。、
声音管理
背景音乐
播放
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25/// <summary>
/// 播放音乐,需要判断需要播放的是否是当前正在播放的
/// 防止重复加载
/// </summary>
public void PlayMusic(string name)
{
if (MusicVolume < 0.1f)
return;
string oldName = "";
if (musicAudio != null)
{
oldName = musicAudio.clip.name;
}
if (oldName == name)
{
musicAudio.Play();
return;
}
Manager.ResourceManager.LoadAsset(ResourceManager.AssetType.Music, name, (UnityEngine.Object obj) =>
{
musicAudio.clip = obj as AudioClip;
musicAudio.Play();
});
}除播放外,其余只需要调用相关方法即可
暂停
恢复
停止
音量
音效
- 音量
- 播放
事件管理
时间管理可以对所有的事件进行一个统一的管理
- 订阅
- 取消订阅
- 执行调用
对象池设计
对象类型
- AssetBundle
- GameObject
特点
使用自定义类,该类有上次使用时间,使用的对象和销毁的时间间隔 PoolObject
对象池中存放多种类型
用一个List\
对所有对象进行存储 短时间内重复使用
在销毁的间隔时间内能以一直使用
过期自动销毁
当过了销毁时间间隔还未被使用,则将对象进行销毁
设计原理:池内不创建对象,对象在外部创建,使用完放入对象池,
使用时再取出。即不预先创建对象,当需要时向对象池中取,池中没有则创建
由于存在两种对象,且加载和释放的方式有不同,所以定义一个基类PoolBase和两个类继承基类
生成
1
2
3
4
5
6
7
8
9
10
11
12
13//从对象池中获得物体
protected virtual Object Spwan(string name)
{
foreach (var go in poolObjects)
{
if(go.name == name)
{
poolObjects.Remove(go);
return go.@object;
}
}
return null;
}回收
1
2
3
4
5protected virtual void UnSpwan(string name, Object obj)
{
PoolObject poolObject = new PoolObject(name, obj);
poolObjects.Add(poolObject);
}
对于AssetBundle,在游戏中可能存在多个物体对其的引用,只有当没有物体的引用时,才能将其放回对象池中。因此,可以定义一个数据结构对bundle的引用进行计数。
1 | class BundleData |
网络模块
通信协议:用于服务端和客户端通信的数据格式
- protobuf、sproto、pbc、json等
- xlua使用通信协议需要添加第三方库,此项目中直接使用的是github上的编译好的库
客户端C#
NetClient:客户端的网络消息处理类
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28///////
//主要协议相关
///////
//缓冲区大小
private const int BufferSize = 1024 * 64;
//基于TCP协议的客户端协议
private TcpClient client;
//网络数据流,用于发送和接收数据
private NetworkStream tcpStream;
//缓冲区
private byte[] buffer = new byte[BufferSize];
//解析接收的数据
private MemoryStream memStream;
private BinaryReader binaryReader;
//开始连接
client.BeginConnect(host, port, OnConnect, null);
//读取数据
tcpStream.BeginRead(buffer, 0, BufferSize, OnRead, null);
//读取对应长度的字节数
byte[] data = binaryReader.ReadBytes(msgLen);
//发送数据
tcpStream.BeginWrite(sendData, 0, sendData.Length, OnEndSend, null);NetManager:提供消息处理的中转接口,调用lua
1
2
3
4
5
6
7
8
9
10//消息队列,接收到消息就放入消息队列中
private Queue<KeyValuePair<int, string>> messageQueue = new Queue<KeyValuePair<int, string>>();
//lua的function
private XLua.LuaFunction ReceiveMessage;
public void SendMessage(int msgID, string message);
public void ConnectedServer(string host, int port);
public void OnNetConneted();
public void OnDisConnected();
public void Receive(int msgID, string message);
客户端Lua
1 | -- 提供一个基础类定义 |
基于Class存在一个baseMessage类,存在消息注册的方法。在该模块中会存在对消息请求和接收的处理。
1 | --消息注册 |
功能模块化
- 消息注册
- 消息发送
- 消息接收
模块管理器
模块管理器即消息管理,在此类中保存所有的模块列表和所有的回调列表。所有的模块的名字需要手动添加
模块化初始化
1
2
3
4
5
6
7-- 遍历名字列表msgNameList,将所有的模块实例放入模块列表中
function msgManager.init()
for k,v in pairs(msgNameList) do
-- 获得Message目录下的所有相关的模块
msgModelList[v] = require("Message."..v).new();
end
end模块获取
1
2
3
4
5
6
7-- 获取模块实例
function msgManager.getMsg(key)
if not msgModelList[key] then
Log.Error(key .. "不存在");
end
return msgModelList[key];
end消息接收
1
2
3
4
5
6
7
8
9-- 接收消息,用于在C# NetManager 中进行调用
function receiveMsg(msgID, msg)
Log.Info("reveived msg id = " .. msgID .. " : " .. msg);
if type(msgResponses[msgID]) == "function" then
msgResponses[msgID](msg);
else
Log.Error(msgID .. " 消息不存在回调");
end
end消息发送
1
2
3
4
5
6-- 消息发送,调用C# NetManager中方法进行处理
function msgManager.sendMsg(msgID, sendData)
local str = Json.encode(sendData);
Log.Info("send msg id = " .. msgID .. " : " .. str);
Manager.NetManager:SendMessage(msgID, str);
end
简单服务器
热更新框架
为什么需要更新
- 修复产品问题
- 游戏内容扩充/删减
热更新是什么
普通更新流程
需要商店审核,周期长,更新需要重新安装。
热更新流程
不需要商店审核,提交便捷,在游戏内自动完成资源下载和更新
为什么选择LUA
Unity3D 资源热更
C# 编译型语言 脚本打包成动态链接库DLL
Lua 解释型语言 脚本运行时翻译成机器语言,不需要编译。脚本和资源都可以动态替换
Lua热更的几个点
- 需要导入热更插件(XLua、ToLua、SLua等) 稳定易用,不用重复造轮子
- 综合考量,划分项目功能结构。 需要频繁更新的部分就采用热性新,即使用Lua脚本实现
- 编写框架或使用成熟的热更行框架,便于之后开发流程的进行
Xlua框架与项目框架
引入xLua插件, 制定整体架构,选择对应的技术方案
lua框架—主程所考虑的
- 游戏类型:强联网、若联网
- 功能划分与Scene场景规划:背包,战斗等
- 特殊需求
- 制定模块系统:版本更新检测系统、登录等。C#仅调用lua的主要接口,lua脚本中的功能或操作均在lua中处理
中后期项目的热更重构建议
- 核心数据Lua存储
- 配置文件挪到Lua
- 网络交互使用Lua封装和调用
- 业务功能模块不在绑定C#
- 每个UI功能使用Lua处理
- 战斗等复合功能模块,增加辅助Lua处理
- 原框架核心功能部分改成Lua实现,部分打上LUACALLCSHARP标签
- 实现Lua模块间的通信。
XLua框架
框架基本功能
资源目录结构划分
以资源类型划分一级目录
- Custom:用户自定义功能
- Font:字体
- Module:所有功能模块,每个功能对应Lua脚本
- Prefab:预制体
- Sound:音效等
- Sprite:图片
- Texture:纹理
功能模块的引用与操作
功能预制体资源
按照功能将模块封装为预制体
存放于Module目录对应的目录下
Tag设置为uiComponent
功能脚本资源
- 功能名.lua (逻辑脚本)
- Create() 加载并创建功能模块预制体
- Awake() 完成View脚本的初始化和逻辑脚本初始化
- BottonClickHander() 按钮对应方法的调用
- 功能名View.lua (试图脚本)
- Create() 指向模块路径和模块名
- SetUICompenent() 绑定模块下的物体 按钮 图片 预制体
- StartInit() 按钮功能监听
- 功能名.lua (逻辑脚本)
配置文件的使用
图片与预制体资源的动态获取
单机Demo热更新制作规划
ARPG游戏DEMO的需求分析
正式确定DEMO的功能模块
功能模块与技术支持
功能模块
技术支撑
- 准备开发
战斗系统分析
需求分析
- 形式:回合制、即时战斗
- 如何开始:攻击怪物、靠近怪物
- 如何结束:打败敌人、退出地图
配置表与数据结构
需要较为完整的配置表才能更好的进行代码逻辑的编写
模块支撑
分析
- 场景管理:进入战场,场景切换
- 怪物管理:怪物刷新、怪物相关任务
- 战斗计算:战斗过程处理
- 战斗结算:进行结算
- 战斗动画:管理动画
- 特效管理:管理特效
普攻实现
- 攻击距离
- 攻击判定
- 攻击力动态计算
- 数值与公式
技能实现
- 回复术:回复生命值/魔法值等(对Player属性的修改)
- buff:存在时间,可以对多种属性进行增益或减益
- 火球术:伤害类技能,直接碰撞检测等
网游功能模块分析
模块分析
框架技术
网络请求
网络模块分析
- Net模块工作原理
- Net模块功能分析
- Net模块使用操作
- 短链接
- 缺点:连接操作频繁
- 优点:服务器管理成本低,对服务器压力小
- C#端与Http请求回调
- IP地址问题
- 接收Lua回调方法
- 使用Post请求
- 响应后将回调方法加入C#CallLua列表
一次业务请求逻辑流程
方式一
方式二:主数据的 更新交给框架处理,不需要再业务逻辑开发的过程中处理
背包模块分析
- 需求分析
- 滑动列表多物体处理:只显示框内的物体,对在列表外的物体进行隐藏
- 数据流
- 数据结构
- 配置文件
总结
框架概念
框架目标
可靠性
- bug为什么多
- 公共功能架构解决
安全性
- 核心是数据安全
- 加密
- 身份验证
- 只相信服务器的运算
可扩展和和维护
功能和功能之间需要进行合理的划分,之间不能直接进行调用,要方便后续的功能开发
- 功能不断增加
- 功能需要维护
- 功能划分
- 松耦合
可定制和可伸缩
- 可根据需求进行调整:不同项目侧重不同——框架扩充
- 可根据新技术对现有框架进行扩展
- 不同项目自定义模块方法
- lua插件本体也可以更换
客户体验
方便开发
- 开发模板
- 开发工具 :配置文件等
为什么需要热更框架
被动因素
- XLua:和Tween/easyTouch等插件不一样,使用的是lua语言进行项目开发,从根本上改变了原有框架
热更框架目标
- 引擎部分
- 内部采用打包时间作为版本号
C#部分
Lua部分:使代码功能模式化
框架结构
框架项目目录结构
- Ant:自定义组件C#脚本
- AssetBundlesLocal:热更新资源目录
- Editor:编辑器开发目录
- EditorPrefab:自定义组件预制体
- Lua:lua脚本存放目录
- Plugins:插件目录
- Resources:内部资源目录
- Xlua:xlua插件目录
重点功能模块
功能模块调用关系
重点功能模块
UIGameLoading
AppBoot
LuaTools
LuaBehaviour
ResourceManager
SystemTool
重点脚本
ui.lua
main.lua
网络模块
list.lua
GameMainData
event.lua
事件系统,可以订阅和调用
plugin.lua
插件资源管理,方便管理
public.lua
公共资源管理,存放公共方法
tools.lua
框架功能设计
业务功能管理
ui.lua
UIGameLoading
网络请求管理
net.lua
资源打包策略
资源打包前提
资源打包流程
资源打包策略
相关类和函数
AssetImporter:
属性
- assetBundleName / assetBundleVariant:获取或设置AssetBundle Name / Property;
静态方法
- GetAtPath():通过静态方法GetAtPath获取指定路径(相对路径)下的资源的导入器
资源管理策略
ab包加载
编辑器开发
使用unity资源数据库类管理
AssetDatabase.LoadAssetAtPath<AudioClip>("Assets/AssetBundlesLocal/" + path + "/" + name + ".wav")
真机环境(PC、安卓)
- 判断内存中是否加载需要的资源
- 若不存在相应资源,则判断是否存在对应的ab包
- 若存在,则从ab包中读取对应的资源
- 若不存在,则先load这个ab包,放入对应容器,然后加载资源
优点
- 可以避免重复加载同一个ab包
- 每一个资源都不会重复加载
- 通过ab包名和资源名就可以获取相应的资源。ab包名对应路径
ab包加载路径
真机
在从资源服务器判断热更后,下载ab包,将其存放在客户端的PersistentData目录下
编辑器
要将ab包复制到PersistentData目录下
预加载资源
有一些资源需要在运行前加载到内存中
有些资源需要在某功能开始前加载
依赖关系
一个预制体,使用了text,text使用了字体
字体和预制体都被打包未ab包
则这个预制体ab依赖于字体的ab
要使用这个预制体,则需要预加载字体ab
- 按照功能划分ab包,一个功能的所有依赖都打包到同一个ab包中
- 公共部分打包为公共ab包,所有使用它的模块共同依赖这个ab包
预加载常驻内存资源
- 提供加载方法,一般用于字体,shader等资源
预加载模块资源
资源维护与清理
- 维护
- allAssetBundle[path] :存储所有ab包的容器(字典),每一个ab在内存中的引用。ab包被清理后,对应的值也会被清理
- ab.Unload(true):清理ab包,并清理ab包中每一个资源的引用
- ab.Unload(false):仅清理ab包,不清理资源的引用。可能会导致内存泄露
- 清理
- C#端,判断内存中是否存在ab包,存在则Unload
- lua端根据需要的时候调用
- 维护
资源清理策略
如何清理
调用清理方法
调用时机
功能模块关闭后直接调用清理
对于ab包容量不大,资源数适中,在load时不卡顿
根据实际情况决定时机
对于ab包容量大,资源数较多,load时有卡顿
策略一:从源头
- 在ab包划分时:功能划分,资源划分,公共资源部分确定(常驻)
- 资源本生容量控制:在显示效果牺牲不大的情况下降低资源配置
二:补救,通过配置文件决定如何清理
- 延迟清理:在模块关闭一定时间后再清理
- 根据需要
- 时间段:某个时间段内不清理
- 等级:某些等级对某些模块的使用更频繁
- 其他:vip、等
开发工具设计
编译器工具
通过自定义扩展编辑器功能
常规工具
独立运行的exe程序
准换工具
json转换为lua
excel转换为json