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");}
}

切换代码启动模式

分为两种模式:

  1. 编辑器模式,直接从代码lua脚本读取
  2. 模拟模式,从打包的AssetBundle读取资源
  3. 发布模式,从打包的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#通讯原理(重要)

应用

  1. 想要在Lua添加C#写的组件,需要在代码中加上[LuaCallCSharp],在Lua脚本中AddComponent
  2. AddComponent之后,我们就可以在lua中利用这个组件调用其中的函数了

原理

  1. [LuaCallCSharp]这个注解会做lua导出,在Xlua插件中的Gen文件夹有很多Wrap代码,当点击Xlua插件的生成代码按钮后,拥有[LuaCallCSharp]注解的类会写到一个link.xml文件中,并会导出对应的包装文件到wrap文件夹中,导出后Lua才能调用C#脚本中编写的函数。
  2. 要想通过lua调用这个方法,lua类型中的元表必须有这个方法,因此在C#端需要把元表设置好。Utils.BeginObjectRegister往类型中添加了元表,加了元表后,把方法都注册到了元表中
  3. 这些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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/149823.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Threejs_06 多材质的实现

Threejs 同一个几何体如何实现多材质呢&#xff1f; 多材质的实现 1.使用索引绘制一个几何体 //创建几何体(三角形) const geometry new THREE.BufferGeometry();//使用索引绘制 (两个共用的) const vertices new Float32Array([-1.0, -1.0, 0.0, 1.0, -1.0, 0.0, 1.0, 1…

谈谈系统性能调优中都需要考虑哪些因素

一、 什么是性能调优&#xff1f; 这个系统好慢、网站又打不开了&#xff0c;太卡了&#xff0c;又没响应了&#xff0c;相信大家都遇到过用户的这种抱怨&#xff0c;此时&#xff0c;说明我们的应用系统出现了性能问题&#xff0c;那么怎么办呢&#xff0c;首先想到的应该是优…

npm私有云

安装node时npm会自动安装&#xff0c;npm也可以单独安装。 package.json 在使用npm时&#xff0c;package.json文件是非常重要的&#xff0c;因为它包含了关于项目的必要信息&#xff0c;比如名称、版本、依赖项等。在初始化新项目时&#xff0c;通常会使用npm init命令生成一…

HP惠普暗影精灵9笔记本电脑OMEN by HP Transcend 16英寸游戏本16-u0000原厂Windows11系统

惠普暗影9恢复出厂开箱状态&#xff0c;原装出厂Win11-22H2系统ISO镜像 下载链接&#xff1a;https://pan.baidu.com/s/17ftbBHEMFSEOw22tnYvPog?pwd91p1 提取码&#xff1a;91p1 适用型号&#xff1a;16-u0006TX、16-u0007TX、16-u0008TX、16-u0009TX、16-u0017TX 原厂系…

每天一道算法题(五)——判断一组数字是否连续,出现连续数字的时候以‘-’输出

文章目录 1、问题2、示例3、解决方法&#xff08;0&#xff09;错误示范——两个for循环遍历&#xff08;1&#xff09;方法1(递归)&#xff08;2&#xff09;方法2&#xff08;推荐&#xff09; 1、问题 实现一个函数&#xff0c;判断一组数字是否连续。当出现连续数字的时候以…

数据结构与算法编程题2

逆置线性表&#xff0c;使空间复杂度为 O(1) #include <iostream> using namespace std;typedef int ElemType; #define Maxsize 100 #define OK 1 #define ERROR 0 typedef struct SqList {ElemType data[Maxsize];int length; }SqList;void Init_SqList(SqList& …

YOLOV8部署Android Studio安卓平台NCNN

下载Android Studio&#xff0c;配置安卓开发环境&#xff0c;这个过程比较漫长。 安装cmake&#xff0c;注意安装的是cmake3.10版本。 根据手机安卓版本选择相应的安卓版本&#xff0c;我的是红米K30Pro&#xff0c;安卓12。 使用腾讯开源的ncnn&#xff0c;这是一个为手机端极…

驶入产业发展快车道,汉鑫科技人工智能研发中心正式启用!

11月18日&#xff0c;汉鑫科技人工智能研发中心正式启用。中心立足烟台&#xff0c;服务全国&#xff0c;聚焦工业智能、智能网联、智慧城市三大业务板块&#xff0c;以人工智能技术赋能政企实现“数智化”转型升级。该中心的启用标志着汉鑫科技在人工智能研发应用领域迈上了新…

移动端表格分页uni-app

