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/LuaScripts
            scriptPath = 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") 
end

main.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.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

        /*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();
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<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()
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<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 = 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<show_fps>();
        this.gameObject.AddComponent<xLuaMgr>();
        this.gameObject.AddComponent<ResMgr>();
        // 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<xLuaMgr> {
    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<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

/// 
/// 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<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...");
        }
#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<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_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<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_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<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所在目录 + 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");
    }
}

切换代码启动模式

分为两种模式:

  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

/// 
/// 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<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;

/// 
/// 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<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   Lua
            packagePath = AssetBundleUtility.AssetsPathToPackagePath(targetAssetPath); // packagePath

            // 文件对应的数据库目录下 xxx.asset文件;
            databaseAssetPath = AssetBundleInspectorUtils.AssetPathToDatabasePath(targetAssetPath);

            // 加载数据库文件 例如: Lua.asset
            dispatcherConfig = 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时候----》 引发 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<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;
            }
            // 删除数据库文件;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<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...");
        }
#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<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_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<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_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<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_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<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;
        }
#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<AssetBundleManager>(); // 实例化一个AssetBudnleManager;
        this.gameObject.AddComponent<show_fps>();
        this.gameObject.AddComponent<xLuaMgr>();
        this.gameObject.AddComponent<ResMgr>();
        // 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#通讯原理(重要)

应用

  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

你可能感兴趣的:(游戏开发,lua,Xlua)