安装插件
下载Xlua插件:https://github.com/Tencent/xLua
下载完成后,把Asset文件夹下的文件拖入自己的工程Asset中,看到Unity编辑器上多了个Xlua菜单,说明插件导入成功
Lua启动代码
新建一个空场景,场景中什么都不放,只有一个启动脚本,所有的东西都从启动脚本中加载,这样打包时才能没有依赖,所有资源支持热更。
启动脚本
GameLaunch:
using UnityEngine; public class GameLaunch : MonoBehaviour { void Awake() { // 初始化框架 this.gameObject.AddComponent
(); this.gameObject.AddComponent(); // end xLuaMgr.Instance.Init(); } void Start () { // 进入启动逻辑 xLuaMgr.Instance.EnterLuaGame(); // end } void Update () { } } xLua管理脚本 using UnityEngine; using System.IO; using XLua; public class xLuaMgr : UnitySingleton { 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/LuaScripts scriptPath = Path.Combine(scriptPath, filePath); // Assets/LuaScripts/game/game_start.lua // Debug.Log("Custom Load lua script :"+ scriptPath); return GameUtility.SafeReadAllBytes(scriptPath); } #endif } 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") end main.start = start return main 通用脚本:单例 using UnityEngine; // 实现普通的单例模式 // where 限制模板的类型, new()指的是这个类型必须要能被实例化 public abstract class Singleton 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 : 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 { public const string luaScriptsFolder ="LuaScripts"; const string gameMainScriptName ="main"; // main.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(); // 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/LuaScripts scriptPath = Path.Combine(scriptPath, filePath); // Assets/LuaScripts/game/game_start.lua // Debug.Log("Custom Load lua script :"+ scriptPath); return GameUtility.SafeReadAllBytes(scriptPath); } #endif } 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(); end local function OnApplicationQuit() end local function Update() LuaGameObject.Update() end local function FixedUpdate() LuaGameObject.FixedUpdate() end local function LateUpdate() LuaGameObject.LateUpdate() end main.OnApplicationQuit = OnApplicationQuit main.Update = Update main.FixedUpdate = FixedUpdate main.LateUpdate = LateUpdate main.start = start return main Lua组件的基类 所有的组件基类继承自LuaBehaviour
-- 返回一个基类为base的类;用于继承 function LuaExtend(base) return base:new() end local LuaBehaviour = {} function LuaBehaviour:new(instant) if not instant then instant = {} --类的实例 end setmetatable(instant, {__index = self}) return instant end -- obj: GameObject -- transform, gameObject function LuaBehaviour:init(obj) self.transform = obj.transform self.gameObject = obj end return LuaBehaviour Lua组件化管理 管理脚本:
LuaGameObject = {} local GameObject = CS.UnityEngine.GameObject local GameObjectMap = {} -- key ObjectID: {lua组件实例1, Lua组件实例2, ...}; local function Instantiate(prefab) GameObject.Instantiate(prefab) end local function Destroy(obj) local obj_id = obj:GetInstanceID() if (GameObjectMap[obj_id]) then -- 删除掉所有的组件实例 table.remove(GameObjectMap, obj_id) end GameObject.Destroy(obj) end local function DestroyAfter(obj, afterTime) local obj_id = obj:GetInstanceID() if (GameObjectMap[obj_id]) then -- 删除掉所有的组件实例 table.remove(GameObjectMap, obj_id) end GameObject.Destroy(obj, afterTime) end local function Find(name) return GameObject.Find(name) end local function AddLuaComponent(obj, lua_class) local componet = lua_class:new() componet:init(obj) local obj_id = obj:GetInstanceID() if (GameObjectMap[obj_id]) then table.insert(GameObjectMap[obj_id], componet) else GameObjectMap[obj_id] = {} table.insert(GameObjectMap[obj_id], componet) end if componet.Awake ~= nil then componet:Awake() end return componet end local function GetLuaComponent(obj, lua_class) return nil end local function trigger_update(components_array) local key, value for key, value in pairs(components_array) do if value.Update ~= nil then value:Update() end end end local function trigger_fixupdate(components_array) local key, value for key, value in pairs(components_array) do if value.FixedUpdate ~= nil then value:FixedUpdate() end end end local function trigger_lateupdate(components_array) local key, value for key, value in pairs(components_array) do if value.LateUpdate ~= nil then value:LateUpdate() end end end local function Update() local key, value for key, value in pairs(GameObjectMap) do trigger_update(value) end end local function FixedUpdate() local key, value for key, value in pairs(GameObjectMap) do trigger_fixupdate(value) end end local function LateUpdate() local key, value for key, value in pairs(GameObjectMap) do trigger_lateupdate(value) end end LuaGameObject.Update = Update LuaGameObject.LateUpdate = LateUpdate LuaGameObject.FixedUpdate = FixedUpdate LuaGameObject.Find = Find LuaGameObject.Instantiate = Instantiate LuaGameObject.Destroy = Destroy LuaGameObject.DestroyAfter = DestroyAfter LuaGameObject.AddLuaComponent = AddLuaComponent LuaGameObject.GetLuaComponent = GetLuaComponent return 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) end return 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 end local function enter_game_scene() end local function init() enter_login_scene() end start.init = init return 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(), 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() end return 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 { 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 = GetAssetCache local function LoadAssetBundleAsync(name, callback) cs_ResMgr:LoadAssetBundleAsync(name, callback) end ResMgr.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(); this.gameObject.AddComponent(); this.gameObject.AddComponent(); // end xLuaMgr.Instance.Init(); } IEnumerator InitPackageName() { #if UNITY_EDITOR if (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 { public const string luaScriptsFolder ="LuaScripts"; public const string luaAssetbundleAssetName ="Lua"; const string gameMainScriptName ="main"; // main.lua private 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_EDITOR if (AssetBundleConfig.IsEditorMode) { scriptPath = Path.Combine(Application.dataPath, luaScriptsFolder);// Assets/LuaScripts scriptPath = Path.Combine(scriptPath, filePath); // Assets/LuaScripts/game/game_start.lua byte[] 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) end return ResMgr */ 管理器代码 渠道管理器框架 using System; using XLua; namespace GameChannel { [Hotfix] [LuaCallCSharp] public class ChannelManager : Singleton { private BaseChannel channel = null; private Action initDelFun = null; public Action downLoadGameSucceed = null; public Action downLoadGameFail = null; public Action 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 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(); } } } AssetBundle管理器框架 using UnityEngine; using System.Collections; using System.Collections.Generic; using XLua; using System; #if UNITY_EDITOR using UnityEditor; #endif /// /// 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的包(还正在加载)不会被清理 /// namespace AssetBundles { [Hotfix] [LuaCallCSharp] public class AssetBundleManager : UnitySingleton { // 最大同时进行的ab创建数量 const int MAX_ASSETBUNDLE_CREATE_NUM = 5; // manifest:提供依赖关系查找以及hash值比对 Manifest manifest = null; // 资源路径相关的映射表 AssetsPathMapping assetsPathMapping = null; // 常驻ab包:需要手动添加公共ab包进来,常驻包不会自动卸载(即使引用计数为0),引用计数为0时可以手动卸载 HashSet assetbundleResident = new HashSet(); // ab缓存包:所有目前已经加载的ab包,包括临时ab包与公共ab包 Dictionary assetbundlesCaching = new Dictionary(); // ab缓存包引用计数:卸载ab包时只有引用计数为0时才会真正执行卸载 Dictionary assetbundleRefCount = new Dictionary(); // asset缓存:给非公共ab包的asset提供逻辑层的复用 Dictionary assetsCaching = new Dictionary(); // 加载数据请求:正在prosessing或者等待prosessing的资源请求 Dictionary webRequesting = new Dictionary(); // 等待处理的资源请求 Queue webRequesterQueue = new Queue(); // 正在处理的资源请求 List prosessingWebRequester = new List(); // 逻辑层正在等待的ab加载异步句柄 List prosessingAssetBundleAsyncLoader = new List(); // 逻辑层正在等待的asset加载异步句柄 List prosessingAssetAsyncLoader = new List(); 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..."); } #endif public IEnumerator Initialize() { #if UNITY_EDITOR if (AssetBundleConfig.IsEditorMode) { yield break; } #endif manifest = 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(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_EDITOR if (AssetBundleConfig.IsEditorMode) { yield break; } #endif // 等待所有请求完成 // 要是不等待Unity很多版本都有各种Bug yield 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_EDITOR if (AssetBundleConfig.IsEditorMode) { return; } #endif if (!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(); 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_EDITOR if (AssetBundleConfig.IsEditorMode) { return new EditorAssetBundleAsyncLoader(assetbundleName); } #endif var 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; } // 从服务器下载网页内容,需提供完整url public 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_EDITOR if (AssetBundleConfig.IsEditorMode) { string path = AssetBundleUtility.PackagePathToAssetsPath(assetPath); UnityEngine.Object target = AssetDatabase.LoadAssetAtPath(path, assetType); return new EditorAssetAsyncLoader(target); } #endif string 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 GetAssetbundleResident() { return assetbundleResident; } [BlackList] public ICollection GetAssetbundleCaching() { return assetbundlesCaching.Keys; } [BlackList] public Dictionary GetWebRequesting() { return webRequesting; } [BlackList] public Queue GetWebRequestQueue() { return webRequesterQueue; } [BlackList] public List GetProsessingWebRequester() { return prosessingWebRequester; } [BlackList] public List GetProsessingAssetBundleAsyncLoader() { return prosessingAssetBundleAsyncLoader; } [BlackList] public List GetProsessingAssetAsyncLoader() { return prosessingAssetAsyncLoader; } [BlackList] public string GetAssetBundleName(string assetName) { return assetsPathMapping.GetAssetBundleName(assetName); } [BlackList] public int GetAssetCachingCount() { return assetsCaching.Count; } [BlackList] public Dictionary> GetAssetCaching() { var assetbundleDic = new Dictionary>(); List 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(); } 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 GetAssetBundleRefrences(string assetbundleName) { List refrences = new List(); 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 GetWebRequesterRefrences(string assetbundleName) { List refrences = new List(); 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 GetAssetBundleLoaderRefrences(string assetbundleName) { List refrences = new List(); 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 "; 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所在目录 + AssetsPackage string destination = Path.Combine(Application.dataPath, AssetBundleConfig.AssetsFolderName); // string destination = Path.Combine(Application.dataPath,"AssetsPackage"); // Assets/AssetsPackage/Lua destination = 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.bytes for (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 /// /// added by wsh @ 2017.12.25 /// 注意: /// 1、所有ab路径中目录、文件名不能以下划线打头,否则出包时StreamingAssets中的资源不能打到真机上,很坑爹 /// 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_EDITOR public 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 CheckerFilters = new List(); // 序列化用的,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; /// /// added by wsh @ 2018.01.06 /// 说明:Assetbundle分发器Inspector,为其提供可视化的编辑界面 /// TODO: /// 1、还未完成,目前只是做了基本的配置功能 /// namespace AssetBundles { [CustomEditor(typeof(DefaultAsset), true)] public class AssetBundleDispatcherInspector : Editor { AssetBundleDispatcherConfig dispatcherConfig = null; string packagePath = null; string targetAssetPath = null; string databaseAssetPath = null; static Dictionary inspectorSate = new Dictionary(); 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 Lua packagePath = AssetBundleUtility.AssetsPathToPackagePath(targetAssetPath); // packagePath // 文件对应的数据库目录下 xxx.asset文件; databaseAssetPath = AssetBundleInspectorUtils.AssetPathToDatabasePath(targetAssetPath); // 加载数据库文件 例如: Lua.asset dispatcherConfig = AssetDatabase.LoadAssetAtPath(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(); AssetDatabase.CreateAsset(instance, databaseAssetPath); // 将这个对象实例--->创建到.asset文件里面; AssetDatabase.Refresh(); // 重新同步一下到当前的对象里面; Initialize(); // 调用Repaint时候----》 引发 OnInspectorGUI Repaint(); } } 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 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; } // 删除数据库文件;databaseAssetPath GameUtility.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(); // 检查一下,这个路径,是否为AssetsPackage if (!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 /// /// 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的包(还正在加载)不会被清理 /// namespace AssetBundles { [Hotfix] [LuaCallCSharp] public class AssetBundleManager : UnitySingleton { // 最大同时进行的ab创建数量 const int MAX_ASSETBUNDLE_CREATE_NUM = 5; // manifest:提供依赖关系查找以及hash值比对 Manifest manifest = null; // 资源路径相关的映射表 AssetsPathMapping assetsPathMapping = null; // 常驻ab包:需要手动添加公共ab包进来,常驻包不会自动卸载(即使引用计数为0),引用计数为0时可以手动卸载 HashSet assetbundleResident = new HashSet(); // ab缓存包:所有目前已经加载的ab包,包括临时ab包与公共ab包 Dictionary assetbundlesCaching = new Dictionary(); // ab缓存包引用计数:卸载ab包时只有引用计数为0时才会真正执行卸载 Dictionary assetbundleRefCount = new Dictionary(); // asset缓存:给非公共ab包的asset提供逻辑层的复用 Dictionary assetsCaching = new Dictionary(); // 加载数据请求:正在prosessing或者等待prosessing的资源请求 Dictionary webRequesting = new Dictionary(); // 等待处理的资源请求 Queue webRequesterQueue = new Queue(); // 正在处理的资源请求 List prosessingWebRequester = new List(); // 逻辑层正在等待的ab加载异步句柄 List prosessingAssetBundleAsyncLoader = new List(); // 逻辑层正在等待的asset加载异步句柄 List prosessingAssetAsyncLoader = new List(); 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..."); } #endif public IEnumerator Initialize() { #if UNITY_EDITOR if (AssetBundleConfig.IsEditorMode) { yield break; } #endif manifest = 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(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_EDITOR if (AssetBundleConfig.IsEditorMode) { yield break; } #endif // 等待所有请求完成 // 要是不等待Unity很多版本都有各种Bug yield 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_EDITOR if (AssetBundleConfig.IsEditorMode) { return; } #endif if (!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(); 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_EDITOR if (AssetBundleConfig.IsEditorMode) { return new EditorAssetBundleAsyncLoader(assetbundleName); } #endif var 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; } // 从服务器下载网页内容,需提供完整url public 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_EDITOR if (AssetBundleConfig.IsEditorMode) { string path = AssetBundleUtility.PackagePathToAssetsPath(assetPath); UnityEngine.Object target = AssetDatabase.LoadAssetAtPath(path, assetType); return new EditorAssetAsyncLoader(target); } #endif string 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 GetAssetbundleResident() { return assetbundleResident; } [BlackList] public ICollection GetAssetbundleCaching() { return assetbundlesCaching.Keys; } [BlackList] public Dictionary GetWebRequesting() { return webRequesting; } [BlackList] public Queue GetWebRequestQueue() { return webRequesterQueue; } [BlackList] public List GetProsessingWebRequester() { return prosessingWebRequester; } [BlackList] public List GetProsessingAssetBundleAsyncLoader() { return prosessingAssetBundleAsyncLoader; } [BlackList] public List GetProsessingAssetAsyncLoader() { return prosessingAssetAsyncLoader; } [BlackList] public string GetAssetBundleName(string assetName) { return assetsPathMapping.GetAssetBundleName(assetName); } [BlackList] public int GetAssetCachingCount() { return assetsCaching.Count; } [BlackList] public Dictionary> GetAssetCaching() { var assetbundleDic = new Dictionary>(); List 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(); } 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 GetAssetBundleRefrences(string assetbundleName) { List refrences = new List(); 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 GetWebRequesterRefrences(string assetbundleName) { List refrences = new List(); 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 GetAssetBundleLoaderRefrences(string assetbundleName) { List refrences = new List(); 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(); this.gameObject.AddComponent(); this.gameObject.AddComponent(); // end xLuaMgr.Instance.Init(); } IEnumerator InitPackageName() { #if UNITY_EDITOR if (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 { 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(path); return target; } #endif return 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(); // 实例化一个AssetBudnleManager; this.gameObject.AddComponent(); this.gameObject.AddComponent(); this.gameObject.AddComponent(); // end xLuaMgr.Instance.Init(); } IEnumerator InitPackageName() { #if UNITY_EDITOR if (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 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脚本中AddComponentAddComponent之后,我们就可以在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(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>() { }; 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