热更新-AB包/Lua

AB包

配置路径:自定义的,但是一定要有一个类去存储路径

打个AB包

1.选一个文件,最右下角,加入某个AB包,如果AB包名字中有斜杠如“ui/test”,那么他就会新建个“ui”文件夹,然后里面是“test”AB包文件(AB包内不存在目录):

2.写个Editor代码(放在了顶部菜单栏):

导出选项是一个多选枚举:

3.点

点完之后就会在目标目录下生成一个AB包了。

加载AB包

加载依赖

Simple02是打包进“pre”的预制体,预制体中的Image.Sprite是打包进“ui”的精灵。

此时,打出的AB包会有依赖关系:AB包ui没有依赖关系,AB包pre有一个依赖:ui

加载有依赖的AB包内部数据

如果想处理依赖关系的加载,则必须加载主AB包,因为依赖关系的存储,都存储在主AB包的配置文件中

第一步:(加载依赖的AB包文件)

  1. 加载主AB包
  2. 根据主AB包的配置文件,获得当前需要的AB包所依赖的AB包们
  3. 将所有依赖的AB包们加载进来

第二步:(加载AB包文件)

  1. AB包 = AssetBundle.LoadFromFile(“路径”);
  2. AB包 = AssetBundle.LoadFromFileSync(“路径”);

第三步:(加载包内部资源)

  1. T obj = AB包.LoadAsset<T>("名字");
  2. T obj = AB包.LoadAssetSync<T>("名字");

第四步:(释放AB包内存)

  AB包.Unload(false);

AB包不能重复加载!

异步加载

AB包类似

内存相关

初始运行时440MB

生成AB包478MB:

生成图片后不变:

卸载AB包440.4MB(图片大小192kb)

以上三步循环6次,442.8MB:

回收,回到初始状态附近:

代码:

解释:加载AB包时,会把整个AB包加载到内存里440MB->480MB;

生成图片时,就是加了个指针;

卸载AB包,用的Unload(false),将指针指向的图片留了下来,440->440.8MB;

重复操作,Unity为懒回收:资源空闲后不回收。因此会一直存在于内存440.8->442.8MB;

回收后,空闲资源都没了;

Destroy仅销毁游戏对象,不销毁内存空闲资源,因此要在后续释放内存;

Resources.UnloadUnusedAssets();不能频繁调用;

LUA

Why

像AB包,就是在代码运行时,还没有读取到目标AB包,可以从网上下载一个AB包,然后去读;

而C#不行,在代码运行时,所有的C#代码就是一个文件,如果要更新某一部分的C#文件,就得把整个项目关掉;

因此引入了LUA,在C#运行时,还没有运行到的LUA,可以删掉,然后下个新的放进来;

而这,就是热更新。

What

How

我们直接来讲案例

使用LUA实现一个这样的功能:其中,图片和该布局是从AB包里加载出来的(预制体依赖UI);53是LUA读写Json的持久化数据

事先准备:

1.自然是要一个存放ui图片和预制体的AB包:其中,是pre依赖ui。

2.引擎布局:一个相机、一个画布(也可以没有,但用LUA去创建太麻烦);画布上挂了一个脚本

3.自然是在引擎里要几个函数去读取LUA文件:LiveCycle

a.首先,我们需要xLua插件sdk,github有,直接下就行

b.对xLua的sdk进行封装:

C#封装的xLua
using System.IO;
using XLua;

namespace lua
{
    /// <summary>
    /// Lua环境单例
    /// </summary>
    public class xLuaEnv
    {
        #region Singleton
        private static xLuaEnv instance;
        public static xLuaEnv Instance
        { 
            get
            { 
                if (instance == null)
                {
                    instance = new xLuaEnv();
                }
                return instance;
            } 
        }
        #endregion

        #region CreatLuaEnv
        private LuaEnv Env;
        private xLuaEnv()
        {
            Env = new LuaEnv();
            Env.AddLoader(data);
        }
        #endregion

        #region Loader
        public byte[] data(ref string path)//返回的路径
        {
            path = E.Config.LuaPath + path + ".lua";
            if (File.Exists(path)) return File.ReadAllBytes(path);
            else return null;
        }
        #endregion

        #region FreeEnv
        public void Free()
        {
            Env.Dispose();
            instance = null;
        }
        #endregion

        #region RunEnv
        public object[] DoString(string code)
        {
            return Env.DoString(code);
        }
        #endregion

        #region EnvGlobal
        public LuaTable Global
        {
            get { return Env.Global; }
            private set { }
        }
        #endregion
    }
}

tips:

Singleton:做成了一个单例,不解释了

CreatLuaEnv:需要创建一个Lua运行环境,但这还不够,运行某个lua文件时需要require("LuaName.lua")那环境肯定不知道去哪找这个文件,因此,看data

