XLua热更新框架原理和代码实战

安装插件

下载Xlua插件:https://github.com/Tencent/xLua

下载完成后,把Asset文件夹下的文件拖入自己的工程Asset中,看到Unity编辑器上多了个Xlua菜单,说明插件导入成功

Lua启动代码

新建一个空场景,场景中什么都不放,只有一个启动脚本,所有的东西都从启动脚本中加载,这样打包时才能没有依赖,所有资源支持热更。

启动脚本

GameLaunch:

csharp 复制代码
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管理脚本

csharp 复制代码
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

Lua 复制代码
require("game.game_start")

main = {} -- main是一个全局模块;

local function start()
	print("game started") 
end

main.start = start
return main

通用脚本:单例

csharp 复制代码
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

csharp 复制代码
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脚本:

lua 复制代码
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

lua 复制代码
-- 返回一个基类为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组件化管理

管理脚本:

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 

添加组件方法

例如有一个控制物体移动的脚本:

lua 复制代码
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#当中的。

调用这个脚本时:

lua 复制代码
local cube_ctrl = require("game.cube_ctrl")
local obj = LuaGameObject.Find("Cube")
LuaGameObject.AddLuaComponent(obj, cube_ctrl)

例子:

lua 复制代码
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目录下作为编辑器脚本:

csharp 复制代码
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文件夹存放模板,里面的文件如下:

lua 复制代码
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中重新进行定义:

lua 复制代码
local Time = CS.UnityEngine.Time

另外,在上面组件化开发模式中,我们可以直接才lua模块中使用obj.gameObject,obj.transform来获取对应物体和对应物体的transform的信息。

C#端创建一个脚本:

csharp 复制代码
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调用这个脚本

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

使用的时候:

lua 复制代码
local ResMgr = require("managers.ResMgr")
ResMgr.GetAssetCache("test","test_type")
--回调函数
ResMgr.LoadAssetBundleAsync("name", function() print("C#") end)

资源管理

编辑器内部资源启动和AssetBundle启动游戏

启动游戏脚本
csharp 复制代码
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
csharp 复制代码
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

*/

管理器代码

渠道管理器框架
csharp 复制代码
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管理器框架
csharp 复制代码
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using XLua;
using System;
#if UNITY_EDITOR
using UnityEditor;
#endif

/// <summary>
/// added by wsh @ 2017-12-21
/// 功能:assetbundle管理类,为外部提供统一的资源加载界面、协调Assetbundle各个子系统的运行
/// 注意:
/// 1、抛弃Resources目录的使用,官方建议:https://unity3d.com/cn/learn/tutorials/temas/best-practices/resources-folder?playlist=30089
/// 2、提供Editor和Simulate模式,前者不适用Assetbundle,直接加载资源,快速开发;后者使用Assetbundle,用本地服务器模拟资源更新
/// 3、场景不进行打包,场景资源打包为预设
/// 4、只提供异步接口,所有加载按异步进行
/// 5、采用LZMA压缩方式,性能瓶颈在Assetbundle加载上,ab加载异步,asset加载同步,ab加载后导出全部asset并卸载ab
/// 6、所有公共ab包(被多个ab包依赖)常驻内存,非公共包加载asset以后立刻卸载,被依赖的公共ab包会随着资源预加载自动加载并常驻内存
/// 7、随意卸载公共ab包可能导致内存资源重复,最好在切换场景时再手动清理不需要的公共ab包
/// 8、常驻包(公共ab包)引用计数不为0时手动清理无效,正在等待加载的所有ab包不能强行终止---一旦发起创建就一定要等操作结束,异步过程进行中清理无效
/// 9、切换场景时最好预加载所有可能使用到的资源,所有加载器用完以后记得Dispose回收,清理GC时注意先释放所有Asset缓存
/// 10、逻辑层所有Asset路径带文件类型后缀,且是AssetBundleConfig.ResourcesFolderName下的相对路径,注意:路径区分大小写
/// TODO:
/// 1、区分场景常驻包和全局公共包,切换场景时自动卸载场景公共包
/// 使用说明:
/// 1、由Asset路径获取AssetName、AssetBundleName:ParseAssetPathToNames
/// 2、设置常驻(公共)ab包:SetAssetBundleResident(assebundleName, true)---公共ab包已经自动设置常驻
/// 2、(预)加载资源:var loader = LoadAssetBundleAsync(assetbundleName),协程等待加载完毕后Dispose:loader.Dispose()
/// 3、加载Asset资源:var loader = LoadAssetAsync(assetPath, TextAsset),协程等待加载完毕后Dispose:loader.Dispose()
/// 4、离开场景清理所有Asset缓存:ClearAssetsCache(),UnloadUnusedAssetBundles(), Resources.UnloadUnusedAssets()
/// 5、离开场景清理必要的(公共)ab包:TryUnloadAssetBundle(),注意:这里只是尝试卸载,所有引用计数不为0的包(还正在加载)不会被清理
/// </summary>

