XLua热更新框架原理和代码实战
安装插件
下载Xlua插件:https://github.com/Tencent/xLua
下载完成后,把Asset文件夹下的文件拖入自己的工程Asset中,看到Unity编辑器上多了个Xlua菜单,说明插件导入成功
Lua启动代码
新建一个空场景,场景中什么都不放,只有一个启动脚本,所有的东西都从启动脚本中加载,这样打包时才能没有依赖,所有资源支持热更。
启动脚本
GameLaunch:
using UnityEngine;public class GameLaunch : MonoBehaviour {void Awake() { // 初始化框架this.gameObject.AddComponent<show_fps>();this.gameObject.AddComponent<xLuaMgr>();// end xLuaMgr.Instance.Init();}void Start () {// 进入启动逻辑xLuaMgr.Instance.EnterLuaGame();// end }void Update () {}
}
xLua管理脚本
using UnityEngine;
using System.IO;
using XLua;public class xLuaMgr : UnitySingleton<xLuaMgr> {public const string luaScriptsFolder = "LuaScripts";const string gameMainScriptName = "main"; // main.lua// Lua解释器的上下文的运行环境private LuaEnv luaEnv = null;private bool HasGameStart = false;public override void Awake() {base.Awake();}public void SafeDoString(string scriptContent) { // 执行脚本, scriptContent脚本代码的文本内容;if (this.luaEnv != null) {try {luaEnv.DoString(scriptContent); // 执行我们的脚本代码;}catch (System.Exception ex) {string msg = string.Format("xLua exception : {0}\n {1}", ex.Message, ex.StackTrace);Debug.LogError(msg, null);}}}public void LoadScript(string scriptName) { // require(game.game_start) scriptName = "game.game_start"SafeDoString(string.Format("require('{0}')", scriptName)); // }public void ReloadScript(string scriptName) {SafeDoString(string.Format("package.loaded['{0}'] = nil", scriptName));LoadScript(scriptName);}public void Init() {this.luaEnv = new LuaEnv(); // 添加Lua代码装载器,当请求文件的时候(调用require),会调用对应的CustomLoader函数if (this.luaEnv != null) {this.luaEnv.AddLoader(CustomLoader);}}public static byte[] CustomLoader(ref string filePath){string scriptPath = string.Empty;// 把传递文件路径时修改的点改回斜杠,加上尾缀filePath = filePath.Replace(".", "/") + ".lua";
#if UNITY_EDITOR// if (AssetBundleConfig.IsEditorMode){scriptPath = Path.Combine(Application.dataPath, luaScriptsFolder);// Assets/LuaScriptsscriptPath = Path.Combine(scriptPath, filePath); // Assets/LuaScripts/game/game_start.lua// Debug.Log("Custom Load lua script : " + scriptPath);return GameUtility.SafeReadAllBytes(scriptPath);}
#endif/*scriptPath = string.Format("{0}/{1}.bytes", luaAssetbundleAssetName, filePath);string assetbundleName = null;string assetName = null;bool status = AssetBundleManager.Instance.MapAssetPath(scriptPath, out assetbundleName, out assetName);if (!status){Debug.LogError("MapAssetPath failed : " + scriptPath);return null;}var asset = AssetBundleManager.Instance.GetAssetCache(assetName) as TextAsset;if (asset != null){return asset.bytes;}Debug.LogError("Load lua script failed : " + scriptPath + ", You should preload lua assetbundle first!!!");return null;*/}void Start () {}public void EnterLuaGame() { // 进入游戏 if (this.luaEnv != null) {// 装载main脚本this.LoadScript(gameMainScriptName);// 执行main.start()SafeDoString("main.start()");this.HasGameStart = true;}}void Update () {}
}
Main.lua
require("game.game_start")main = {} -- main是一个全局模块;local function start()print("game started")
endmain.start = start
return main
通用脚本:单例
using UnityEngine;// 实现普通的单例模式
// where 限制模板的类型, new()指的是这个类型必须要能被实例化
public abstract class Singleton<T> where T : new() {private static T _instance;private static object mutex = new object();public static T instance {get {if (_instance == null) {lock (mutex) { // 保证我们的单例,是线程安全的;if (_instance == null) {_instance = new T();}}}return _instance;}}
}// Monobeavior: 声音, 网络
// Unity单例public class UnitySingleton<T> : MonoBehaviour
where T : Component {private static T _instance = null;public static T Instance {get {if (_instance == null) {_instance = FindObjectOfType(typeof(T)) as T;if (_instance == null) {GameObject obj = new GameObject();_instance = (T)obj.AddComponent(typeof(T));obj.hideFlags = HideFlags.DontSave;// obj.hideFlags = HideFlags.HideAndDontSave;obj.name = typeof(T).Name;}}return _instance;}}public virtual void Awake() {DontDestroyOnLoad(this.gameObject);if (_instance == null) {_instance = this as T;}else {GameObject.Destroy(this.gameObject);}}
}
把对应的Lua代码放到LuaScripts文件夹中,这些程序就能正常执行了,从此C#能正确调用Lua,可以使用Lua代替C#进行开发了。
Lua脚本组件化开发模式
关联Update、LateUpdate等
xLuaMgr.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using XLua;public class xLuaMgr : UnitySingleton<xLuaMgr> {public const string luaScriptsFolder = "LuaScripts";const string gameMainScriptName = "main"; // main.luaprivate LuaEnv luaEnv = null;private bool HasGameStart = false;public override void Awake() {base.Awake();}public void SafeDoString(string scriptContent) { // 执行脚本, scriptContent脚本代码的文本内容;if (this.luaEnv != null) {try {luaEnv.DoString(scriptContent); // 执行我们的脚本代码;}catch (System.Exception ex) {string msg = string.Format("xLua exception : {0}\n {1}", ex.Message, ex.StackTrace);Debug.LogError(msg, null);}}}public void LoadScript(string scriptName) { // require(game.game_start) scriptName = "game.game_start"SafeDoString(string.Format("require('{0}')", scriptName)); // }public void ReloadScript(string scriptName) {SafeDoString(string.Format("package.loaded['{0}'] = nil", scriptName));LoadScript(scriptName);}public void Init() {this.luaEnv = new LuaEnv(); // if (this.luaEnv != null) {this.luaEnv.AddLoader(CustomLoader);}}// require(main); // require(game.game_start)public static byte[] CustomLoader(ref string filePath){string scriptPath = string.Empty;filePath = filePath.Replace(".", "/") + ".lua"; // game/game_start.lua
#if UNITY_EDITOR// if (AssetBundleConfig.IsEditorMode){scriptPath = Path.Combine(Application.dataPath, luaScriptsFolder);// Assets/LuaScriptsscriptPath = Path.Combine(scriptPath, filePath); // Assets/LuaScripts/game/game_start.lua// Debug.Log("Custom Load lua script : " + scriptPath);return GameUtility.SafeReadAllBytes(scriptPath);}
#endif/*scriptPath = string.Format("{0}/{1}.bytes", luaAssetbundleAssetName, filePath);string assetbundleName = null;string assetName = null;bool status = AssetBundleManager.Instance.MapAssetPath(scriptPath, out assetbundleName, out assetName);if (!status){Debug.LogError("MapAssetPath failed : " + scriptPath);return null;}var asset = AssetBundleManager.Instance.GetAssetCache(assetName) as TextAsset;if (asset != null){return asset.bytes;}Debug.LogError("Load lua script failed : " + scriptPath + ", You should preload lua assetbundle first!!!");return null;*/}void Start () {}public void EnterLuaGame() { // 进入游戏 if (this.luaEnv != null) {this.LoadScript(gameMainScriptName);SafeDoString("main.start()");this.HasGameStart = true;}}void Update () {if (this.HasGameStart) {SafeDoString("main.Update()");}}void FixedUpdate() {if (this.HasGameStart) {SafeDoString("main.FixedUpdate()");}}void LateUpdate() {if (this.HasGameStart) {SafeDoString("main.LateUpdate()");}}
}
GameLaunch脚本:
require("managers.LuaGameObject")
local game = require("game.start")main = {} -- main是一个全局模块;local function start()game.init();
endlocal function OnApplicationQuit()
endlocal function Update()LuaGameObject.Update()
endlocal function FixedUpdate()LuaGameObject.FixedUpdate()
endlocal function LateUpdate()LuaGameObject.LateUpdate()
endmain.OnApplicationQuit = OnApplicationQuit
main.Update = Update
main.FixedUpdate = FixedUpdate
main.LateUpdate = LateUpdate
main.start = startreturn main
Lua组件的基类
所有的组件基类继承自LuaBehaviour
-- 返回一个基类为base的类;用于继承
function LuaExtend(base) return base:new()
endlocal LuaBehaviour = {}
function LuaBehaviour:new(instant) if not instant then instant = {} --类的实例endsetmetatable(instant, {__index = self}) return instant
end-- obj: GameObject
-- transform, gameObject
function LuaBehaviour:init(obj)self.transform = obj.transformself.gameObject = obj
endreturn LuaBehaviour
Lua组件化管理
管理脚本:
LuaGameObject = {}local GameObject = CS.UnityEngine.GameObject
local GameObjectMap = {}
-- key ObjectID: {lua组件实例1, Lua组件实例2, ...};local function Instantiate(prefab)GameObject.Instantiate(prefab)
endlocal function Destroy(obj)local obj_id = obj:GetInstanceID()if (GameObjectMap[obj_id]) then -- 删除掉所有的组件实例table.remove(GameObjectMap, obj_id)endGameObject.Destroy(obj)
endlocal function DestroyAfter(obj, afterTime)local obj_id = obj:GetInstanceID()if (GameObjectMap[obj_id]) then -- 删除掉所有的组件实例table.remove(GameObjectMap, obj_id)endGameObject.Destroy(obj, afterTime)
endlocal function Find(name)return GameObject.Find(name)
endlocal function AddLuaComponent(obj, lua_class)local componet = lua_class:new()componet:init(obj) local obj_id = obj:GetInstanceID()if (GameObjectMap[obj_id]) thentable.insert(GameObjectMap[obj_id], componet)elseGameObjectMap[obj_id] = {}table.insert(GameObjectMap[obj_id], componet)endif componet.Awake ~= nil then componet:Awake()endreturn componet
endlocal function GetLuaComponent(obj, lua_class)return nil
endlocal function trigger_update(components_array)local key, valuefor key, value in pairs(components_array) doif value.Update ~= nil thenvalue:Update()endend
endlocal function trigger_fixupdate(components_array)local key, valuefor key, value in pairs(components_array) doif value.FixedUpdate ~= nil thenvalue:FixedUpdate()endend
endlocal function trigger_lateupdate(components_array)local key, valuefor key, value in pairs(components_array) doif value.LateUpdate ~= nil thenvalue:LateUpdate()endend
endlocal function Update()local key, valuefor key, value in pairs(GameObjectMap) dotrigger_update(value)end
endlocal function FixedUpdate()local key, valuefor key, value in pairs(GameObjectMap) dotrigger_fixupdate(value)end
endlocal function LateUpdate()local key, valuefor key, value in pairs(GameObjectMap) dotrigger_lateupdate(value)end
endLuaGameObject.Update = Update
LuaGameObject.LateUpdate = LateUpdate
LuaGameObject.FixedUpdate = FixedUpdateLuaGameObject.Find = Find
LuaGameObject.Instantiate = Instantiate
LuaGameObject.Destroy = Destroy
LuaGameObject.DestroyAfter = DestroyAfter
LuaGameObject.AddLuaComponent = AddLuaComponent
LuaGameObject.GetLuaComponent = GetLuaComponentreturn LuaGameObject
添加组件方法
例如有一个控制物体移动的脚本:
local LuaBehaviour = require("Component.LuaBehaviour")
local cube_ctrl = LuaExtend(LuaBehaviour)function cube_ctrl:Awake()print("========Awake=========")
end function cube_ctrl:Update()self.transform:Translate(0, 0, 5 * CS.UnityEngine.Time.deltaTime)
endreturn cube_ctrl
这个脚本除了加上最上面两行和最后一行,其他的写法都是C#当中的。
调用这个脚本时:
local cube_ctrl = require("game.cube_ctrl")
local obj = LuaGameObject.Find("Cube")
LuaGameObject.AddLuaComponent(obj, cube_ctrl)
例子:
local start = {}
local cube_ctrl = require("game.cube_ctrl")local function enter_login_scene()print("enter_login_scene")-- 放地图-- end-- 放怪物-- end -- 放玩家-- end-- 放UI--end-- 测试local obj = LuaGameObject.Find("Cube")LuaGameObject.AddLuaComponent(obj, cube_ctrl)-- end
endlocal function enter_game_scene()
endlocal function init()enter_login_scene()
endstart.init = initreturn start;
Unity编辑器创建Lua模板
这个脚本放在Editor目录下作为编辑器脚本:
using UnityEngine;
using System.Collections;
using UnityEditor.ProjectWindowCallback;
using System.IO;
using UnityEditor;
public class CreateLua {[MenuItem("Assets/Create/Lua Script",false,80)] //80是菜单的次序public static void CreateNewLua(){ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0,ScriptableObject.CreateInstance<CreateScriptAssetAction>(),GetSelectedPathOrFallback() + "/New Lua.lua",null,"Assets/Editor/Template/LuaComponent.lua");}public static string GetSelectedPathOrFallback(){string path = "Assets";foreach (UnityEngine.Object obj in Selection.GetFiltered(typeof(UnityEngine.Object), SelectionMode.Assets)){path = AssetDatabase.GetAssetPath(obj);if (!string.IsNullOrEmpty(path) && File.Exists(path)){path = Path.GetDirectoryName(path);break;}}return path;}
}
class CreateScriptAssetAction:EndNameEditAction
{public override void Action(int instanceId, string pathName, string resourceFile){//创建资源UnityEngine.Object obj = CreateAssetFromTemplate(pathName, resourceFile);//高亮显示该资源ProjectWindowUtil.ShowCreatedAsset(obj);}internal static UnityEngine.Object CreateAssetFromTemplate(string pahtName, string resourceFile){//获取要创建的资源的绝对路径string fullName = Path.GetFullPath(pahtName);//读取本地模板文件StreamReader reader = new StreamReader(resourceFile);string content = reader.ReadToEnd();reader.Close();//获取资源的文件名// string fileName = Path.GetFileNameWithoutExtension(pahtName);//替换默认的文件名content = content.Replace("#TIME", System.DateTime.Now.ToString("yyyy年MM月dd日 HH:mm:ss dddd"));//写入新文件StreamWriter writer = new StreamWriter(fullName, false, new System.Text.UTF8Encoding(false));writer.Write(content);writer.Close();//刷新本地资源AssetDatabase.ImportAsset(pahtName);AssetDatabase.Refresh();return AssetDatabase.LoadAssetAtPath(pahtName, typeof(UnityEngine.Object));}
}
在Editor目录下,创建Template文件夹存放模板,里面的文件如下:
local LuaBehaviour = require("Component.LuaBehaviour")
local newclass = LuaExtend(LuaBehaviour)function newclass:Awake()
end function newclass:Update()
endreturn newclass
这样,在右键菜单Create中就会多出一个Lua Script的选项,可以直接创建Lua的模板文件。
Lua调用Unity相关组件和接口
Unity编辑器相关:CS.UnityEngine,例如:CS.UnityEngine.Time.deltaTime
自己定义的类:CS.命名空间.类名
为了方便我们可以在lua中重新进行定义:
local Time = CS.UnityEngine.Time
另外,在上面组件化开发模式中,我们可以直接才lua模块中使用obj.gameObject,obj.transform来获取对应物体和对应物体的transform的信息。
C#端创建一个脚本:
using UnityEngine;
using System;
using XLua;[LuaCallCSharp] // Lua 能否调用到这个装饰器很重要;
public class ResMgr : UnitySingleton<ResMgr>
{public override void Awake() {base.Awake();}public UnityEngine.Object GetAssetCache(string name, string type_name) {Debug.Log("UnityEngine: GetAssetCache");return null;}public void LoadAssetBundleAsync(string assetbundleName, Action end_func){end_func();// this.StartCoroutine(this.IE_LoadAssetBundleAsync(assetbundleName, end_func));}
}
Lua调用这个脚本
local ResMgr = {}local cs_ResMgr = CS.ResMgr.Instance
local function GetAssetCache(name, type_name)cs_ResMgr:GetAssetCache(name, type_name)
end
ResMgr.GetAssetCache = GetAssetCachelocal function LoadAssetBundleAsync(name, callback)cs_ResMgr:LoadAssetBundleAsync(name, callback)
endResMgr.LoadAssetBundleAsync = LoadAssetBundleAsync
return ResMgr
使用的时候:
local ResMgr = require("managers.ResMgr")
ResMgr.GetAssetCache("test","test_type")
--回调函数
ResMgr.LoadAssetBundleAsync("name", function() print("C#") end)
资源管理
编辑器内部资源启动和AssetBundle启动游戏
启动游戏脚本
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using AssetBundles;
using GameChannel;public class GameLaunch : MonoBehaviour {void Awake() { // 初始化框架this.gameObject.AddComponent<show_fps>();this.gameObject.AddComponent<xLuaMgr>();this.gameObject.AddComponent<ResMgr>();// end xLuaMgr.Instance.Init();}IEnumerator InitPackageName(){
#if UNITY_EDITORif (AssetBundleConfig.IsEditorMode){yield break;}
#endif// 获取渠道名字,在文件中设置var packageNameRequest = AssetBundleManager.Instance.RequestAssetFileAsync(BuildUtils.PackageNameFileName);yield return packageNameRequest;var packageName = packageNameRequest.text;packageNameRequest.Dispose();AssetBundleManager.ManifestBundleName = packageName;// 初始化渠道ChannelManager.instance.Init(packageName);Debug.Log(string.Format("packageName = {0}", packageName));yield break;}IEnumerator GameStart(){var start = DateTime.Now;yield return InitPackageName();Debug.Log(string.Format("InitPackageName use {0}ms", (DateTime.Now - start).Milliseconds));// 启动资源管理模块start = DateTime.Now;yield return AssetBundleManager.Instance.Initialize();Debug.Log(string.Format("AssetBundleManager Initialize use {0}ms", (DateTime.Now - start).Milliseconds));string luaAssetbundleName = xLuaMgr.Instance.AssetbundleName;// lua脚本AssetBundle装载进内存AssetBundleManager.Instance.SetAssetBundleResident(luaAssetbundleName, true);var abloader = AssetBundleManager.Instance.LoadAssetBundleAsync(luaAssetbundleName);yield return abloader;abloader.Dispose();xLuaMgr.Instance.EnterLuaGame();yield break;}void Start () {this.StartCoroutine(this.GameStart());}void Update () {}
}
XLuaManager
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using XLua;
using AssetBundles;public class xLuaMgr : UnitySingleton<xLuaMgr> {public const string luaScriptsFolder = "LuaScripts";public const string luaAssetbundleAssetName = "Lua";const string gameMainScriptName = "main"; // main.luaprivate LuaEnv luaEnv = null;private bool HasGameStart = false;public override void Awake() {base.Awake();string path = AssetBundleUtility.PackagePathToAssetsPath(luaAssetbundleAssetName);AssetbundleName = AssetBundleUtility.AssetBundlePathToAssetBundleName(path);}public string AssetbundleName {get;protected set;}public void SafeDoString(string scriptContent) { // 执行脚本, scriptContent脚本代码的文本内容;if (this.luaEnv != null) {try {luaEnv.DoString(scriptContent); // 执行我们的脚本代码;}catch (System.Exception ex) {string msg = string.Format("xLua exception : {0}\n {1}", ex.Message, ex.StackTrace);Debug.LogError(msg, null);}}}public void LoadScript(string scriptName) { // require(game.game_start) scriptName = "game.game_start"SafeDoString(string.Format("require('{0}')", scriptName)); // }public void ReloadScript(string scriptName) {SafeDoString(string.Format("package.loaded['{0}'] = nil", scriptName));LoadScript(scriptName);}public void Init() {this.luaEnv = new LuaEnv(); // if (this.luaEnv != null) {this.luaEnv.AddLoader(CustomLoader);}}// require(main); // require(game.game_start)public static byte[] CustomLoader(ref string filePath){string scriptPath = string.Empty;filePath = filePath.Replace(".", "/") + ".lua"; // game/game_start.lua// 编辑器模式,直接从本地lua文件读代码
#if UNITY_EDITORif (AssetBundleConfig.IsEditorMode) {scriptPath = Path.Combine(Application.dataPath, luaScriptsFolder);// Assets/LuaScriptsscriptPath = Path.Combine(scriptPath, filePath); // Assets/LuaScripts/game/game_start.luabyte[] data = GameUtility.SafeReadAllBytes(scriptPath);return data;}
#endif// 非编辑器模式,从AssetBundle读scriptPath = string.Format("{0}/{1}.bytes", luaAssetbundleAssetName, filePath);string assetbundleName = null;string assetName = null;bool status = AssetBundleManager.Instance.MapAssetPath(scriptPath, out assetbundleName, out assetName);if (!status){Debug.LogError("MapAssetPath failed : " + scriptPath);return null;}var asset = AssetBundleManager.Instance.GetAssetCache(assetName) as TextAsset;if (asset != null){return asset.bytes;}Debug.LogError("Load lua script failed : " + scriptPath + ", You should preload lua assetbundle first!!!");return null;}void Start () {}public void EnterLuaGame() { // 进入游戏 if (this.luaEnv != null) {this.LoadScript(gameMainScriptName);SafeDoString("main.start()");this.HasGameStart = true;}}void Update () {if (this.HasGameStart) {SafeDoString("main.Update()");}}void FixedUpdate() {if (this.HasGameStart) {SafeDoString("main.FixedUpdate()");}}void LateUpdate() {if (this.HasGameStart) {SafeDoString("main.LateUpdate()");}}
}/** local ResMgr = {}local cs_ResMgr = CS.ResMgr.Instance
local function GetAssetCache(name, type_name)cs_ResMgr:GetAssetCache(name, type_name)
endreturn ResMgr*/
管理器代码
渠道管理器框架
using System;
using XLua;namespace GameChannel
{[Hotfix][LuaCallCSharp]public class ChannelManager : Singleton<ChannelManager>{private BaseChannel channel = null;private Action initDelFun = null;public Action downLoadGameSucceed = null;public Action downLoadGameFail = null;public Action<int> downLoadGameProgress = null;public string packageName{get;protected set;}public string noticeVersion{get;set;}public string resVersion{get;set;}public string appVersion{get;set;}public string svnVersion{get;set;}public void Init(string packageName){this.packageName = packageName;channel = CreateChannel(packageName);}public BaseChannel CreateChannel(string packageName){ChannelType platName = (ChannelType)Enum.Parse(typeof(ChannelType), packageName);switch ((platName)){case ChannelType.Test:return new TestChannel();default:return new TestChannel();}}public void InitSDK(Action delFun){initDelFun = delFun;channel.Init();channel.DataTrackInit();}public void InitSDKComplete(string msg){// Logger.platChannel = packageName;if (initDelFun != null){initDelFun.Invoke();initDelFun = null;}}public void StartDownLoadGame(string url, Action succeed = null, Action fail = null, Action<int> progress = null, string saveName = null){downLoadGameSucceed = succeed;downLoadGameFail = fail;downLoadGameProgress = progress;channel.DownloadGame(url, saveName);}public void DownLoadGameEnd(bool succeed){if (succeed){if (downLoadGameSucceed != null){downLoadGameSucceed.Invoke();}}else{if (downLoadGameFail != null){downLoadGameFail.Invoke();}}downLoadGameSucceed = null;downLoadGameFail = null;downLoadGameProgress = null;}public void DownLoadGameProgress(int progress){if (downLoadGameProgress != null){downLoadGameProgress.Invoke(progress);}}public void InstallGame(Action succeed, Action fail){downLoadGameSucceed = succeed;downLoadGameFail = fail;AndroidSDKHelper.FuncCall("InstallApk");}public bool IsInternalVersion(){if (channel == null){return true;}return channel.IsInternalChannel();}/*public override void Dispose(){}*/}
}
AssetBundle管理器框架
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using XLua;
using System;
#if UNITY_EDITOR
using UnityEditor;
#endif/// <summary>
/// added by wsh @ 2017-12-21
/// 功能:assetbundle管理类,为外部提供统一的资源加载界面、协调Assetbundle各个子系统的运行
/// 注意:
/// 1、抛弃Resources目录的使用,官方建议:https://unity3d.com/cn/learn/tutorials/temas/best-practices/resources-folder?playlist=30089
/// 2、提供Editor和Simulate模式,前者不适用Assetbundle,直接加载资源,快速开发;后者使用Assetbundle,用本地服务器模拟资源更新
/// 3、场景不进行打包,场景资源打包为预设
/// 4、只提供异步接口,所有加载按异步进行
/// 5、采用LZMA压缩方式,性能瓶颈在Assetbundle加载上,ab加载异步,asset加载同步,ab加载后导出全部asset并卸载ab
/// 6、所有公共ab包(被多个ab包依赖)常驻内存,非公共包加载asset以后立刻卸载,被依赖的公共ab包会随着资源预加载自动加载并常驻内存
/// 7、随意卸载公共ab包可能导致内存资源重复,最好在切换场景时再手动清理不需要的公共ab包
/// 8、常驻包(公共ab包)引用计数不为0时手动清理无效,正在等待加载的所有ab包不能强行终止---一旦发起创建就一定要等操作结束,异步过程进行中清理无效
/// 9、切换场景时最好预加载所有可能使用到的资源,所有加载器用完以后记得Dispose回收,清理GC时注意先释放所有Asset缓存
/// 10、逻辑层所有Asset路径带文件类型后缀,且是AssetBundleConfig.ResourcesFolderName下的相对路径,注意:路径区分大小写
/// TODO:
/// 1、区分场景常驻包和全局公共包,切换场景时自动卸载场景公共包
/// 使用说明:
/// 1、由Asset路径获取AssetName、AssetBundleName:ParseAssetPathToNames
/// 2、设置常驻(公共)ab包:SetAssetBundleResident(assebundleName, true)---公共ab包已经自动设置常驻
/// 2、(预)加载资源:var loader = LoadAssetBundleAsync(assetbundleName),协程等待加载完毕后Dispose:loader.Dispose()
/// 3、加载Asset资源:var loader = LoadAssetAsync(assetPath, TextAsset),协程等待加载完毕后Dispose:loader.Dispose()
/// 4、离开场景清理所有Asset缓存:ClearAssetsCache(),UnloadUnusedAssetBundles(), Resources.UnloadUnusedAssets()
/// 5、离开场景清理必要的(公共)ab包:TryUnloadAssetBundle(),注意:这里只是尝试卸载,所有引用计数不为0的包(还正在加载)不会被清理
/// </summary>namespace AssetBundles
{[Hotfix][LuaCallCSharp]public class AssetBundleManager : UnitySingleton<AssetBundleManager>{// 最大同时进行的ab创建数量const int MAX_ASSETBUNDLE_CREATE_NUM = 5;// manifest:提供依赖关系查找以及hash值比对Manifest manifest = null;// 资源路径相关的映射表AssetsPathMapping assetsPathMapping = null;// 常驻ab包:需要手动添加公共ab包进来,常驻包不会自动卸载(即使引用计数为0),引用计数为0时可以手动卸载HashSet<string> assetbundleResident = new HashSet<string>();// ab缓存包:所有目前已经加载的ab包,包括临时ab包与公共ab包Dictionary<string, AssetBundle> assetbundlesCaching = new Dictionary<string, AssetBundle>();// ab缓存包引用计数:卸载ab包时只有引用计数为0时才会真正执行卸载Dictionary<string, int> assetbundleRefCount = new Dictionary<string, int>(); // asset缓存:给非公共ab包的asset提供逻辑层的复用Dictionary<string, UnityEngine.Object> assetsCaching = new Dictionary<string, UnityEngine.Object>();// 加载数据请求:正在prosessing或者等待prosessing的资源请求Dictionary<string, ResourceWebRequester> webRequesting = new Dictionary<string, ResourceWebRequester>();// 等待处理的资源请求Queue<ResourceWebRequester> webRequesterQueue = new Queue<ResourceWebRequester>();// 正在处理的资源请求List<ResourceWebRequester> prosessingWebRequester = new List<ResourceWebRequester>();// 逻辑层正在等待的ab加载异步句柄List<AssetBundleAsyncLoader> prosessingAssetBundleAsyncLoader = new List<AssetBundleAsyncLoader>();// 逻辑层正在等待的asset加载异步句柄List<AssetAsyncLoader> prosessingAssetAsyncLoader = new List<AssetAsyncLoader>();public static string ManifestBundleName{get;set;}#if UNITY_EDITOR || CLIENT_DEBUG
#if !CLIENT_DEBUG[BlackList]
#endif// Hotfix测试---用于侧测试资源模块的热修复public void TestHotfix(){Debug.Log("********** AssetBundleManager : Call TestHotfix in cs...");}
#endifpublic IEnumerator Initialize(){
#if UNITY_EDITORif (AssetBundleConfig.IsEditorMode){yield break;}
#endifmanifest = new Manifest();assetsPathMapping = new AssetsPathMapping();// 说明:同时请求资源可以提高加载速度var manifestRequest = RequestAssetBundleAsync(manifest.AssetbundleName);var pathMapRequest = RequestAssetBundleAsync(assetsPathMapping.AssetbundleName);yield return manifestRequest;var assetbundle = manifestRequest.assetbundle;manifest.LoadFromAssetbundle(assetbundle);assetbundle.Unload(false);manifestRequest.Dispose();yield return pathMapRequest;assetbundle = pathMapRequest.assetbundle;var mapContent = assetbundle.LoadAsset<TextAsset>(assetsPathMapping.AssetName);if (mapContent != null){assetsPathMapping.Initialize(mapContent.text);}assetbundle.Unload(true);pathMapRequest.Dispose();// 设置所有公共包为常驻包var start = DateTime.Now;var allAssetbundleNames = manifest.GetAllAssetBundleNames();foreach (var curAssetbundleName in allAssetbundleNames){if (string.IsNullOrEmpty(curAssetbundleName)){continue;}int count = 0;foreach (var checkAssetbundle in allAssetbundleNames){if (checkAssetbundle == curAssetbundleName || string.IsNullOrEmpty(checkAssetbundle)){continue;}var allDependencies = manifest.GetAllDependencies(checkAssetbundle);if (Array.IndexOf(allDependencies, curAssetbundleName) >= 0){count++;if (count >= 2){break;}}}if (count >= 2){SetAssetBundleResident(curAssetbundleName, true);}}Debug.Log(string.Format("AssetBundleResident Initialize use {0}ms", (DateTime.Now - start).Milliseconds));yield break;}public IEnumerator Cleanup(){
#if UNITY_EDITORif (AssetBundleConfig.IsEditorMode){yield break;}
#endif// 等待所有请求完成// 要是不等待Unity很多版本都有各种Bugyield return new WaitUntil(() =>{return prosessingWebRequester.Count == 0;});yield return new WaitUntil(() =>{return prosessingAssetBundleAsyncLoader.Count == 0;});yield return new WaitUntil(() =>{return prosessingAssetAsyncLoader.Count == 0;});ClearAssetsCache();foreach (var assetbunle in assetbundlesCaching.Values){if (assetbunle != null){assetbunle.Unload(false);}}assetbundlesCaching.Clear();assetbundleRefCount.Clear();assetbundleResident.Clear();yield break;}public Manifest curManifest{get{return manifest;}}public string DownloadUrl{get{// return Setting.SERVER_RESOURCE_ADDR;return null;}}public void SetAssetBundleResident(string assetbundleName, bool resident){Debug.Log("SetAssetBundleResident : " + assetbundleName + ", " + resident.ToString());bool exist = assetbundleResident.Contains(assetbundleName);if (resident && !exist){assetbundleResident.Add(assetbundleName);}else if(!resident && exist){assetbundleResident.Remove(assetbundleName);}}public bool IsAssetBundleResident(string assebundleName){return assetbundleResident.Contains(assebundleName);}public bool IsAssetBundleLoaded(string assetbundleName){return assetbundlesCaching.ContainsKey(assetbundleName);}public AssetBundle GetAssetBundleCache(string assetbundleName){AssetBundle target = null;assetbundlesCaching.TryGetValue(assetbundleName, out target);return target;}protected void RemoveAssetBundleCache(string assetbundleName){assetbundlesCaching.Remove(assetbundleName);}protected void AddAssetBundleCache(string assetbundleName, AssetBundle assetbundle){assetbundlesCaching[assetbundleName] = assetbundle;}public bool IsAssetLoaded(string assetName){return assetsCaching.ContainsKey(assetName);}public UnityEngine.Object GetAssetCache(string assetName){UnityEngine.Object target = null;assetsCaching.TryGetValue(assetName, out target);return target;}public void AddAssetCache(string assetName, UnityEngine.Object asset){assetsCaching[assetName] = asset;}public void AddAssetbundleAssetsCache(string assetbundleName){
#if UNITY_EDITORif (AssetBundleConfig.IsEditorMode){return;}
#endifif (!IsAssetBundleLoaded(assetbundleName)){Debug.LogError("Try to add assets cache from unloaded assetbundle : " + assetbundleName);return;}var curAssetbundle = GetAssetBundleCache(assetbundleName);var allAssetNames = assetsPathMapping.GetAllAssetNames(assetbundleName);for (int i = 0; i < allAssetNames.Count; i++){var assetName = allAssetNames[i];if (IsAssetLoaded(assetName)){continue;}var assetPath = AssetBundleUtility.PackagePathToAssetsPath(assetName);var asset = curAssetbundle == null ? null : curAssetbundle.LoadAsset(assetPath);AddAssetCache(assetName, asset);#if UNITY_EDITOR// 说明:在Editor模拟时,Shader要重新指定var go = asset as GameObject;if (go != null){var renderers = go.GetComponentsInChildren<Renderer>();for (int j = 0; j < renderers.Length; j++){var mat = renderers[j].sharedMaterial;if (mat == null){continue;}var shader = mat.shader;if (shader != null){var shaderName = shader.name;mat.shader = Shader.Find(shaderName);}}}
#endif}}public void ClearAssetsCache(){assetsCaching.Clear();}public ResourceWebRequester GetAssetBundleAsyncCreater(string assetbundleName){ResourceWebRequester creater = null;webRequesting.TryGetValue(assetbundleName, out creater);return creater;}protected int GetReferenceCount(string assetbundleName){int count = 0;assetbundleRefCount.TryGetValue(assetbundleName, out count);return count;}protected int IncreaseReferenceCount(string assetbundleName){int count = 0;assetbundleRefCount.TryGetValue(assetbundleName, out count);count++;assetbundleRefCount[assetbundleName] = count;return count;}protected int DecreaseReferenceCount(string assetbundleName){int count = 0;assetbundleRefCount.TryGetValue(assetbundleName, out count);count--;assetbundleRefCount[assetbundleName] = count;return count;}protected bool CreateAssetBundleAsync(string assetbundleName){if (IsAssetBundleLoaded(assetbundleName) || webRequesting.ContainsKey(assetbundleName)){return false;}var creater = ResourceWebRequester.Get();var url = AssetBundleUtility.GetAssetBundleFileUrl(assetbundleName);creater.Init(assetbundleName, url);webRequesting.Add(assetbundleName, creater);webRequesterQueue.Enqueue(creater);// 创建器持有的引用:创建器对每个ab来说是全局唯一的IncreaseReferenceCount(assetbundleName);return true;}// 异步请求Assetbundle资源,AB是否缓存取决于是否设置为常驻包,Assets一律缓存,处理依赖public BaseAssetBundleAsyncLoader LoadAssetBundleAsync(string assetbundleName){
#if UNITY_EDITORif (AssetBundleConfig.IsEditorMode){return new EditorAssetBundleAsyncLoader(assetbundleName);}
#endifvar loader = AssetBundleAsyncLoader.Get();prosessingAssetBundleAsyncLoader.Add(loader);if (manifest != null){string[] dependancies = manifest.GetAllDependencies(assetbundleName);for (int i = 0; i < dependancies.Length; i++){var dependance = dependancies[i];if (!string.IsNullOrEmpty(dependance) && dependance != assetbundleName){CreateAssetBundleAsync(dependance);// ab缓存对依赖持有的引用IncreaseReferenceCount(dependance);}}loader.Init(assetbundleName, dependancies);}else{loader.Init(assetbundleName, null);}CreateAssetBundleAsync(assetbundleName);// 加载器持有的引用:同一个ab能同时存在多个加载器,等待ab创建器完成IncreaseReferenceCount(assetbundleName);return loader;}// 从服务器下载网页内容,需提供完整urlpublic ResourceWebRequester DownloadWebResourceAsync(string url){var creater = ResourceWebRequester.Get();creater.Init(url, url, true);webRequesting.Add(url, creater);webRequesterQueue.Enqueue(creater);return creater;}// 从资源服务器下载非Assetbundle资源public ResourceWebRequester DownloadAssetFileAsync(string filePath){if (string.IsNullOrEmpty(DownloadUrl)){Debug.LogError("You should set download url first!!!");return null;}var creater = ResourceWebRequester.Get();var url = DownloadUrl + filePath;creater.Init(filePath, url, true);webRequesting.Add(filePath, creater);webRequesterQueue.Enqueue(creater);return creater;}// 从资源服务器下载Assetbundle资源,不缓存,无依赖public ResourceWebRequester DownloadAssetBundleAsync(string filePath){// 如果ResourceWebRequester升级到使用UnityWebRequester,那么下载AB和下载普通资源需要两个不同的DownLoadHandler// 兼容升级的可能性,这里也做一下区分return DownloadAssetFileAsync(filePath);}// 本地异步请求非Assetbundle资源public ResourceWebRequester RequestAssetFileAsync(string filePath, bool streamingAssetsOnly = true){var creater = ResourceWebRequester.Get();string url = null;if (streamingAssetsOnly){url = AssetBundleUtility.GetStreamingAssetsFilePath(filePath);}else{url = AssetBundleUtility.GetAssetBundleFileUrl(filePath);}creater.Init(filePath, url, true);webRequesting.Add(filePath, creater);webRequesterQueue.Enqueue(creater);return creater;}// 本地异步请求Assetbundle资源,不缓存,无依赖public ResourceWebRequester RequestAssetBundleAsync(string assetbundleName){var creater = ResourceWebRequester.Get();var url = AssetBundleUtility.GetAssetBundleFileUrl(assetbundleName);creater.Init(assetbundleName, url, true);webRequesting.Add(assetbundleName, creater);webRequesterQueue.Enqueue(creater);return creater;}public void UnloadAssetBundleDependencies(string assetbundleName){if (manifest != null){string[] dependancies = manifest.GetAllDependencies(assetbundleName);for (int i = 0; i < dependancies.Length; i++){var dependance = dependancies[i];if (!string.IsNullOrEmpty(dependance) && dependance != assetbundleName){UnloadAssetBundle(dependance);}}}}protected bool UnloadAssetBundle(string assetbundleName, bool unloadResident = false, bool unloadAllLoadedObjects = false){int count = GetReferenceCount(assetbundleName);if (count <= 0){return false;}count = DecreaseReferenceCount(assetbundleName);if (count > 0){return false;}var assetbundle = GetAssetBundleCache(assetbundleName);var isResident = IsAssetBundleResident(assetbundleName);if (assetbundle != null){if (!isResident || isResident && unloadResident){assetbundle.Unload(unloadAllLoadedObjects);RemoveAssetBundleCache(assetbundleName);UnloadAssetBundleDependencies(assetbundleName);return true;}}return false;}public bool TryUnloadAssetBundle(string assetbundleName, bool unloadAllLoadedObjects = false){int count = GetReferenceCount(assetbundleName);if (count > 0){return false;}return UnloadAssetBundle(assetbundleName, true, unloadAllLoadedObjects);}public void UnloadUnusedAssetBundles(bool unloadResident = false, bool unloadAllLoadedObjects = false){int unloadCount = 0;bool hasDoUnload = false;do{hasDoUnload = false;var iter = assetbundleRefCount.GetEnumerator();while (iter.MoveNext()){var assetbundleName = iter.Current.Key;var referenceCount = iter.Current.Value;if (referenceCount <= 0){var result = UnloadAssetBundle(assetbundleName, unloadResident, unloadAllLoadedObjects);if (result){unloadCount++;hasDoUnload = true;}}}} while (hasDoUnload);}public bool MapAssetPath(string assetPath, out string assetbundleName, out string assetName){return assetsPathMapping.MapAssetPath(assetPath, out assetbundleName, out assetName);}public BaseAssetAsyncLoader LoadAssetAsync(string assetPath, System.Type assetType){
#if UNITY_EDITORif (AssetBundleConfig.IsEditorMode){string path = AssetBundleUtility.PackagePathToAssetsPath(assetPath); UnityEngine.Object target = AssetDatabase.LoadAssetAtPath(path, assetType);return new EditorAssetAsyncLoader(target);}
#endifstring assetbundleName = null;string assetName = null;bool status = MapAssetPath(assetPath, out assetbundleName, out assetName);if (!status){Debug.LogError("No assetbundle at asset path :" + assetPath);return null;}var loader = AssetAsyncLoader.Get();prosessingAssetAsyncLoader.Add(loader);if (IsAssetLoaded(assetName)){loader.Init(assetName, GetAssetCache(assetName));return loader;}else{var assetbundleLoader = LoadAssetBundleAsync(assetbundleName);loader.Init(assetName, assetbundleLoader);return loader;}}void Update(){OnProsessingWebRequester();OnProsessingAssetBundleAsyncLoader();OnProsessingAssetAsyncLoader();}void OnProsessingWebRequester(){for (int i = prosessingWebRequester.Count - 1; i >= 0; i--){var creater = prosessingWebRequester[i];creater.Update();if (creater.IsDone()){prosessingWebRequester.RemoveAt(i);webRequesting.Remove(creater.assetbundleName);UnloadAssetBundle(creater.assetbundleName);if (creater.noCache){return;}// 说明:有错误也缓存下来,只不过资源为空// 1、避免再次错误加载// 2、如果不存下来加载器将无法判断什么时候结束AddAssetBundleCache(creater.assetbundleName, creater.assetbundle);creater.Dispose();}}int slotCount = prosessingWebRequester.Count;while (slotCount < MAX_ASSETBUNDLE_CREATE_NUM && webRequesterQueue.Count > 0){var creater = webRequesterQueue.Dequeue();creater.Start();prosessingWebRequester.Add(creater);slotCount++;}}void OnProsessingAssetBundleAsyncLoader(){for (int i = prosessingAssetBundleAsyncLoader.Count - 1; i >= 0; i--){var loader = prosessingAssetBundleAsyncLoader[i];loader.Update();if (loader.IsDone()){UnloadAssetBundle(loader.assetbundleName);prosessingAssetBundleAsyncLoader.RemoveAt(i);}}}void OnProsessingAssetAsyncLoader(){for (int i = prosessingAssetAsyncLoader.Count - 1; i >= 0; i--){var loader = prosessingAssetAsyncLoader[i];loader.Update();if (loader.IsDone()){prosessingAssetAsyncLoader.RemoveAt(i);}}}#if UNITY_EDITOR[BlackList]public HashSet<string> GetAssetbundleResident(){return assetbundleResident;}[BlackList]public ICollection<string> GetAssetbundleCaching(){return assetbundlesCaching.Keys;}[BlackList]public Dictionary<string, ResourceWebRequester> GetWebRequesting(){return webRequesting;}[BlackList]public Queue<ResourceWebRequester> GetWebRequestQueue(){return webRequesterQueue;}[BlackList]public List<ResourceWebRequester> GetProsessingWebRequester(){return prosessingWebRequester;}[BlackList]public List<AssetBundleAsyncLoader> GetProsessingAssetBundleAsyncLoader(){return prosessingAssetBundleAsyncLoader;}[BlackList]public List<AssetAsyncLoader> GetProsessingAssetAsyncLoader(){return prosessingAssetAsyncLoader;}[BlackList]public string GetAssetBundleName(string assetName){return assetsPathMapping.GetAssetBundleName(assetName);}[BlackList]public int GetAssetCachingCount(){return assetsCaching.Count;}[BlackList]public Dictionary<string, List<string>> GetAssetCaching(){var assetbundleDic = new Dictionary<string, List<string>>();List<string> assetNameList = null;var iter = assetsCaching.GetEnumerator();while (iter.MoveNext()){var assetName = iter.Current.Key;var assetbundleName = assetsPathMapping.GetAssetBundleName(assetName);assetbundleDic.TryGetValue(assetbundleName, out assetNameList);if (assetNameList == null){assetNameList = new List<string>();}assetNameList.Add(assetName);assetbundleDic[assetbundleName] = assetNameList;}return assetbundleDic;}[BlackList]public int GetAssetbundleRefrenceCount(string assetbundleName){return GetReferenceCount(assetbundleName);}[BlackList]public int GetAssetbundleDependenciesCount(string assetbundleName){string[] dependancies = manifest.GetAllDependencies(assetbundleName);int count = 0;for (int i = 0; i < dependancies.Length; i++){var cur = dependancies[i];if (!string.IsNullOrEmpty(cur) && cur != assetbundleName){count++;}}return count;}[BlackList]public List<string> GetAssetBundleRefrences(string assetbundleName){List<string> refrences = new List<string>();var cachingIter = assetbundlesCaching.GetEnumerator();while (cachingIter.MoveNext()){var curAssetbundleName = cachingIter.Current.Key;if (curAssetbundleName == assetbundleName){continue;}string[] dependancies = manifest.GetAllDependencies(curAssetbundleName);for (int i = 0; i < dependancies.Length; i++){var dependance = dependancies[i];if (dependance == assetbundleName){refrences.Add(curAssetbundleName);}}}var requestingIter = webRequesting.GetEnumerator();while (requestingIter.MoveNext()){var curAssetbundleName = requestingIter.Current.Key;if (curAssetbundleName == assetbundleName){continue;}string[] dependancies = manifest.GetAllDependencies(curAssetbundleName);for (int i = 0; i < dependancies.Length; i++){var dependance = dependancies[i];if (dependance == assetbundleName){refrences.Add(curAssetbundleName);}}}return refrences;}[BlackList]public List<string> GetWebRequesterRefrences(string assetbundleName){List<string> refrences = new List<string>();var iter = webRequesting.GetEnumerator();while (iter.MoveNext()){var curAssetbundleName = iter.Current.Key;var webRequster = iter.Current.Value;if (curAssetbundleName == assetbundleName){refrences.Add(webRequster.Sequence.ToString());continue;}}return refrences;}[BlackList]public List<string> GetAssetBundleLoaderRefrences(string assetbundleName){List<string> refrences = new List<string>();var iter = prosessingAssetBundleAsyncLoader.GetEnumerator();while (iter.MoveNext()){var curAssetbundleName = iter.Current.assetbundleName;var curLoader = iter.Current;if (curAssetbundleName == assetbundleName){refrences.Add(curLoader.Sequence.ToString());}}return refrences;}
#endif}
}
Lua GC
Lua有自己的垃圾回收系统,简称GC。我们不需要自己编写GC,但是要设定好什么条件下启动GC,一般来说,可以隔100帧启动一次。
代码:
if(Time.frameCount % 100 == 0){this.luaEnv.Tick();
}
AssetBundle菜单工具
定义菜单宏
const string kSimulateMode = "AssetBundles/Switch Model/Simulate Mode";
const string kEditorMode = "AssetBundles/Switch Model/Editor Mode";
const string kToolRunAllCheckers = "AssetBundles/Run All Checkers";
const string kToolBuildForCurrentSetting = "AssetBundles/Build For Current Setting";
const string kToolsCopyAssetbundles = "AssetBundles/Copy To StreamingAssets";
const string kToolsOpenOutput = "AssetBundles/Open Current Output";
const string kToolsOpenPerisitentData = "AssetBundles/Open PersistentData";
const string kToolsClearOutput = "AssetBundles/Clear Current Output";
const string kToolsClearStreamingAssets = "AssetBundles/Clear StreamingAssets";
const string kToolsClearPersistentAssets = "AssetBundles/Clear PersistentData";const string kCreateAssetbundleForCurrent = "Assets/AssetBundles/Create Assetbundle For Current &#z";
const string kCreateAssetbundleForChildren = "Assets/AssetBundles/Create Assetbundle For Children &#x";
const string kAssetDependencis = "Assets/AssetBundles/Asset Dependencis &#h";
const string kAssetbundleAllDependencis = "Assets/AssetBundles/Assetbundle All Dependencis &#j";
const string kAssetbundleDirectDependencis = "Assets/AssetBundles/Assetbundle Direct Dependencis &#k";
复制lua文件
.lua文件没有办法被Xlua打成ab包,因此我们要把这些文件加上.bytes结尾,这样能打成二进制包,并且复制到对应的路径下
using UnityEngine;
using UnityEditor;
using System.IO;
using Debug = UnityEngine.Debug;
using AssetBundles;[InitializeOnLoad]
public static class XLuaMenu
{[MenuItem("AssetBundles/Copy Lua Files To AssetsPackage", false, 51)]public static void CopyLuaFilesToAssetsPackage(){// Application.dataPath ---> Assets所在目录 + AssetsPackagestring destination = Path.Combine(Application.dataPath, AssetBundleConfig.AssetsFolderName);// string destination = Path.Combine(Application.dataPath, "AssetsPackage");// Assets/AssetsPackage/Luadestination = Path.Combine(destination, xLuaMgr.luaAssetbundleAssetName);Debug.Log(destination);// Assets/LuaScripts/string source = Path.Combine(Application.dataPath, xLuaMgr.luaScriptsFolder);GameUtility.SafeDeleteDir(destination); // 删除目标路径下所有得文件FileUtil.CopyFileOrDirectoryFollowSymlinks(source, destination); // // 将不是.lua 文件名字的文件,全部都获取出来;var notLuaFiles = GameUtility.GetSpecifyFilesInFolder(destination, new string[] { ".lua" }, true);if (notLuaFiles != null && notLuaFiles.Length > 0){for (int i = 0; i < notLuaFiles.Length; i++){GameUtility.SafeDeleteFile(notLuaFiles[i]); // .meta}}// 找出所有的.lua文件;var luaFiles = GameUtility.GetSpecifyFilesInFolder(destination, new string[] { ".lua" }, false);if (luaFiles != null && luaFiles.Length > 0){// 重新命名文件,加上一个.bytes的后缀; .lua.bytesfor (int i = 0; i < luaFiles.Length; i++){GameUtility.SafeRenameFile(luaFiles[i], luaFiles[i] + ".bytes");}}AssetDatabase.Refresh();Debug.Log("Copy lua files over");}
}
切换代码启动模式
分为两种模式:
- 编辑器模式,直接从代码lua脚本读取
- 模拟模式,从打包的AssetBundle读取资源
- 发布模式,从打包的AssetBundle读取资源
// 点击编辑器模式按钮[MenuItem(kEditorMode, false)]
public static void ToggleEditorMode()
{if (AssetBundleConfig.IsSimulateMode){AssetBundleConfig.IsEditorMode = true; // set里面,保存到EditorPrefs里面;LaunchAssetBundleServer.CheckAndDoRunning();}
}// 如果是true, 就会打一个勾;
[MenuItem(kEditorMode, true)]
public static bool ToggleEditorModeValidate()
{Menu.SetChecked(kEditorMode, AssetBundleConfig.IsEditorMode);return true;
}
// 点击模拟模式按钮
[MenuItem(kSimulateMode)]
public static void ToggleSimulateMode()
{if (AssetBundleConfig.IsEditorMode){AssetBundleConfig.IsSimulateMode = true;CheckSimulateModelEnv();LaunchAssetBundleServer.CheckAndDoRunning();}}[MenuItem(kSimulateMode, true)]
public static bool ToggleSimulateModeValidate()
{Menu.SetChecked(kSimulateMode, AssetBundleConfig.IsSimulateMode);return true;
}
在配置文件AssetBundleConfig中
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
using System.IO;
#endif/// <summary>
/// added by wsh @ 2017.12.25
/// 注意:
/// 1、所有ab路径中目录、文件名不能以下划线打头,否则出包时StreamingAssets中的资源不能打到真机上,很坑爹
/// </summary>namespace AssetBundles
{public class AssetBundleConfig{public const string localSvrAppPath = "Editor/AssetBundle/LocalServer/AssetBundleServer.exe";public const string AssetBundlesFolderName = "AssetBundles";public const string AssetBundleSuffix = ".assetbundle";public const string AssetsFolderName = "AssetsPackage";public const string ChannelFolderName = "Channel";public const string AssetsPathMapFileName = "AssetsMap.bytes";public const string VariantsMapFileName = "VariantsMap.bytes";public const string AssetBundleServerUrlFileName = "AssetBundleServerUrl.txt";public const string VariantMapParttren = "Variant";public const string CommonMapPattren = ",";#if UNITY_EDITORpublic static string AssetBundlesBuildOutputPath{get{string outputPath = Path.Combine(System.Environment.CurrentDirectory, AssetBundlesFolderName);GameUtility.CheckDirAndCreateWhenNeeded(outputPath);return outputPath;}}public static string LocalSvrAppPath{get{return Path.Combine(Application.dataPath, localSvrAppPath);}}public static string LocalSvrAppWorkPath{get{return AssetBundlesBuildOutputPath;}}private static int mIsEditorMode = -1;private const string kIsEditorMode = "IsEditorMode";private static int mIsSimulateMode = -1;private const string kIsSimulateMode = "IsSimulateMode";public static bool IsEditorMode{get{if (mIsEditorMode == -1){if (!EditorPrefs.HasKey(kIsEditorMode)){EditorPrefs.SetBool(kIsEditorMode, false);}mIsEditorMode = EditorPrefs.GetBool(kIsEditorMode, true) ? 1 : 0;}return mIsEditorMode != 0;}set{int newValue = value ? 1 : 0;if (newValue != mIsEditorMode){mIsEditorMode = newValue;EditorPrefs.SetBool(kIsEditorMode, value);if (value){IsSimulateMode = false;}}}}public static bool IsSimulateMode{get{if (mIsSimulateMode == -1){if (!EditorPrefs.HasKey(kIsSimulateMode)){EditorPrefs.SetBool(kIsSimulateMode, true);}mIsSimulateMode = EditorPrefs.GetBool(kIsSimulateMode, true) ? 1 : 0;}return mIsSimulateMode != 0;}set{int newValue = value ? 1 : 0;if (newValue != mIsSimulateMode){mIsSimulateMode = newValue;EditorPrefs.SetBool(kIsSimulateMode, value);if (value){IsEditorMode = false;}}}}
#endif}
}
渠道和版本管理、打包工具
渠道配置文件:
// 目前就设置了Test渠道
namespace GameChannel
{public enum ChannelType{Test,}
}
打包设置:
public class PackageTool : EditorWindow
{static private BuildTarget buildTarget = EditorUserBuildSettings.activeBuildTarget;static private ChannelType channelType = ChannelType.Test;static private string resVersion = "1.0.0";static PackageTool(){// EditorPrefs--->ChannelName--->"字符串"--->解析出来时哪种渠道枚举,如果没有就用Test;channelType = PackageUtils.GetCurSelectedChannel();}//打包工具,显示OnGUI中的界面// Tools/Package;[MenuItem("Tools/Package", false, 0)]static void Init() {EditorWindow.GetWindow(typeof(PackageTool));}void OnGUI(){GUILayout.BeginVertical();GUILayout.Space(10);// 目标平台;buildTarget = (BuildTarget)EditorGUILayout.EnumPopup("Build Target : ", buildTarget);GUILayout.Space(5);// 渠道;channelType = (ChannelType)EditorGUILayout.EnumPopup("Build Channel : ", channelType);GUILayout.EndVertical();if (GUI.changed){// 如果渠道修改了,我就保存这个渠道;PackageUtils.SaveCurSelectedChannel(channelType);}DrawConfigGUI();DrawAssetBundlesGUI();DrawXLuaGUI();DrawBuildPlayerGUI();} }
PackageUtils.GetCurSelectedChannel:
public static ChannelType GetCurSelectedChannel(){ChannelType channelType = ChannelType.Test;string channelName = EditorPrefs.GetString("ChannelName");if (Enum.IsDefined(typeof(ChannelType), channelName)){channelType = (ChannelType)Enum.Parse(typeof(ChannelType), channelName);}else{EditorPrefs.SetString("ChannelName", ChannelType.Test.ToString());}return channelType;}
打包的时候拥有app版本和资源版本,两者可能不同。保存为app_version.bytes,res_version.bytes文件
AssetBundle打包操作
流程
打包预备:所有生成出来的lua脚本都以.lua.bytes结尾的文件形式存放在AssetsPackage/lua中,其他资源也存放在AssetsPackage的子文件夹中,例如AssetsPackage/Sound。AssetsPackage的子文件夹在未选择打包的时候,其Inspector中会存在Create AssetBundle Dispatcher按钮(由代码决定),点击之后可以选择四种打包模式。选择的打包模式绘制在Editor/Database/AssetsPackage中生成对应的.asset配置文件,用于打包,点击自定义菜单AssetBundles/Build For Current Settings进行打包,这时候在AssetsPackage目录下将生成AssetsMap和VariantMap文件。
Unity ScriptableObject
一个C#对象继承自ScriptableObject,则会将对应的数据写入.asset文件中
这个脚本定义了基本的数据结构
using UnityEngine;
using System.Collections.Generic;
namespace AssetBundles
{public class AssetBundleDispatcherConfig : ScriptableObject{public string PackagePath = string.Empty;public AssetBundleDispatcherFilterType Type = AssetBundleDispatcherFilterType.Root;public List<AssetBundleCheckerFilter> CheckerFilters = new List<AssetBundleCheckerFilter>();// 序列化用的,AssetBundleCheckerFilter的字段拆成两个数组[SerializeField]string[] RelativePaths = null;[SerializeField]string[] ObjectFilters = null;public AssetBundleDispatcherConfig(){Load();}public void Load(){CheckerFilters.Clear();if (RelativePaths != null && RelativePaths.Length > 0){for (int i = 0; i < RelativePaths.Length; i++){CheckerFilters.Add(new AssetBundleCheckerFilter(RelativePaths[i], ObjectFilters[i]));}}}public void Apply(){if (CheckerFilters.Count <= 0){RelativePaths = null;ObjectFilters = null;return;}RelativePaths = new string[CheckerFilters.Count];ObjectFilters = new string[CheckerFilters.Count];for (int i = 0; i < CheckerFilters.Count; i++){RelativePaths[i] = CheckerFilters[i].RelativePath;ObjectFilters[i] = CheckerFilters[i].ObjectFilter;}}}
}
这个脚本负责对应的点击GUI的操作:
注意这里应用了编辑器扩展,给特定文件夹的Inspector下添加了按钮:
每次属性检查器刷新的时候会调用OnInspectorGUI
每次点击文件夹的时候这个脚本会调用OnEnable
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Collections.Generic;/// <summary>
/// added by wsh @ 2018.01.06
/// 说明:Assetbundle分发器Inspector,为其提供可视化的编辑界面
/// TODO:
/// 1、还未完成,目前只是做了基本的配置功能
/// </summary>namespace AssetBundles
{[CustomEditor(typeof(DefaultAsset), true)]public class AssetBundleDispatcherInspector : Editor{AssetBundleDispatcherConfig dispatcherConfig = null;string packagePath = null;string targetAssetPath = null;string databaseAssetPath = null;static Dictionary<string, bool> inspectorSate = new Dictionary<string, bool>();AssetBundleDispatcherFilterType filterType = AssetBundleDispatcherFilterType.Root;bool configChanged = false;void OnEnable(){Initialize();}// 每次选中这个文件夹的时候,我们会调用Initialize;void Initialize(){configChanged = false;filterType = AssetBundleDispatcherFilterType.Root; // 默认的打包方式;targetAssetPath = AssetDatabase.GetAssetPath(target); // 获取我们选的当前的路径;if (!AssetBundleUtility.IsPackagePath(targetAssetPath)) // 这个路径是否在AssetsPackages路径下;{return;}// Assets/AssetsPackage/Lua ---> pakcage path LuapackagePath = AssetBundleUtility.AssetsPathToPackagePath(targetAssetPath); // packagePath// 文件对应的数据库目录下 xxx.asset文件;databaseAssetPath = AssetBundleInspectorUtils.AssetPathToDatabasePath(targetAssetPath);// 加载数据库文件 例如: Lua.assetdispatcherConfig = AssetDatabase.LoadAssetAtPath<AssetBundleDispatcherConfig>(databaseAssetPath);if (dispatcherConfig != null) // 如果有,就不为null, 之前已经创建,吧数据加载进来;{dispatcherConfig.Load();filterType = dispatcherConfig.Type;}}// 如果读不到数据库文件配置,那么这个时候,走这里绘制一个创建按钮;void DrawCreateAssetBundleDispatcher(){if (GUILayout.Button("Create AssetBundle Dispatcher")){// 创建数据库文件路径;var dir = Path.GetDirectoryName(databaseAssetPath);GameUtility.CheckDirAndCreateWhenNeeded(dir); // 是否存在,如果不存在,就创建一个;// 创建一个ScriptableObject 对象; --->构造函数;, 初始化数据;// 所以你创建完以后,默认的初始值, 对象里面初始值决定的;var instance = CreateInstance<AssetBundleDispatcherConfig>();AssetDatabase.CreateAsset(instance, databaseAssetPath); // 将这个对象实例--->创建到.asset文件里面;AssetDatabase.Refresh();// 重新同步一下到当前的对象里面;Initialize();// 调用Repaint时候----》 引发 OnInspectorGUIRepaint();}}void DrawFilterItem(AssetBundleCheckerFilter checkerFilter){GUILayout.BeginVertical(); var relativePath = GUILayoutUtils.DrawInputField("RelativePath:", checkerFilter.RelativePath, 300f, 80f);var objectFilter = GUILayoutUtils.DrawInputField("ObjectFilter:", checkerFilter.ObjectFilter, 300f, 80f);if (relativePath != checkerFilter.RelativePath){configChanged = true;checkerFilter.RelativePath = relativePath;}if (objectFilter != checkerFilter.ObjectFilter){configChanged = true;checkerFilter.ObjectFilter = objectFilter;}GUILayout.EndVertical();}void DrawFilterTypesList(List<AssetBundleCheckerFilter> checkerFilters){GUILayout.BeginVertical(EditorStyles.textField);GUILayout.Space(3);EditorGUILayout.Separator();for (int i = 0; i < checkerFilters.Count; i++){var curFilter = checkerFilters[i];var relativePath = string.IsNullOrEmpty(curFilter.RelativePath) ? "root" : curFilter.RelativePath;var objectFilter = string.IsNullOrEmpty(curFilter.ObjectFilter) ? "all" : curFilter.ObjectFilter;var filterType = relativePath + ": <" + objectFilter + ">";var stateKey = "CheckerFilters" + i.ToString();if (GUILayoutUtils.DrawRemovableSubHeader(1, filterType, inspectorSate, stateKey, () =>{configChanged = true;checkerFilters.RemoveAt(i);i--;})){DrawFilterItem(curFilter);}EditorGUILayout.Separator();}if (GUILayout.Button("Add")){configChanged = true;checkerFilters.Add(new AssetBundleCheckerFilter("", "t:prefab"));}EditorGUILayout.Separator();GUILayout.Space(3);GUILayout.EndVertical();}void DrawAssetDispatcherConfig(){GUILayoutUtils.BeginContents(false);GUILayoutUtils.DrawProperty("Path:", AssetBundleUtility.AssetsPathToPackagePath(targetAssetPath), 300f, 80f);EditorGUILayout.BeginHorizontal();EditorGUILayout.LabelField("FilterType:", GUILayout.MaxWidth(80f));// 打包的模式var selectType = (AssetBundleDispatcherFilterType)EditorGUILayout.EnumPopup(filterType);if (selectType != filterType){filterType = selectType;configChanged = true;}EditorGUILayout.EndHorizontal();EditorGUILayout.Separator();var filtersCount = dispatcherConfig.CheckerFilters.Count;if (GUILayoutUtils.DrawSubHeader(0, "CheckerFilters:", inspectorSate, "CheckerFilters", filtersCount.ToString())){DrawFilterTypesList(dispatcherConfig.CheckerFilters);}Color color = GUI.color;if (configChanged){GUI.color = color * new Color(1, 1, 0.5f);}EditorGUILayout.Separator();GUILayout.BeginHorizontal();if (GUILayout.Button("Apply")) // 同步到数据库;{Apply();}GUI.color = new Color(1, 0.5f, 0.5f);if (GUILayout.Button("Remove")) // 删除数据库文件;{Remove();}GUI.color = color;GUILayout.EndHorizontal();EditorGUILayout.Separator();GUILayoutUtils.EndContents(false);}void Apply(){dispatcherConfig.PackagePath = packagePath;dispatcherConfig.Type = filterType;dispatcherConfig.Apply();EditorUtility.SetDirty(dispatcherConfig);AssetDatabase.SaveAssets();// 刷新编辑器;Initialize();Repaint();configChanged = false;}void Remove(){bool checkRemove = EditorUtility.DisplayDialog("Remove Warning","Sure to remove the AssetBundle dispatcher ?","Confirm", "Cancel");if (!checkRemove){return;}// 删除数据库文件;databaseAssetPathGameUtility.SafeDeleteFile(databaseAssetPath);AssetDatabase.Refresh();// Initialize(); Repaint(); // 调用一下OnIntxxGUI() ---> create 按钮configChanged = false;}void DrawAssetBundleDispatcherInspector(){// 创建一个Layout面板出来;if (GUILayoutUtils.DrawHeader("AssetBundle Dispatcher : ", inspectorSate, "DispatcherConfig", true, false)){DrawAssetDispatcherConfig();}}public override void OnInspectorGUI(){base.OnInspectorGUI();// 检查一下,这个路径,是否为AssetsPackageif (!AssetBundleInspectorUtils.CheckMaybeAssetBundleAsset(targetAssetPath)) { // 其它文件夹下路径, return;return;}GUI.enabled = true;if (dispatcherConfig == null) // 数据库配置为null;{DrawCreateAssetBundleDispatcher(); // 创建按钮, 视图}else // 绘制编辑按钮;{DrawAssetBundleDispatcherInspector(); // 编辑配置模式的一个视图}}void OnDisable(){if (configChanged){bool checkApply = EditorUtility.DisplayDialog("Modify Warning","You have modified the AssetBundle dispatcher setting, Apply it ?","Confirm", "Cancel");if (checkApply){Apply();}}dispatcherConfig = null;inspectorSate.Clear();}}
}
对应的数据库创建完成后,可以点击RunAllCheck来检查
正式打包
[MenuItem(kToolBuildForCurrentSetting, false, 1100)]
static public void ToolBuildForCurrentSetting()
{var buildTargetName = PackageUtils.GetCurPlatformName();var channelName = PackageUtils.GetCurSelectedChannel().ToString();bool checkCopy = EditorUtility.DisplayDialog("Build AssetBundles Warning",string.Format("Build AssetBundles for : \n\nplatform : {0} \nchannel : {1} \n\nContinue ?", buildTargetName, channelName),"Confirm", "Cancel");if (!checkCopy){return;}PackageTool.BuildAssetBundlesForCurrentChannel();
}
PackageTool
public static void BuildAssetBundlesForCurrentChannel()
{var start = DateTime.Now;BuildPlayer.BuildAssetBundles(buildTarget, channelType.ToString());var buildTargetName = PackageUtils.GetPlatformName(buildTarget);EditorUtility.DisplayDialog("Success", string.Format("Build AssetBundles for : \n\nplatform : {0} \nchannel : {1} \n\ndone! use {2}s", buildTargetName, channelType, (DateTime.Now - start).TotalSeconds), "Confirm");
}
BuildPlayer
public static void BuildAssetBundles(BuildTarget buildTarget, string channelName)
{var start = DateTime.Now;CheckAssetBundles.Run();Debug.Log("Finished CheckAssetBundles.Run! use " + (DateTime.Now - start).TotalSeconds + "s");start = DateTime.Now;CheckAssetBundles.SwitchChannel(channelName.ToString());Debug.Log("Finished CheckAssetBundles.SwitchChannel! use " + (DateTime.Now - start).TotalSeconds + "s");start = DateTime.Now;InnerBuildAssetBundles(buildTarget, channelName, true);Debug.Log("Finished InnerBuildAssetBundles! use " + (DateTime.Now - start).TotalSeconds + "s");var targetName = PackageUtils.GetPlatformName(buildTarget);Debug.Log(string.Format("Build assetbundles for platform : {0} and channel : {1} done!", targetName, channelName));
}private static void InnerBuildAssetBundles(BuildTarget buildTarget, string channelName, bool writeConfig)
{BuildAssetBundleOptions buildOption = BuildAssetBundleOptions.IgnoreTypeTreeChanges | BuildAssetBundleOptions.DeterministicAssetBundle;var outputPath = PackageUtils.GetBuildPlatformOutputPath(buildTarget, channelName);// 正式打包AssetBundleManifest manifest = BuildPipeline.BuildAssetBundles(outputPath, buildOption, buildTarget);if (manifest != null && writeConfig){// 生成信息文件AssetsPathMappingEditor.BuildPathMapping(manifest);VariantMappingEditor.BuildVariantMapping(manifest);// 把这两个文件也打包进去BuildPipeline.BuildAssetBundles(outputPath, buildOption, buildTarget);}// 写包的名字和大小WritePackageNameFile(buildTarget, channelName);WriteAssetBundleSize(buildTarget, channelName);AssetDatabase.Refresh();
}
资源加载
我们需要编写一个AssetBundleManager脚本作为单例,在一开始就加载进来。
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using XLua;
using System;
#if UNITY_EDITOR
using UnityEditor;
#endif/// <summary>
/// added by wsh @ 2017-12-21
/// 功能:assetbundle管理类,为外部提供统一的资源加载界面、协调Assetbundle各个子系统的运行
/// 注意:
/// 1、抛弃Resources目录的使用,官方建议:https://unity3d.com/cn/learn/tutorials/temas/best-practices/resources-folder?playlist=30089
/// 2、提供Editor和Simulate模式,前者不适用Assetbundle,直接加载资源,快速开发;后者使用Assetbundle,用本地服务器模拟资源更新
/// 3、场景不进行打包,场景资源打包为预设
/// 4、只提供异步接口,所有加载按异步进行
/// 5、采用LZMA压缩方式,性能瓶颈在Assetbundle加载上,ab加载异步,asset加载同步,ab加载后导出全部asset并卸载ab
/// 6、所有公共ab包(被多个ab包依赖)常驻内存,非公共包加载asset以后立刻卸载,被依赖的公共ab包会随着资源预加载自动加载并常驻内存
/// 7、随意卸载公共ab包可能导致内存资源重复,最好在切换场景时再手动清理不需要的公共ab包
/// 8、常驻包(公共ab包)引用计数不为0时手动清理无效,正在等待加载的所有ab包不能强行终止---一旦发起创建就一定要等操作结束,异步过程进行中清理无效
/// 9、切换场景时最好预加载所有可能使用到的资源,所有加载器用完以后记得Dispose回收,清理GC时注意先释放所有Asset缓存
/// 10、逻辑层所有Asset路径带文件类型后缀,且是AssetBundleConfig.ResourcesFolderName下的相对路径,注意:路径区分大小写
/// TODO:
/// 1、区分场景常驻包和全局公共包,切换场景时自动卸载场景公共包
/// 使用说明:
/// 1、由Asset路径获取AssetName、AssetBundleName:ParseAssetPathToNames
/// 2、设置常驻(公共)ab包:SetAssetBundleResident(assebundleName, true)---公共ab包已经自动设置常驻
/// 2、(预)加载资源:var loader = LoadAssetBundleAsync(assetbundleName),协程等待加载完毕后Dispose:loader.Dispose()
/// 3、加载Asset资源:var loader = LoadAssetAsync(assetPath, TextAsset),协程等待加载完毕后Dispose:loader.Dispose()
/// 4、离开场景清理所有Asset缓存:ClearAssetsCache(),UnloadUnusedAssetBundles(), Resources.UnloadUnusedAssets()
/// 5、离开场景清理必要的(公共)ab包:TryUnloadAssetBundle(),注意:这里只是尝试卸载,所有引用计数不为0的包(还正在加载)不会被清理
/// </summary>namespace AssetBundles
{[Hotfix][LuaCallCSharp]public class AssetBundleManager : UnitySingleton<AssetBundleManager>{// 最大同时进行的ab创建数量const int MAX_ASSETBUNDLE_CREATE_NUM = 5;// manifest:提供依赖关系查找以及hash值比对Manifest manifest = null;// 资源路径相关的映射表AssetsPathMapping assetsPathMapping = null;// 常驻ab包:需要手动添加公共ab包进来,常驻包不会自动卸载(即使引用计数为0),引用计数为0时可以手动卸载HashSet<string> assetbundleResident = new HashSet<string>();// ab缓存包:所有目前已经加载的ab包,包括临时ab包与公共ab包Dictionary<string, AssetBundle> assetbundlesCaching = new Dictionary<string, AssetBundle>();// ab缓存包引用计数:卸载ab包时只有引用计数为0时才会真正执行卸载Dictionary<string, int> assetbundleRefCount = new Dictionary<string, int>(); // asset缓存:给非公共ab包的asset提供逻辑层的复用Dictionary<string, UnityEngine.Object> assetsCaching = new Dictionary<string, UnityEngine.Object>();// 加载数据请求:正在prosessing或者等待prosessing的资源请求Dictionary<string, ResourceWebRequester> webRequesting = new Dictionary<string, ResourceWebRequester>();// 等待处理的资源请求Queue<ResourceWebRequester> webRequesterQueue = new Queue<ResourceWebRequester>();// 正在处理的资源请求List<ResourceWebRequester> prosessingWebRequester = new List<ResourceWebRequester>();// 逻辑层正在等待的ab加载异步句柄List<AssetBundleAsyncLoader> prosessingAssetBundleAsyncLoader = new List<AssetBundleAsyncLoader>();// 逻辑层正在等待的asset加载异步句柄List<AssetAsyncLoader> prosessingAssetAsyncLoader = new List<AssetAsyncLoader>();public static string ManifestBundleName{get;set;}#if UNITY_EDITOR || CLIENT_DEBUG
#if !CLIENT_DEBUG[BlackList]
#endif// Hotfix测试---用于侧测试资源模块的热修复public void TestHotfix(){Debug.Log("********** AssetBundleManager : Call TestHotfix in cs...");}
#endifpublic IEnumerator Initialize(){
#if UNITY_EDITORif (AssetBundleConfig.IsEditorMode){yield break;}
#endifmanifest = new Manifest();assetsPathMapping = new AssetsPathMapping();// 说明:同时请求资源可以提高加载速度var manifestRequest = RequestAssetBundleAsync(manifest.AssetbundleName);var pathMapRequest = RequestAssetBundleAsync(assetsPathMapping.AssetbundleName);yield return manifestRequest;var assetbundle = manifestRequest.assetbundle;manifest.LoadFromAssetbundle(assetbundle);assetbundle.Unload(false);manifestRequest.Dispose();yield return pathMapRequest;assetbundle = pathMapRequest.assetbundle;var mapContent = assetbundle.LoadAsset<TextAsset>(assetsPathMapping.AssetName);if (mapContent != null){assetsPathMapping.Initialize(mapContent.text);}assetbundle.Unload(true);pathMapRequest.Dispose();// 设置所有公共包为常驻包var start = DateTime.Now;var allAssetbundleNames = manifest.GetAllAssetBundleNames();foreach (var curAssetbundleName in allAssetbundleNames){if (string.IsNullOrEmpty(curAssetbundleName)){continue;}int count = 0;foreach (var checkAssetbundle in allAssetbundleNames){if (checkAssetbundle == curAssetbundleName || string.IsNullOrEmpty(checkAssetbundle)){continue;}var allDependencies = manifest.GetAllDependencies(checkAssetbundle);if (Array.IndexOf(allDependencies, curAssetbundleName) >= 0){count++;if (count >= 2){break;}}}if (count >= 2){SetAssetBundleResident(curAssetbundleName, true);}}Debug.Log(string.Format("AssetBundleResident Initialize use {0}ms", (DateTime.Now - start).Milliseconds));yield break;}public IEnumerator Cleanup(){
#if UNITY_EDITORif (AssetBundleConfig.IsEditorMode){yield break;}
#endif// 等待所有请求完成// 要是不等待Unity很多版本都有各种Bugyield return new WaitUntil(() =>{return prosessingWebRequester.Count == 0;});yield return new WaitUntil(() =>{return prosessingAssetBundleAsyncLoader.Count == 0;});yield return new WaitUntil(() =>{return prosessingAssetAsyncLoader.Count == 0;});ClearAssetsCache();foreach (var assetbunle in assetbundlesCaching.Values){if (assetbunle != null){assetbunle.Unload(false);}}assetbundlesCaching.Clear();assetbundleRefCount.Clear();assetbundleResident.Clear();yield break;}public Manifest curManifest{get{return manifest;}}public string DownloadUrl{get{// return Setting.SERVER_RESOURCE_ADDR;return null;}}public void SetAssetBundleResident(string assetbundleName, bool resident){Debug.Log("SetAssetBundleResident : " + assetbundleName + ", " + resident.ToString());bool exist = assetbundleResident.Contains(assetbundleName);if (resident && !exist){assetbundleResident.Add(assetbundleName);}else if(!resident && exist){assetbundleResident.Remove(assetbundleName);}}public bool IsAssetBundleResident(string assebundleName){return assetbundleResident.Contains(assebundleName);}public bool IsAssetBundleLoaded(string assetbundleName){return assetbundlesCaching.ContainsKey(assetbundleName);}public AssetBundle GetAssetBundleCache(string assetbundleName){AssetBundle target = null;assetbundlesCaching.TryGetValue(assetbundleName, out target);return target;}protected void RemoveAssetBundleCache(string assetbundleName){assetbundlesCaching.Remove(assetbundleName);}protected void AddAssetBundleCache(string assetbundleName, AssetBundle assetbundle){assetbundlesCaching[assetbundleName] = assetbundle;}public bool IsAssetLoaded(string assetName){return assetsCaching.ContainsKey(assetName);}public UnityEngine.Object GetAssetCache(string assetName){UnityEngine.Object target = null;assetsCaching.TryGetValue(assetName, out target);return target;}public void AddAssetCache(string assetName, UnityEngine.Object asset){assetsCaching[assetName] = asset;}public void AddAssetbundleAssetsCache(string assetbundleName){
#if UNITY_EDITORif (AssetBundleConfig.IsEditorMode){return;}
#endifif (!IsAssetBundleLoaded(assetbundleName)){Debug.LogError("Try to add assets cache from unloaded assetbundle : " + assetbundleName);return;}var curAssetbundle = GetAssetBundleCache(assetbundleName);var allAssetNames = assetsPathMapping.GetAllAssetNames(assetbundleName);for (int i = 0; i < allAssetNames.Count; i++){var assetName = allAssetNames[i];if (IsAssetLoaded(assetName)){continue;}var assetPath = AssetBundleUtility.PackagePathToAssetsPath(assetName);var asset = curAssetbundle == null ? null : curAssetbundle.LoadAsset(assetPath);AddAssetCache(assetName, asset);#if UNITY_EDITOR// 说明:在Editor模拟时,Shader要重新指定var go = asset as GameObject;if (go != null){var renderers = go.GetComponentsInChildren<Renderer>();for (int j = 0; j < renderers.Length; j++){var mat = renderers[j].sharedMaterial;if (mat == null){continue;}var shader = mat.shader;if (shader != null){var shaderName = shader.name;mat.shader = Shader.Find(shaderName);}}}
#endif}}public void ClearAssetsCache(){assetsCaching.Clear();}public ResourceWebRequester GetAssetBundleAsyncCreater(string assetbundleName){ResourceWebRequester creater = null;webRequesting.TryGetValue(assetbundleName, out creater);return creater;}protected int GetReferenceCount(string assetbundleName){int count = 0;assetbundleRefCount.TryGetValue(assetbundleName, out count);return count;}protected int IncreaseReferenceCount(string assetbundleName){int count = 0;assetbundleRefCount.TryGetValue(assetbundleName, out count);count++;assetbundleRefCount[assetbundleName] = count;return count;}protected int DecreaseReferenceCount(string assetbundleName){int count = 0;assetbundleRefCount.TryGetValue(assetbundleName, out count);count--;assetbundleRefCount[assetbundleName] = count;return count;}protected bool CreateAssetBundleAsync(string assetbundleName){if (IsAssetBundleLoaded(assetbundleName) || webRequesting.ContainsKey(assetbundleName)){return false;}var creater = ResourceWebRequester.Get();var url = AssetBundleUtility.GetAssetBundleFileUrl(assetbundleName);creater.Init(assetbundleName, url);webRequesting.Add(assetbundleName, creater);webRequesterQueue.Enqueue(creater);// 创建器持有的引用:创建器对每个ab来说是全局唯一的IncreaseReferenceCount(assetbundleName);return true;}// 异步请求Assetbundle资源,AB是否缓存取决于是否设置为常驻包,Assets一律缓存,处理依赖public BaseAssetBundleAsyncLoader LoadAssetBundleAsync(string assetbundleName){
#if UNITY_EDITORif (AssetBundleConfig.IsEditorMode){return new EditorAssetBundleAsyncLoader(assetbundleName);}
#endifvar loader = AssetBundleAsyncLoader.Get();prosessingAssetBundleAsyncLoader.Add(loader);if (manifest != null){string[] dependancies = manifest.GetAllDependencies(assetbundleName);for (int i = 0; i < dependancies.Length; i++){var dependance = dependancies[i];if (!string.IsNullOrEmpty(dependance) && dependance != assetbundleName){CreateAssetBundleAsync(dependance);// ab缓存对依赖持有的引用IncreaseReferenceCount(dependance);}}loader.Init(assetbundleName, dependancies);}else{loader.Init(assetbundleName, null);}CreateAssetBundleAsync(assetbundleName);// 加载器持有的引用:同一个ab能同时存在多个加载器,等待ab创建器完成IncreaseReferenceCount(assetbundleName);return loader;}// 从服务器下载网页内容,需提供完整urlpublic ResourceWebRequester DownloadWebResourceAsync(string url){var creater = ResourceWebRequester.Get();creater.Init(url, url, true);webRequesting.Add(url, creater);webRequesterQueue.Enqueue(creater);return creater;}// 从资源服务器下载非Assetbundle资源public ResourceWebRequester DownloadAssetFileAsync(string filePath){if (string.IsNullOrEmpty(DownloadUrl)){Debug.LogError("You should set download url first!!!");return null;}var creater = ResourceWebRequester.Get();var url = DownloadUrl + filePath;creater.Init(filePath, url, true);webRequesting.Add(filePath, creater);webRequesterQueue.Enqueue(creater);return creater;}// 从资源服务器下载Assetbundle资源,不缓存,无依赖public ResourceWebRequester DownloadAssetBundleAsync(string filePath){// 如果ResourceWebRequester升级到使用UnityWebRequester,那么下载AB和下载普通资源需要两个不同的DownLoadHandler// 兼容升级的可能性,这里也做一下区分return DownloadAssetFileAsync(filePath);}// 本地异步请求非Assetbundle资源public ResourceWebRequester RequestAssetFileAsync(string filePath, bool streamingAssetsOnly = true){var creater = ResourceWebRequester.Get();string url = null;if (streamingAssetsOnly){url = AssetBundleUtility.GetStreamingAssetsFilePath(filePath);}else{url = AssetBundleUtility.GetAssetBundleFileUrl(filePath);}creater.Init(filePath, url, true);webRequesting.Add(filePath, creater);webRequesterQueue.Enqueue(creater);return creater;}// 本地异步请求Assetbundle资源,不缓存,无依赖public ResourceWebRequester RequestAssetBundleAsync(string assetbundleName){var creater = ResourceWebRequester.Get();var url = AssetBundleUtility.GetAssetBundleFileUrl(assetbundleName);creater.Init(assetbundleName, url, true);webRequesting.Add(assetbundleName, creater);webRequesterQueue.Enqueue(creater);return creater;}public void UnloadAssetBundleDependencies(string assetbundleName){if (manifest != null){string[] dependancies = manifest.GetAllDependencies(assetbundleName);for (int i = 0; i < dependancies.Length; i++){var dependance = dependancies[i];if (!string.IsNullOrEmpty(dependance) && dependance != assetbundleName){UnloadAssetBundle(dependance);}}}}protected bool UnloadAssetBundle(string assetbundleName, bool unloadResident = false, bool unloadAllLoadedObjects = false){int count = GetReferenceCount(assetbundleName);if (count <= 0){return false;}count = DecreaseReferenceCount(assetbundleName);if (count > 0){return false;}var assetbundle = GetAssetBundleCache(assetbundleName);var isResident = IsAssetBundleResident(assetbundleName);if (assetbundle != null){if (!isResident || isResident && unloadResident){assetbundle.Unload(unloadAllLoadedObjects);RemoveAssetBundleCache(assetbundleName);UnloadAssetBundleDependencies(assetbundleName);return true;}}return false;}public bool TryUnloadAssetBundle(string assetbundleName, bool unloadAllLoadedObjects = false){int count = GetReferenceCount(assetbundleName);if (count > 0){return false;}return UnloadAssetBundle(assetbundleName, true, unloadAllLoadedObjects);}public void UnloadUnusedAssetBundles(bool unloadResident = false, bool unloadAllLoadedObjects = false){int unloadCount = 0;bool hasDoUnload = false;do{hasDoUnload = false;var iter = assetbundleRefCount.GetEnumerator();while (iter.MoveNext()){var assetbundleName = iter.Current.Key;var referenceCount = iter.Current.Value;if (referenceCount <= 0){var result = UnloadAssetBundle(assetbundleName, unloadResident, unloadAllLoadedObjects);if (result){unloadCount++;hasDoUnload = true;}}}} while (hasDoUnload);}public bool MapAssetPath(string assetPath, out string assetbundleName, out string assetName){return assetsPathMapping.MapAssetPath(assetPath, out assetbundleName, out assetName);}public BaseAssetAsyncLoader LoadAssetAsync(string assetPath, System.Type assetType){
#if UNITY_EDITORif (AssetBundleConfig.IsEditorMode){string path = AssetBundleUtility.PackagePathToAssetsPath(assetPath); UnityEngine.Object target = AssetDatabase.LoadAssetAtPath(path, assetType);return new EditorAssetAsyncLoader(target);}
#endifstring assetbundleName = null;string assetName = null;bool status = MapAssetPath(assetPath, out assetbundleName, out assetName);if (!status){Debug.LogError("No assetbundle at asset path :" + assetPath);return null;}var loader = AssetAsyncLoader.Get();prosessingAssetAsyncLoader.Add(loader);if (IsAssetLoaded(assetName)){loader.Init(assetName, GetAssetCache(assetName));return loader;}else{var assetbundleLoader = LoadAssetBundleAsync(assetbundleName);loader.Init(assetName, assetbundleLoader);return loader;}}void Update(){OnProsessingWebRequester();OnProsessingAssetBundleAsyncLoader();OnProsessingAssetAsyncLoader();}void OnProsessingWebRequester(){for (int i = prosessingWebRequester.Count - 1; i >= 0; i--){var creater = prosessingWebRequester[i];creater.Update();if (creater.IsDone()){prosessingWebRequester.RemoveAt(i);webRequesting.Remove(creater.assetbundleName);UnloadAssetBundle(creater.assetbundleName);if (creater.noCache){return;}// 说明:有错误也缓存下来,只不过资源为空// 1、避免再次错误加载// 2、如果不存下来加载器将无法判断什么时候结束AddAssetBundleCache(creater.assetbundleName, creater.assetbundle);creater.Dispose();}}int slotCount = prosessingWebRequester.Count;while (slotCount < MAX_ASSETBUNDLE_CREATE_NUM && webRequesterQueue.Count > 0){var creater = webRequesterQueue.Dequeue();creater.Start();prosessingWebRequester.Add(creater);slotCount++;}}void OnProsessingAssetBundleAsyncLoader(){for (int i = prosessingAssetBundleAsyncLoader.Count - 1; i >= 0; i--){var loader = prosessingAssetBundleAsyncLoader[i];loader.Update();if (loader.IsDone()){UnloadAssetBundle(loader.assetbundleName);prosessingAssetBundleAsyncLoader.RemoveAt(i);}}}void OnProsessingAssetAsyncLoader(){for (int i = prosessingAssetAsyncLoader.Count - 1; i >= 0; i--){var loader = prosessingAssetAsyncLoader[i];loader.Update();if (loader.IsDone()){prosessingAssetAsyncLoader.RemoveAt(i);}}}#if UNITY_EDITOR[BlackList]public HashSet<string> GetAssetbundleResident(){return assetbundleResident;}[BlackList]public ICollection<string> GetAssetbundleCaching(){return assetbundlesCaching.Keys;}[BlackList]public Dictionary<string, ResourceWebRequester> GetWebRequesting(){return webRequesting;}[BlackList]public Queue<ResourceWebRequester> GetWebRequestQueue(){return webRequesterQueue;}[BlackList]public List<ResourceWebRequester> GetProsessingWebRequester(){return prosessingWebRequester;}[BlackList]public List<AssetBundleAsyncLoader> GetProsessingAssetBundleAsyncLoader(){return prosessingAssetBundleAsyncLoader;}[BlackList]public List<AssetAsyncLoader> GetProsessingAssetAsyncLoader(){return prosessingAssetAsyncLoader;}[BlackList]public string GetAssetBundleName(string assetName){return assetsPathMapping.GetAssetBundleName(assetName);}[BlackList]public int GetAssetCachingCount(){return assetsCaching.Count;}[BlackList]public Dictionary<string, List<string>> GetAssetCaching(){var assetbundleDic = new Dictionary<string, List<string>>();List<string> assetNameList = null;var iter = assetsCaching.GetEnumerator();while (iter.MoveNext()){var assetName = iter.Current.Key;var assetbundleName = assetsPathMapping.GetAssetBundleName(assetName);assetbundleDic.TryGetValue(assetbundleName, out assetNameList);if (assetNameList == null){assetNameList = new List<string>();}assetNameList.Add(assetName);assetbundleDic[assetbundleName] = assetNameList;}return assetbundleDic;}[BlackList]public int GetAssetbundleRefrenceCount(string assetbundleName){return GetReferenceCount(assetbundleName);}[BlackList]public int GetAssetbundleDependenciesCount(string assetbundleName){string[] dependancies = manifest.GetAllDependencies(assetbundleName);int count = 0;for (int i = 0; i < dependancies.Length; i++){var cur = dependancies[i];if (!string.IsNullOrEmpty(cur) && cur != assetbundleName){count++;}}return count;}[BlackList]public List<string> GetAssetBundleRefrences(string assetbundleName){List<string> refrences = new List<string>();var cachingIter = assetbundlesCaching.GetEnumerator();while (cachingIter.MoveNext()){var curAssetbundleName = cachingIter.Current.Key;if (curAssetbundleName == assetbundleName){continue;}string[] dependancies = manifest.GetAllDependencies(curAssetbundleName);for (int i = 0; i < dependancies.Length; i++){var dependance = dependancies[i];if (dependance == assetbundleName){refrences.Add(curAssetbundleName);}}}var requestingIter = webRequesting.GetEnumerator();while (requestingIter.MoveNext()){var curAssetbundleName = requestingIter.Current.Key;if (curAssetbundleName == assetbundleName){continue;}string[] dependancies = manifest.GetAllDependencies(curAssetbundleName);for (int i = 0; i < dependancies.Length; i++){var dependance = dependancies[i];if (dependance == assetbundleName){refrences.Add(curAssetbundleName);}}}return refrences;}[BlackList]public List<string> GetWebRequesterRefrences(string assetbundleName){List<string> refrences = new List<string>();var iter = webRequesting.GetEnumerator();while (iter.MoveNext()){var curAssetbundleName = iter.Current.Key;var webRequster = iter.Current.Value;if (curAssetbundleName == assetbundleName){refrences.Add(webRequster.Sequence.ToString());continue;}}return refrences;}[BlackList]public List<string> GetAssetBundleLoaderRefrences(string assetbundleName){List<string> refrences = new List<string>();var iter = prosessingAssetBundleAsyncLoader.GetEnumerator();while (iter.MoveNext()){var curAssetbundleName = iter.Current.assetbundleName;var curLoader = iter.Current;if (curAssetbundleName == assetbundleName){refrences.Add(curLoader.Sequence.ToString());}}return refrences;}
#endif}
}
启动游戏时
GameLauch
public class GameLaunch : MonoBehaviour {void Awake() { // 初始化框架this.gameObject.AddComponent<show_fps>();this.gameObject.AddComponent<xLuaMgr>();this.gameObject.AddComponent<ResMgr>();// end xLuaMgr.Instance.Init();}IEnumerator InitPackageName(){
#if UNITY_EDITORif (AssetBundleConfig.IsEditorMode){yield break;}
#endif// 重要,请求本地文件,这里要先看看可写的本地目录,而不是Streaming目录var packageNameRequest = AssetBundleManager.Instance.RequestAssetFileAsync(BuildUtils.PackageNameFileName);yield return packageNameRequest; // 中断协程直到请求结束var packageName = packageNameRequest.text;packageNameRequest.Dispose();AssetBundleManager.ManifestBundleName = packageName;ChannelManager.instance.Init(packageName);Debug.Log(string.Format("packageName = {0}", packageName));yield break;}IEnumerator GameStart(){var start = DateTime.Now;yield return InitPackageName();Debug.Log(string.Format("InitPackageName use {0}ms", (DateTime.Now - start).Milliseconds));// 启动资源管理模块start = DateTime.Now;yield return AssetBundleManager.Instance.Initialize();Debug.Log(string.Format("AssetBundleManager Initialize use {0}ms", (DateTime.Now - start).Milliseconds));string luaAssetbundleName = xLuaMgr.Instance.AssetbundleName;AssetBundleManager.Instance.SetAssetBundleResident(luaAssetbundleName, true);var abloader = AssetBundleManager.Instance.LoadAssetBundleAsync(luaAssetbundleName);yield return abloader;abloader.Dispose();xLuaMgr.Instance.EnterLuaGame();yield break;}void Start () {this.StartCoroutine(this.GameStart());}void Update () {}
}
ResMgr
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using XLua;
using AssetBundles;[LuaCallCSharp] // Lua 能否调用到这个装饰器很重要;
public class ResMgr : UnitySingleton<ResMgr> { public override void Awake() {base.Awake();}public UnityEngine.Object GetAssetCache(string name, string type_name) {
#if UNITY_EDITOR// Type.GetType("资源名字")if (AssetBundleConfig.IsEditorMode){string path = AssetBundleUtility.PackagePathToAssetsPath(name);// LoadAssetAtPath 只支持模板模式;// UnityEditor.AssetDatabase.LoadAssetAtPath(name, GameObject)// 根据资源类型的名字来加if判断,来使用模板函数;UnityEngine.Object target = UnityEditor.AssetDatabase.LoadAssetAtPath<GameObject>(path);return target;}
#endifreturn AssetBundleManager.Instance.GetAssetCache(name);}public void LoadAssetBundleAsync(string assetbundleName, Action end_func){this.StartCoroutine(this.IE_LoadAssetBundleAsync(assetbundleName, end_func));}IEnumerator IE_LoadAssetBundleAsync(string assetbundleName, Action end_func) {var loader = AssetBundleManager.Instance.LoadAssetBundleAsync(assetbundleName);yield return loader;end_func();}
}
代码热更
具体热更步骤从前面的文章查看,简单来说,是比较本地版本和服务器版本,再看看哪些有变动,然后下载。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using AssetBundles;
using GameChannel;public class GameLaunch : MonoBehaviour {void Awake() { // 初始化框架this.gameObject.AddComponent<AssetBundleManager>(); // 实例化一个AssetBudnleManager;this.gameObject.AddComponent<show_fps>();this.gameObject.AddComponent<xLuaMgr>();this.gameObject.AddComponent<ResMgr>();// end xLuaMgr.Instance.Init();}IEnumerator InitPackageName(){
#if UNITY_EDITORif (AssetBundleConfig.IsEditorMode){yield break;}
#endifvar packageNameRequest = AssetBundleManager.Instance.RequestAssetFileAsync(BuildUtils.PackageNameFileName);yield return packageNameRequest; // 中断当前协程,直到请求结束;var packageName = packageNameRequest.text;packageNameRequest.Dispose(); // 释放请求;AssetBundleManager.ManifestBundleName = packageName; // 包名字;ChannelManager.instance.Init(packageName);Debug.Log(string.Format("packageName = {0}", packageName));yield break;}IEnumerator CheckAndDownload() {// 如果已经是最新的,直接返回就可以了;// end // 更新的资源包的下载; 直接下载, 最新的ab包;// 根据版本,拉取要下载文件列表,然后来一个个下载, 下载完成后直接进入游戏,即可;// 检车更新;var downloadRequest = AssetBundleManager.Instance.DownloadAssetBundleAsync("lua.assetbundle");yield return downloadRequest;GameUtility.SafeWriteAllBytes(AssetBundleUtility.GetPersistentDataPath() + "/lua.assetbundle", downloadRequest.bytes);downloadRequest.Dispose();// end yield break;}IEnumerator GameStart(){var start = DateTime.Now;yield return InitPackageName();Debug.Log(string.Format("InitPackageName use {0}ms", (DateTime.Now - start).Milliseconds));// 启动资源管理模块start = DateTime.Now;yield return AssetBundleManager.Instance.Initialize();Debug.Log(string.Format("AssetBundleManager Initialize use {0}ms", (DateTime.Now - start).Milliseconds));// 启动检测更新yield return CheckAndDownload();// end string luaAssetbundleName = xLuaMgr.Instance.AssetbundleName;// Lua脚本设置的是常驻AB包,不是释放的;AssetBundleManager.Instance.SetAssetBundleResident(luaAssetbundleName, true);var abloader = AssetBundleManager.Instance.LoadAssetBundleAsync(luaAssetbundleName);yield return abloader;abloader.Dispose();xLuaMgr.Instance.EnterLuaGame();yield break;}void Start () {this.StartCoroutine(this.GameStart());}void Update () {}
}
Lua和C#通讯原理(重要)
应用
- 想要在Lua添加C#写的组件,需要在代码中加上[LuaCallCSharp],在Lua脚本中AddComponent
- AddComponent之后,我们就可以在lua中利用这个组件调用其中的函数了
原理
- [LuaCallCSharp]这个注解会做lua导出,在Xlua插件中的Gen文件夹有很多Wrap代码,当点击Xlua插件的生成代码按钮后,拥有[LuaCallCSharp]注解的类会写到一个link.xml文件中,并会导出对应的包装文件到wrap文件夹中,导出后Lua才能调用C#脚本中编写的函数。
- 要想通过lua调用这个方法,lua类型中的元表必须有这个方法,因此在C#端需要把元表设置好。Utils.BeginObjectRegister往类型中添加了元表,加了元表后,把方法都注册到了元表中
- 这些Wrap文件会在XLuaGenAutoRegister脚本中统一被调用注册和导出函数,这些都放在脚本的初始化函数中,初始化函数在虚拟机启动时调用。
以ResMgrWrap示例
#if USE_UNI_LUA
using LuaAPI = UniLua.Lua;
using RealStatePtr = UniLua.ILuaState;
using LuaCSFunction = UniLua.CSharpFunctionDelegate;
#else
using LuaAPI = XLua.LuaDLL.Lua;
using RealStatePtr = System.IntPtr;
using LuaCSFunction = XLua.LuaDLL.lua_CSFunction;
#endif
using XLua;
using System.Collections.Generic;namespace XLua.CSObjectWrap
{using Utils = XLua.Utils;public class ResMgrWrap {public static void __Register(RealStatePtr L){ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);System.Type type = typeof(ResMgr);// 创建一个元表Utils.BeginObjectRegister(type, L, translator, 0, 3, 0, 0);// 注册方法Utils.RegisterFunc(L, Utils.METHOD_IDX, "Awake", _m_Awake);Utils.RegisterFunc(L, Utils.METHOD_IDX, "GetAssetCache", _m_GetAssetCache);Utils.RegisterFunc(L, Utils.METHOD_IDX, "LoadAssetBundleAsync", _m_LoadAssetBundleAsync);Utils.EndObjectRegister(type, L, translator, null, null,null, null, null);Utils.BeginClassRegister(type, L, __CreateInstance, 1, 0, 0);Utils.EndClassRegister(type, L, translator);}[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]static int __CreateInstance(RealStatePtr L){try {ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);if(LuaAPI.lua_gettop(L) == 1){ResMgr gen_ret = new ResMgr();translator.Push(L, gen_ret);return 1;}}catch(System.Exception gen_e) {return LuaAPI.luaL_error(L, "c# exception:" + gen_e);}return LuaAPI.luaL_error(L, "invalid arguments to ResMgr constructor!");} [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]static int _m_Awake(RealStatePtr L){try {ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);ResMgr gen_to_be_invoked = (ResMgr)translator.FastGetCSObj(L, 1); {gen_to_be_invoked.Awake( );return 0;}} catch(System.Exception gen_e) {return LuaAPI.luaL_error(L, "c# exception:" + gen_e);}}[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]static int _m_GetAssetCache(RealStatePtr L){try {ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L); ResMgr gen_to_be_invoked = (ResMgr)translator.FastGetCSObj(L, 1);{string _name = LuaAPI.lua_tostring(L, 2);string _type_name = LuaAPI.lua_tostring(L, 3);UnityEngine.Object gen_ret = gen_to_be_invoked.GetAssetCache( _name, _type_name );translator.Push(L, gen_ret); return 1;}} catch(System.Exception gen_e) {return LuaAPI.luaL_error(L, "c# exception:" + gen_e);}}[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]static int _m_LoadAssetBundleAsync(RealStatePtr L){try {ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);ResMgr gen_to_be_invoked = (ResMgr)translator.FastGetCSObj(L, 1);{string _assetbundleName = LuaAPI.lua_tostring(L, 2);System.Action _end_func = translator.GetDelegate<System.Action>(L, 3);gen_to_be_invoked.LoadAssetBundleAsync( _assetbundleName, _end_func ); return 0;}} catch(System.Exception gen_e) {return LuaAPI.luaL_error(L, "c# exception:" + gen_e);}} }
}
public class XLua_Gen_Initer_Register__
{static void wrapInit0(LuaEnv luaenv, ObjectTranslator translator){ translator.DelayWrapLoader(typeof(ResMgr), ResMgrWrap.__Register);--snip--}static void Init(LuaEnv luaenv, ObjectTranslator translator){wrapInit0(luaenv, translator);wrapInit1(luaenv, translator);translator.AddInterfaceBridgeCreator(typeof(System.Collections.IEnumerator), SystemCollectionsIEnumeratorBridge.__Create);translator.AddInterfaceBridgeCreator(typeof(XLuaTest.IExchanger), XLuaTestIExchangerBridge.__Create);translator.AddInterfaceBridgeCreator(typeof(Tutorial.CSCallLua.ItfD), TutorialCSCallLuaItfDBridge.__Create);translator.AddInterfaceBridgeCreator(typeof(XLuaTest.InvokeLua.ICalc), XLuaTestInvokeLuaICalcBridge.__Create);}static XLua_Gen_Initer_Register__(){XLua.LuaEnv.AddIniter(Init);}
}namespace XLua
{public partial class ObjectTranslator{static XLua.CSObjectWrap.XLua_Gen_Initer_Register__ s_gen_reg_dumb_obj = new XLua.CSObjectWrap.XLua_Gen_Initer_Register__();static XLua.CSObjectWrap.XLua_Gen_Initer_Register__ gen_reg_dumb_obj {get{return s_gen_reg_dumb_obj;}}}internal partial class InternalGlobals{static InternalGlobals(){extensionMethodMap = new Dictionary<Type, IEnumerable<MethodInfo>>(){};genTryArrayGetPtr = StaticLuaCallbacks.__tryArrayGet;genTryArraySetPtr = StaticLuaCallbacks.__tryArraySet;}}
}
从上面的ObjectTranslator就会进行启动注册
为什么CS.XXX能访问C#中的代码
是因为LuaEnv初始化的时候,会执行lua代码,代码里面会创建CS ={},CS这个table的元表的__index元方法你可以看下,里面会用到xlua.import_type函数。干活的逻辑就是csharp层的ImportType
也就是说,创建一个类型表,一种方式是lua层,CS.A.B.C 会通过触发元方法走到csharp层创建(也是到getTypeId)。另一种是当push该类型的obj实例的时候,也会到getTypeId
可参考的链接:
https://www.cnblogs.com/iwiniwin/p/15307368.html
https://www.cnblogs.com/iwiniwin/p/15323970.html
相关文章:
XLua热更新框架原理和代码实战
安装插件 下载Xlua插件:https://github.com/Tencent/xLua 下载完成后,把Asset文件夹下的文件拖入自己的工程Asset中,看到Unity编辑器上多了个Xlua菜单,说明插件导入成功 Lua启动代码 新建一个空场景,场景中什么都不…...
Hive客户端hive与beeline的区别
hive与beeline简介 1、背景2、hive3、beeline4、hive与beeline的关系 1、背景 Hive的hive与beeline命令都可以为客户端提供Hive的控制台连接。两者之间有什么区别或联系吗? Hive-cli(hive)是Hive连接hiveserver2的命令行工具,从Hive出生就一直存在&…...
<MySQL> 什么是数据库索引?数据库索引的底层结构是什么?
目录 一、什么是数据库索引? 1.1 索引的概念 1.2 索引的特点 1.3 索引的适用场景 1.4 索引的使用 1.4.1 创建索引 1.4.2 查看索引 1.4.3 删除索引 二、数据库索引的底层结构是什么? 2.1 数据库中的 B树 长啥样? 2.2 B树为什么适合做数据库索…...
对于koa中间件的理解
洋葱模型 大家都知道koa是洋葱模型,先一层一层通过next往下,之后再回去执行next后面的内容,next即使没写,最后也会进入下一个中间件。 那么什么是ctx呢,ctx顾名思义就是上下文,也就是上一层传给下一层的东…...
分页文件pagefile.sys引出的疑问
现象描述: 磁盘中显示无任何文件,却占用5GB左右的磁盘空间;格式化D盘时提示【此驱动器正在使用中。另一个程序或进程正在使用此驱动器。是否仍要对其进行格式化?】,点击【是】提示【Windows 无法完成格式化。】&#…...
【开题报告】疫苗在线预约小程序的设计与实现
1.选题背景 (1)新冠疫情下的疫苗接种挑战: 针对当前全球范围内的新冠疫情,疫苗接种成为控制疫情蔓延的重要手段。然而,大规模疫苗接种也带来了接种排队、人群聚集等管理难题,为了更好地组织和管理疫苗接种…...
【深度学习实验】注意力机制(二):掩码Softmax 操作
文章目录 一、实验介绍二、实验环境1. 配置虚拟环境2. 库版本介绍 三、实验内容0. 理论介绍a. 认知神经学中的注意力b. 注意力机制: 1. 注意力权重矩阵可视化(矩阵热图)2. 掩码Softmax 操作a. 导入必要的库b. masked_softmaxc. 实验结果 …...
idea运行项目之后一直卡在Writing classes… 解决方案
最近遇到idea里直接运行一个Spring boot项目后,idea一直慢悠悠的parsing java,然后就writing classes,然后就一直卡着不动了,运气好10几分钟能把项目启动起来。 多年的摸鱼经验告诉我,事出反常必有妖,赶紧…...
CentOS7 安装mysql8(离线安装)postgresql14(在线安装)
注:linux系统为vmware虚拟机,和真实工作环境可能有出入,不过正因如此我暴露了NAT转出的IP也没什么大碍 引言 postgresql与mysql目前都是非常受人欢迎的两大数据库,其各有各的优势,初学者先使用简单一张图来说明两者区…...
使用vant list实现订单列表,支持下拉加载更多
在公司项目开发时,有一个需求是实现可以分页的订单列表,由于是移动端项目,所以最好的解决方法是做下拉加载更多。 1.在页面中使用vant组件 <van-listv-model"loading":finished"finished"finished-text"没有更…...
OpenCV快速入门:图像形态学操作
文章目录 前言一、图像形态学基础1.1 背景介绍1.2 像素距离1.2.1 什么是像素距离?1.2.2 常见的像素距离度量方法1.2.3 计算像素距离的代码实现 1.3 图像连通性1.3.1 什么是图像连通性?1.3.2 连通类型1.3.3 连通组件标记1.3.4 连通性在图像处理中的应用 1…...
Scrapy----Scrapy简介
文章目录 概述与应用背景架构和组件功能和特点社区生态概述与应用背景 Scrapy,一个高效、灵活、且强大的Web爬取框架,被广泛应用于数据抓取和网页内容的结构化提取。它是用Python编写的,支持多平台运行,适用于数据挖掘、在线零售信息收集、历史数据存档等多种场景。Scrapy…...
基环树(pseudotree)入门
目录 无向基环树找环,[题目](https://www.luogu.com.cn/problem/P8655)拓扑排序找环并查集找环dfs找环 内向基环树[2876. 有向图访问计数](https://leetcode.cn/problems/count-visited-nodes-in-a-directed-graph/description/)[2127. 参加会议的最多员工数](https…...
nrm的安装以及使用
1,什么是nrm nrm 是一个 npm 源管理器,允许你快速地在 npm源间切换。 什么意思呢,npm默认情况下是使用npm官方源(使用npm config ls命令可以查看),在国内用这个源肯定是不靠谱的,一般我们都会…...
Linux:补充一些常用命令
Linux:补充一些常用命令 1. free -h2. df -lh3. du -sh *4. uname -a5. which6. mvn install 编译打包7. find -name *.jar8. cd -9. nohup java -jar *.jar &10. ps -ef|grep java11. netstat -ntlp 1. free -h free 命令显示系统使用和空闲的内存情况&#x…...
Maven编译报错:javacTask: 源发行版 1.8 需要目标发行版 1.8
报错截图: IDEA中的jdk检查都正常设置的1.8一点毛病没有。参考其他帖子链接如下: https://blog.csdn.net/zhishidi/article/details/131480199https://blog.51cto.com/u_16213460/7197764https://blog.csdn.net/lck_csdn/article/details/125387878 逐…...
python批量为视频添加文字水印和图片水印的程序
如题,代码如下,可设置多个图片水印及它们的移动位置 功能为:可以添加多个动态移动的水印,还可以设置水印的大小以及移动速度,也可以增加文字水印,重点是这个是批量执行的,可以对目录下的所有视…...
使用 webpack 打包 express 应用
使用 webpack 打包 express 应用 安装 webpack 依赖 pnpm add webpack webpack-cli -D初始化配置 可以使用命令 webpack init 初始化配置或者直接自己创建 webpack.config.js 文件和增加 npm 脚本: 下面是 npm 脚本 和 webpack.config.js 配置: // G…...
深度学习——(生成模型)DDPM
前置数学知识 1、先验概率和后验概率 先验概率:根据以往经验和分析得到的概率,它往往作为“由因求果”问题中的“因”出现,如 q ( x t ∣ x t − 1 ) q(x_t|x_{t-1}) q(xt∣xt−1) 后验概率:指在得到“结果”的信息后重新修正的概率,是…...
23-Oracle 23 ai 区块链表(Blockchain Table)
小伙伴有没有在金融强合规的领域中遇见,必须要保持数据不可变,管理员都无法修改和留痕的要求。比如医疗的电子病历中,影像检查检验结果不可篡改行的,药品追溯过程中数据只可插入无法删除的特性需求;登录日志、修改日志…...
Java - Mysql数据类型对应
Mysql数据类型java数据类型备注整型INT/INTEGERint / java.lang.Integer–BIGINTlong/java.lang.Long–––浮点型FLOATfloat/java.lang.FloatDOUBLEdouble/java.lang.Double–DECIMAL/NUMERICjava.math.BigDecimal字符串型CHARjava.lang.String固定长度字符串VARCHARjava.lang…...
Java-41 深入浅出 Spring - 声明式事务的支持 事务配置 XML模式 XML+注解模式
点一下关注吧!!!非常感谢!!持续更新!!! 🚀 AI篇持续更新中!(长期更新) 目前2025年06月05日更新到: AI炼丹日志-28 - Aud…...
ServerTrust 并非唯一
NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...
JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案
JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停 1. 安全点(Safepoint)阻塞 现象:JVM暂停但无GC日志,日志显示No GCs detected。原因:JVM等待所有线程进入安全点(如…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
【笔记】WSL 中 Rust 安装与测试完整记录
#工作记录 WSL 中 Rust 安装与测试完整记录 1. 运行环境 系统:Ubuntu 24.04 LTS (WSL2)架构:x86_64 (GNU/Linux)Rust 版本:rustc 1.87.0 (2025-05-09)Cargo 版本:cargo 1.87.0 (2025-05-06) 2. 安装 Rust 2.1 使用 Rust 官方安…...
腾讯云V3签名
想要接入腾讯云的Api,必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口,但总是卡在签名这一步,最后放弃选择SDK,这次终于自己代码实现。 可能腾讯云翻新了接口文档,现在阅读起来,清晰了很多&…...
C++ 设计模式 《小明的奶茶加料风波》
👨🎓 模式名称:装饰器模式(Decorator Pattern) 👦 小明最近上线了校园奶茶配送功能,业务火爆,大家都在加料: 有的同学要加波霸 🟤,有的要加椰果…...
uniapp 集成腾讯云 IM 富媒体消息(地理位置/文件)
UniApp 集成腾讯云 IM 富媒体消息全攻略(地理位置/文件) 一、功能实现原理 腾讯云 IM 通过 消息扩展机制 支持富媒体类型,核心实现方式: 标准消息类型:直接使用 SDK 内置类型(文件、图片等)自…...