使用uni-app提供的uni-table表格 网址&#xff1a;https://uniapp.dcloud.net.cn/component/uniui/uni-table.html#%E4%BB%8B%E7%BB%8D <uni-table ref"table" :loading"loading" border stripe type"selection" emptyText"暂无更多数据…

Java的构造器

构造器 1.package com.msb2; 2. 3./** 4. * Auther: msb-zhaoss 5. */ 6.public class Person { 7. //构造器&#xff1a;没有任何参数的构造器我们叫做&#xff1a;空参构造器--》空构造器 8. public Person(){ 9. /*age 19; 10. name "lili"…

【Nacos】配置管理、微服务配置拉取、实现配置热更新、多环境配置

&#x1f40c;个人主页&#xff1a; &#x1f40c; 叶落闲庭 &#x1f4a8;我的专栏&#xff1a;&#x1f4a8; c语言 数据结构 javaEE 操作系统 Redis 石可破也&#xff0c;而不可夺坚&#xff1b;丹可磨也&#xff0c;而不可夺赤。 Nacos 一、nacos实现配置管理1.1 统一配置管…

Taro安装及使用

安装及使用 安装​ Taro 项目基于 node&#xff0c;请确保已具备较新的 node 环境&#xff08;>12.0.0&#xff09;&#xff0c;推荐使用 node 版本管理工具 nvm 来管理 node&#xff0c;这样不仅可以很方便地切换 node 版本&#xff0c;而且全局安装时候也不用加 sudo 了…

不必购买Mac,这款国产设计工具能轻松替代Sketch!

介绍 即时设计是新一代可以直接在浏览器中使用的设计工具&#xff0c;具有Sketch和实时协作功能。与本地Sketch相比&#xff0c;增加了实时协作功能&#xff0c;即时设计可以看作是在线Sketch&#xff0c;两个工具可以简单粗暴地总结为一个公式&#xff1a; 即时设计Sketch云…

Nginx - 本机读取服务器图像、视频

目录 一.引言 二.安装 Nginx 1.安装 By apt 2.安装 By 官网 三.配置 Nginx 1.Linux 机器配置 2.重启 Nginx 服务 3.本机查看机器文件 四.总结 一.引言 前面介绍了 GFP-GAN、PNG2GIF、GIF2PNG 等操作&#xff0c;我们生成的 video、gif、png 等形式的文件都存储在 lin…

Nginx(反向代理,负载均衡,动静分离)

反向代理 Nginx反向代理是一种将客户端请求转发给后端服务器的技术&#xff0c;即反向代理服务器。在这种架构中&#xff0c;客户端请求首先到达Nginx服务器&#xff0c;然后由Nginx服务器将请求转发给后端服务器&#xff0c;后端服务器响应请求&#xff0c;并将响应传递回Ngi…

【wp】2023第七届HECTF信息安全挑战赛 Web

伪装者 考点&#xff1a;http协议flask的session伪造ssrf读取文件 首先根据题目要求就行伪造HTTP 这里不多说&#xff0c;比较基础 然后下面得到是个登入 页面&#xff0c;我们输入zxk1ing 得到 说什么要白马王子 &#xff0c;一眼session伪造 看到ey开头感觉是jwt 输入看看 得…

通达信的ebk文件

我们在通达信软件中 调出 “自定义板块设置” 这个菜单&#xff0c;点击“导出”&#xff0c;会提示你存储 “自选股.EBK”&#xff0c;其实就是对自定义板块里的目录进行备份的一种方式&#xff0c; 当我们打开 这个文件&#xff0c;你会发现其实就是存储了 股票代码&#xff…

CRM系统定制开发价格

我们都知道&#xff0c;CRM系统对企业有着很大的帮助。但是市面上大多数CRM系统都是标准化的&#xff0c;无法满足那些产品线复杂&#xff0c;或者有着特殊需求的企业。这个时候&#xff0c;就需要对CRM系统进行二次开发。那么&#xff0c;CRM系统二次开发的价格是多少&#xf…

快手运营的必备的10个工具

一、引言 快手作为短视频领域的佼佼者&#xff0c;为众多创作者提供了广阔的舞台。要想在快手运营中取得成功&#xff0c;掌握一些必备的工具是必不可少的。本文将为您介绍快手运营的10个必备工具&#xff0c;帮助您提高工作效率&#xff0c;优化内容创作。 二、工具推荐 1. …

Linux(4):Linux文件与目录管理

目录与路径 相对路径在进行软件或软件安装时非常有用&#xff0c;更加方便。利用相对路径的写法必须要确认目前的路径才能正确的去到想要去的目录。 绝对路径的正确度要比相对路径好&#xff0c;因此&#xff0c;在写程序&#xff08;shell scripts&#xff09;来管理系统的条…