namespace AssetBundles
{
    [Hotfix]
    [LuaCallCSharp]
    public class AssetBundleManager : UnitySingleton<AssetBundleManager>
    {
        // 最大同时进行的ab创建数量
        const int MAX_ASSETBUNDLE_CREATE_NUM = 5;
        // manifest:提供依赖关系查找以及hash值比对
        Manifest manifest = null;
        // 资源路径相关的映射表
        AssetsPathMapping assetsPathMapping = null;
        // 常驻ab包:需要手动添加公共ab包进来,常驻包不会自动卸载(即使引用计数为0),引用计数为0时可以手动卸载
        HashSet<string> assetbundleResident = new HashSet<string>();
        // ab缓存包:所有目前已经加载的ab包,包括临时ab包与公共ab包
        Dictionary<string, AssetBundle> assetbundlesCaching = new Dictionary<string, AssetBundle>();
        // ab缓存包引用计数:卸载ab包时只有引用计数为0时才会真正执行卸载
        Dictionary<string, int> assetbundleRefCount = new Dictionary<string, int>(); 
        // asset缓存:给非公共ab包的asset提供逻辑层的复用
        Dictionary<string, UnityEngine.Object> assetsCaching = new Dictionary<string, UnityEngine.Object>();
        // 加载数据请求:正在prosessing或者等待prosessing的资源请求
        Dictionary<string, ResourceWebRequester> webRequesting = new Dictionary<string, ResourceWebRequester>();
        // 等待处理的资源请求
        Queue<ResourceWebRequester> webRequesterQueue = new Queue<ResourceWebRequester>();
        // 正在处理的资源请求
        List<ResourceWebRequester> prosessingWebRequester = new List<ResourceWebRequester>();
        // 逻辑层正在等待的ab加载异步句柄
        List<AssetBundleAsyncLoader> prosessingAssetBundleAsyncLoader = new List<AssetBundleAsyncLoader>();
        // 逻辑层正在等待的asset加载异步句柄
        List<AssetAsyncLoader> prosessingAssetAsyncLoader = new List<AssetAsyncLoader>();