Loader:在运行某个Lua文件时(DoString(string code)),会传一个字符串,如果定义了AddLoader,就会把字符串传到这,然后使用IO把读到的lua文件以字节数组的形式传出去

FreeEnv:释放资源,减少内存占用

RunEnv:运行传进来的lua文件

EnvGlobal:像就是在一个目录下新建了一个lua文件去运行LiveCycle.lua文件,但其实我们需要的是LiveCycle.lua文件

因此使用该函数去拿到这个文件,并且绑定起来:

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
using XLua;

namespace lua
{
    public delegate void LC();
    public struct LuaLiveCycle
    {
        public LC Start;
        public LC Update;
        public LC OnDestory;
        public string name;
    }

    /// <summary>
    /// 
    /// </summary>
    public class LiveCycle : MonoBehaviour
    {
        LuaLiveCycle _luaLiveCycle = new LuaLiveCycle();

        private void Start()
        {
            xLuaEnv.Instance.DoString("require('LiveCycle')");
            _luaLiveCycle = xLuaEnv.Instance.Global.Get<LuaLiveCycle>("LiveCycle");
            _luaLiveCycle.Start();
            _luaLiveCycle.Update();

            //TestFunc();
        }

        private void TestFunc()
        {
            GameObject go = new GameObject("img");
            go.transform.SetParent(GameObject.Find("Canvas").transform);
            Image img = go.AddComponent<Image>();

            AssetBundle ab = AssetBundle.LoadFromFile(E.Config.NewABPath + "ui");
            img.sprite = ab.LoadAsset<Sprite>("奥拓");
            ab.Unload(false);

            RectTransform rt = go.transform as RectTransform;
            rt.localPosition = Vector3.zero;
            rt.sizeDelta = new Vector2(1920, 1080);
        }

        private void Update()
        {
            
        }

        private void OnDestroy()
        {
            _luaLiveCycle.OnDestory();

            _luaLiveCycle.Start = null;
            _luaLiveCycle.Update = null;
            _luaLiveCycle.OnDestory = null;

            xLuaEnv.Instance.Free();
        }
    }
}

用委托去存定义要存lua方法的委托

用结构体去定义LiveCycle.lua,里面有所有LiveCycle的方法(只要名字一样,会自动绑定)

Lua部分:MVC框架,还有一个AB包加载器,还有个读写json

首先就是LiveCycle.lua:

LiveCycle={};

LiveCycle.Controllers={};

LiveCycle.Start=function ()
    LiveCycle.LoadPage("ImageController");
end

LiveCycle.Update=function ()
    for k,v in pairs(LiveCycle.Controllers) do
        if(v.Update~=nil)
        then
            v.Update();
        end
    end
end

LiveCycle.OnDestory=function ()
    print("OnDestory");
end

LiveCycle.LoadPage=function(name)
    local c=require("Controller/"..name);
    LiveCycle.Controllers[name]=c;
    c:Start();
end  

require("Config");
require("ABManager");
LuaJson=require("LuaJson");

卧槽,感觉有点不整齐啊

额,总之这个就是去模拟生命周期函数,C#那边就会调这边的生命周期函数

然后就是LiveCycle.LoadPage("ImageController");

以后如果有其他的Controller,也可以直接去LoadPage("OthersController");

这可以直接把各个Controller绑定到周期函数里面的

好像这个圈子基本都是这种架构了

 

然后就是MVC内部了

先来解释一下MVC:

Model:dataModel数据模型,创建/存放对后台的数据进行操作的方法,比如这里就是对Json文件进行一个读写

View:视图,创建/存放引擎直接的对象,比如这里就是创建了AB包中的对象,存了该对象中的Text,后续可以对Text进行读写

Controller:控制器,去调用M和V的方法,初始化、更新等,事件也会在这里注册,什么周期函数也只有这里有

Controller
local Controller = {};

package.path=package.path..";"..Config.Path.."\\LUA\\View\\?.lua"
package.path=package.path..";"..Config.Path.."\\LUA\\DataModel\\?.lua"
Controller.Data = require("ImageDataModel");
Controller.View = require("ImageView");

function Controller:Start()
    Controller.Data:New();--去看看有没有json,没有就创建
    Controller.View:Instantiate();--去加载AB包预制体,更新预制体↓
    Controller.View:Update(Controller.Data:Read().count);
    local button = Controller.View.GO:GetComponentInChildren(typeof(CS.UnityEngine.UI.Button));
    button.onClick:AddListener(Controller.AddAndUpdate);--注册事件
end

function Controller:Update()
    print("Image:Update()");
end

function Controller:AddAndUpdate()
    Controller.Data:Add(1);
    Controller.View:Update(Controller.Data:Read().count);
end

return Controller;

 主要看Start();

DataModel
 local DataModel={}

DataModel.Path=Config.Json;
DataModel.File={}

function DataModel:New()
  if(not CS.System.IO.File.Exists(DataModel.Path))
  then
    CS.System.IO.File.WriteAllText(DataModel.Path,'{"count":11,"test":111}');
  else
    DataModel.File=LuaJson.decode(CS.System.IO.File.ReadAllText(DataModel.Path));
  end
end

function DataModel:Read()
  if(DataModel.File)
  then
    return DataModel.File;
  else
    return nil;
  end
end

function DataModel:Add(num)
  DataModel.File.count=DataModel.File.count+num;
  CS.System.IO.File.WriteAllText(DataModel.Path,LuaJson.encode(DataModel.File));
end

return DataModel;

New:去路径找Json文件,有就拷贝一份到内存,没有就创建一份

Read:读取存起来的Json文件

Add:需要加多少,直接传参,该类去写入Json

View
 local View={};

local go;
local text;

local abName = "pre";
local name = "OBJ";

function View:Instantiate()
  go = CS.UnityEngine.GameObject.Instantiate(ABManager:LoadObj(abName,name,typeof(CS.UnityEngine.GameObject)));
  go.transform:SetParent(CS.UnityEngine.GameObject.Find("Canvas").transform);
  go.transform.localPosition=CS.UnityEngine.Vector3.zero;
  text=go:GetComponentInChildren(typeof(CS.UnityEngine.UI.Text));
  View.GO=go;
end

function View:Update(num)
  text.text=num;
end

return View;

Instantiate:初始化,去AB包创建物体,放到Canvas下面;把Text存起来,因为后面要用

Update:这个可不是生命周期函数,这就是要更新text的时候调一下

Config里面就是存了几个路径,有一个要讲的点是:

Config={};

Config.ABPath="G:/0-YJS/UnityProject/202308/HotUpdate";

Config.Path="G:\\0-YJS\\UnityProject\\202308\\HotUpdate";

Config.Json="G:/0-YJS/UnityProject/202308/HotUpdate/LUA/DataModel/data.json";

能require到的对象必定是注册过的

本案例是在Controller里面注册的,不知道你有没有发现

package.path=package.path..";"..Config.Path.."\\LUA\\View\\?.lua"
package.path=package.path..";"..Config.Path.."\\LUA\\DataModel\\?.lua"
Controller.Data = require("ImageDataModel");
Controller.View = require("ImageView");

说实话注册很麻烦。。。

最后是将C#的ABManager改写成Lua的ABManager:

C#
 //加载主AB包
AssetBundle main = AssetBundle.LoadFromFile(Config.ABPath + "/AB");
//加载主AB包的配置文件
AssetBundleManifest manifest = main.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
//分析需要加载的资源文件所依赖的AB包
string[] deps = manifest.GetAllDependencies("pre");
//加载依赖的AB包
for (int i = 0; i < deps.Length; i++) AssetBundle.LoadFromFile(Config.ABPath + "/" + deps[i]);
//加载资源文件所在的AB包
AssetBundle target = AssetBundle.LoadFromFile(Config.ABPath + "/pre");
//加载资源文件
GameObject prefabs = target.LoadAsset<GameObject>("Simple02");

Instantiate(prefabs,transform.parent);
Lua
ABManager={};

ABManager.Files={};

--加载AB主包
local mian=CS.UnityEngine.AssetBundle.LoadFromFile(Config.ABPath.."/AB");
ABManager.Mainifest=mian:LoadAsset("AssetBundleManifest",typeof(CS.UnityEngine.AssetBundleManifest));
mian:Unload(false);

--加载AB包并缓存起来,下次来加载时,能够直接在缓存里拿
function ABManager:LoadAB(name)
  --有缓存就不重复加载
  if(ABManager.Files[name] ~= nil)
  then
    return;
  end
  --加载依赖列表string[]
  local deps = ABManager.Mainifest:GetAllDependencies(name);
  --遍历依赖,并加载
  for i = 0,deps.Length-1
  do
    local temp = deps[i];
    if(ABManager.Files[temp] == nil)
    then
      ABManager:LoadAB(temp);
    end
  end
  --加载目标AB包(此时依赖已经加载完毕)
  if(CS.System.IO.File.Exists(Config.ABPath.."/"..name))
  then
    ABManager.Files[name] = CS.UnityEngine.AssetBundle.LoadFromFile(Config.ABPath.."/"..name);
  end
end

--加载对象,AB包(ABName)下的对象(objName),并且返回一个T类型的对象
function ABManager:LoadObj(ABName,ObjName,T)
  --加载AB包
  ABManager:LoadAB(ABName);
  --加载并返回对象
	if(ABManager.Files[ABName]~=nil)
  then
    return ABManager.Files[ABName]:LoadAsset(ObjName,T);
  else
    return nil;
  end
end	

tnnd没有变颜色

posted @ 2023-08-10 15:25  被迫吃冰淇淋的小学生  阅读(157)  评论(0)    收藏  举报