        public static string ManifestBundleName
        {
            get;
            set;
        }

#if UNITY_EDITOR || CLIENT_DEBUG
#if !CLIENT_DEBUG
        [BlackList]
#endif
        // Hotfix测试---用于侧测试资源模块的热修复
        public void TestHotfix()
        {
            Debug.Log("********** AssetBundleManager : Call TestHotfix in cs...");
        }
#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帧启动一次。

代码:

csharp 复制代码
if(Time.frameCount % 100 == 0){
    this.luaEnv.Tick();
}

AssetBundle菜单工具

定义菜单宏

csharp 复制代码
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结尾,这样能打成二进制包,并且复制到对应的路径下

csharp 复制代码
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读取资源
csharp 复制代码
// 点击编辑器模式按钮
 [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中

csharp 复制代码
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
using System.IO;
#endif

/// <summary>
/// added by wsh @ 2017.12.25
/// 注意:
/// 1、所有ab路径中目录、文件名不能以下划线打头,否则出包时StreamingAssets中的资源不能打到真机上,很坑爹
/// </summary>

namespace AssetBundles
{
    public class AssetBundleConfig
    {
        public const string localSvrAppPath = "Editor/AssetBundle/LocalServer/AssetBundleServer.exe";
        public const string AssetBundlesFolderName = "AssetBundles";
        public const string AssetBundleSuffix = ".assetbundle";
        public const string AssetsFolderName = "AssetsPackage";
        public const string ChannelFolderName = "Channel";
        public const string AssetsPathMapFileName = "AssetsMap.bytes";
        public const string VariantsMapFileName = "VariantsMap.bytes";
        public const string AssetBundleServerUrlFileName = "AssetBundleServerUrl.txt";
        public const string VariantMapParttren = "Variant";
        public const string CommonMapPattren = ",";
        
#if UNITY_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
    }
}

渠道和版本管理、打包工具

渠道配置文件:

csharp 复制代码
// 目前就设置了Test渠道
namespace GameChannel
{
    public enum ChannelType
    {
        Test,
    }
}

打包设置:

csharp 复制代码
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:

csharp 复制代码
    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文件中

这个脚本定义了基本的数据结构

csharp 复制代码
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

csharp 复制代码
using UnityEngine;
using UnityEditor;
using System.IO;
using System.Collections.Generic;

/// <summary>
/// added by wsh @ 2018.01.06
/// 说明:Assetbundle分发器Inspector,为其提供可视化的编辑界面
/// TODO:
/// 1、还未完成,目前只是做了基本的配置功能
/// </summary>

namespace AssetBundles
{
    [CustomEditor(typeof(DefaultAsset), true)]
    public class AssetBundleDispatcherInspector : Editor
    {
        AssetBundleDispatcherConfig dispatcherConfig = null;
        string packagePath = null;
        string targetAssetPath = null;
        string databaseAssetPath = null;

        static Dictionary<string, bool> inspectorSate = new Dictionary<string, bool>();
        AssetBundleDispatcherFilterType filterType = AssetBundleDispatcherFilterType.Root;
        bool configChanged = false;

        void OnEnable()
        {
            Initialize();
        }

        // 每次选中这个文件夹的时候,我们会调用Initialize;
        void Initialize()
        {
            configChanged = false;
            filterType = AssetBundleDispatcherFilterType.Root; // 默认的打包方式;
            targetAssetPath = AssetDatabase.GetAssetPath(target); // 获取我们选的当前的路径;
            if (!AssetBundleUtility.IsPackagePath(targetAssetPath)) // 这个路径是否在AssetsPackages路径下;
            {
                return;
            }

            // Assets/AssetsPackage/Lua  ---> pakcage path   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来检查

正式打包

csharp 复制代码
[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

csharp 复制代码
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

csharp 复制代码
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脚本作为单例,在一开始就加载进来。

csharp 复制代码
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using XLua;
using System;
#if UNITY_EDITOR
using UnityEditor;
#endif

/// <summary>
/// added by wsh @ 2017-12-21
/// 功能:assetbundle管理类,为外部提供统一的资源加载界面、协调Assetbundle各个子系统的运行
/// 注意:
/// 1、抛弃Resources目录的使用,官方建议:https://unity3d.com/cn/learn/tutorials/temas/best-practices/resources-folder?playlist=30089
/// 2、提供Editor和Simulate模式,前者不适用Assetbundle,直接加载资源,快速开发;后者使用Assetbundle,用本地服务器模拟资源更新
/// 3、场景不进行打包,场景资源打包为预设
/// 4、只提供异步接口,所有加载按异步进行
/// 5、采用LZMA压缩方式,性能瓶颈在Assetbundle加载上,ab加载异步,asset加载同步,ab加载后导出全部asset并卸载ab
/// 6、所有公共ab包(被多个ab包依赖)常驻内存,非公共包加载asset以后立刻卸载,被依赖的公共ab包会随着资源预加载自动加载并常驻内存
/// 7、随意卸载公共ab包可能导致内存资源重复,最好在切换场景时再手动清理不需要的公共ab包
/// 8、常驻包(公共ab包)引用计数不为0时手动清理无效,正在等待加载的所有ab包不能强行终止---一旦发起创建就一定要等操作结束,异步过程进行中清理无效
/// 9、切换场景时最好预加载所有可能使用到的资源,所有加载器用完以后记得Dispose回收,清理GC时注意先释放所有Asset缓存
/// 10、逻辑层所有Asset路径带文件类型后缀,且是AssetBundleConfig.ResourcesFolderName下的相对路径,注意:路径区分大小写
/// TODO:
/// 1、区分场景常驻包和全局公共包,切换场景时自动卸载场景公共包
/// 使用说明:
/// 1、由Asset路径获取AssetName、AssetBundleName:ParseAssetPathToNames
/// 2、设置常驻(公共)ab包:SetAssetBundleResident(assebundleName, true)---公共ab包已经自动设置常驻
/// 2、(预)加载资源:var loader = LoadAssetBundleAsync(assetbundleName),协程等待加载完毕后Dispose:loader.Dispose()
/// 3、加载Asset资源:var loader = LoadAssetAsync(assetPath, TextAsset),协程等待加载完毕后Dispose:loader.Dispose()
/// 4、离开场景清理所有Asset缓存:ClearAssetsCache(),UnloadUnusedAssetBundles(), Resources.UnloadUnusedAssets()
/// 5、离开场景清理必要的(公共)ab包:TryUnloadAssetBundle(),注意:这里只是尝试卸载,所有引用计数不为0的包(还正在加载)不会被清理
/// </summary>

namespace AssetBundles
{
    [Hotfix]
    [LuaCallCSharp]
    public class AssetBundleManager : UnitySingleton<AssetBundleManager>
    {
        // 最大同时进行的ab创建数量
        const int MAX_ASSETBUNDLE_CREATE_NUM = 5;
        // manifest:提供依赖关系查找以及hash值比对
        Manifest manifest = null;
        // 资源路径相关的映射表
        AssetsPathMapping assetsPathMapping = null;
        // 常驻ab包:需要手动添加公共ab包进来,常驻包不会自动卸载(即使引用计数为0),引用计数为0时可以手动卸载
        HashSet<string> assetbundleResident = new HashSet<string>();
        // ab缓存包:所有目前已经加载的ab包,包括临时ab包与公共ab包
        Dictionary<string, AssetBundle> assetbundlesCaching = new Dictionary<string, AssetBundle>();
        // ab缓存包引用计数:卸载ab包时只有引用计数为0时才会真正执行卸载
        Dictionary<string, int> assetbundleRefCount = new Dictionary<string, int>(); 
        // asset缓存:给非公共ab包的asset提供逻辑层的复用
        Dictionary<string, UnityEngine.Object> assetsCaching = new Dictionary<string, UnityEngine.Object>();
        // 加载数据请求:正在prosessing或者等待prosessing的资源请求
        Dictionary<string, ResourceWebRequester> webRequesting = new Dictionary<string, ResourceWebRequester>();
        // 等待处理的资源请求
        Queue<ResourceWebRequester> webRequesterQueue = new Queue<ResourceWebRequester>();
        // 正在处理的资源请求
        List<ResourceWebRequester> prosessingWebRequester = new List<ResourceWebRequester>();
        // 逻辑层正在等待的ab加载异步句柄
        List<AssetBundleAsyncLoader> prosessingAssetBundleAsyncLoader = new List<AssetBundleAsyncLoader>();
        // 逻辑层正在等待的asset加载异步句柄
        List<AssetAsyncLoader> prosessingAssetAsyncLoader = new List<AssetAsyncLoader>();

        public static string ManifestBundleName
        {
            get;
            set;
        }

#if UNITY_EDITOR || CLIENT_DEBUG
#if !CLIENT_DEBUG
        [BlackList]
#endif
        // Hotfix测试---用于侧测试资源模块的热修复
        public void TestHotfix()
        {
            Debug.Log("********** AssetBundleManager : Call TestHotfix in cs...");
        }
#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

csharp 复制代码
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

csharp 复制代码
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();
    }
}

代码热更

具体热更步骤从前面的文章查看,简单来说,是比较本地版本和服务器版本,再看看哪些有变动,然后下载。

csharp 复制代码
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. 这些Wrap文件会在XLuaGenAutoRegister脚本中统一被调用注册和导出函数,这些都放在脚本的初始化函数中,初始化函数在虚拟机启动时调用。

以ResMgrWrap示例

csharp 复制代码
 #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);
            }
            
        }      	
    }
}
csharp 复制代码
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

相关推荐
老狼孩111228 小时前
2025新版懒人精灵零基础及各板块核心系统视频教程-全分辨率免ROOT自动化开发
android·机器人·自动化·lua·脚本开发·懒人精灵·免root开发
珠峰下的沙砾15 小时前
如何在 Postman 中,自动获取 Token 并将其赋值到环境变量
测试工具·lua·postman
时光话1 天前
Lua 第9部分 闭包
开发语言·lua
时光话1 天前
Lua 第7部分 输入输出
开发语言·lua
Hy行者勇哥3 天前
使用Postman调测“获取IAM用户Token”接口实际操作
测试工具·lua·postman
加油,旭杏5 天前
【Lua语言】Lua语言快速入门
开发语言·lua
徐同保5 天前
fetch使用put请求提交文件,postman使用put请求提交文件
测试工具·lua·postman
码到成功>_<8 天前
postman使用技巧
测试工具·lua·postman
King.6249 天前
SQL2API 核心理念:如何重构数据服务交付范式
大数据·开发语言·数据库·人工智能·sql·lua
巨龙之路10 天前
Lua中的元表
java·开发语言·lua