什么是冷更新
开发者将测试好的代码,发布到应用商店的审核平台,平台方会进行稳定性及性能
测试。测试成功后,用户即可在AppStore看到应用的更新信息,用户点击应用更
新后,需要先关闭应用,再进行更新。
什么是热更新
广义:无需关闭应用,不停机状态下修复漏洞,更新资源等,重点是更新逻辑代码。
狭义定义( iOS热更新):无需将代码重新打包提交至AppStore,即可更新客户端
的执行代码,即不用下载app而自动更新程序。
现状:苹果禁止了C#的部分反射操作,禁止JIT(即时编译,程序运行时创建并运
行新代码),不允许逻辑热更新,只允许使用AssetBundle进行资源热更新。
为何要热更新
缩短用户获取新版应用的客户端的流程,改善用户体验
具体到iOS平台的应用上,有以下几个原因
AppStore的审核周期难控制
手机应用更新频繁
对于大型应用,更新成本太大
终极目标
不重新下载、不停机状态下完全变换一个应用的内容
每个平台如何做热更新
Android,PC(C#)
将执行代码预编译为AssemblyDLL
将代码作为TextAsset打包进AssetBundle
运行时调用AssemblyDLL代码
更新相应的AssetBundle即可实现热更新
iOS(Lua)
苹果官方禁止iOS下的程序热更新;JIT在iOS下无效
热更新方案:Unity + Lua插件
常见的Unity热更新插件
sLua:最快的Lua插件
toLua:由uLua发展而来的,第三代Lua热更新方案
xLua:特性最先进的Lua插件
ILRuntime:纯C#实现的热更新插件
接触一个新的Lua项目时,先要弄懂Lua的加载器规则,只有这样,才能弄懂项目
的Lua执行流程
xLua中Lua调用C#代码。
为什么?
C#实现的系统,因为Lua可以调用,所以完全可以换成Lua实现,因为Lua可。
以即时更改,即时运行,所以游戏的代码逻辑就可以随时修改。。
实现和C#相同效果的系统,如何实现?。
Lua调用Unity的各种API,从而实现C#开发系统同样的效果,
xLua中C#调用Lua代码。
为什么?
Unity是基于C#语言开发的,所有生命周期函数都是基于C#实现,xLua 本身。
是不存在Unity的相关生命周期函数的。如果希望xLua能够拥有生命周期函。
数,那么我们可以实现C#作为Unity原始调用,再使用C#调用Lua对应的方。
法。|
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;//使用xlua的命名空间
public class First : MonoBehaviour
{
private void Start()
{
//lua时解释型语言,所以 需要获得Lua的解析器
//xlua解析器获得
LuaEnv env=new LuaEnv();
//解析器运行lua代码,把字符串当成lua代码去执行
env.DoString("print('HelloWorld')");
//解析器释放
env.Dispose();
}
}
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class Dostring : MonoBehaviour
{
private void Start()
{
//LuaCallCSharpCode();
LuaReturnData();
}
//使用Lua调用c#代码
public void LuaCallCSharpCode()
{
LuaEnv env=new LuaEnv();
//lua调用c#代码(CS.命名空间.类名.方法名(参数))
env.DoString("CS.UnityEngine.Debug.Log('from lua')");
env.Dispose();
}
//Lua返回值给c#
public void LuaReturnData()
{
LuaEnv env=new LuaEnv();
object[] data = env.DoString("return 100,true");
Debug.Log("Lua的第一个返回值:"+data[0].ToString());
Debug.Log("Lua的第一个返回值:"+data[1].ToString());
env.Dispose();
}
}
using System;-----------------------------自定义解析器-------------------------------
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
using XLua;
//Lua是脚本语言,编写代码最重要的方式
//加载器:因为Lua是多脚本编写方式,所以经常会使用require(“文件名”),加载子文件
//加载器是xlua实现了一种方式,可以由c#控制require进行加载
public class Loader : MonoBehaviour
{
private void Start()
{
MyLoader();
}
//系统内置的加载器会在require()命令调用时加载一些特定的目录下的Lua文件
//Resources目录下
//StreamingAssets目录下
public void SystemLoader()
{
LuaEnv env=new LuaEnv();
//对应test.lua
//内置加载器会扫描预制的目录,查找是否存在
//xLua存在默认加载器,StereamingAssets目录下可以加载文件,特殊目录,只读目录
env.DoString("require('test')");
env.Dispose();
}
public void MyLoader()
{
//实现xLua解析器
LuaEnv env=new LuaEnv();
//将我需要控制require添加到下xLua的回调函数中
env.AddLoader(ProjectLoader);
//当Lua中 任意位置执行了require命令,自定义加载器就会执行
env.DoString("require('test1')");
env.Dispose();
}
//自定义加载器
//自定义加载器会先于系统内置加载器执行,当自定义加载器加载到文件后,后续的加载器则不会执行
//当Lua代码执行require函数时,自定义加载器会尝试获得文件的内容
//参数,被加载Lua文件的路径
//如果需要加载的文件不存在,记得返回null
public byte[] ProjectLoader(ref string filepath)
{
//filepath 来自于Lua中的require(”文件名“)
//构造路径,才能将require加载的文件放到我们想放Lua的文件下去
//路径可以任意定制(可以将Lua代码放入AB包)
//因为Application.dataPath在上线的代码中无法获得,所以上线时,
//需要把Lua的存储路径指向到Application.dataPath
string path = Application.dataPath;
path = path.Substring(0, path.Length - 6) +
"DataPath/Lua/" + filepath+".lua";
//将Lua文件读取成为字节数组
//xLua的解析环境会执行我们自定义加载器返回的Lua代码
if (File.Exists(path))
{
return File.ReadAllBytes(path);
}
return null;
}
}
----------------------------工具类---------------------------
using System.IO;
using UnityEngine;
using UnityEngine.UIElements;
using XLua;
public class xLuaEnv
{
#region SingleTon
private static xLuaEnv _Instance;
public static xLuaEnv Instance
{
get
{
if (_Instance==null)
{
_Instance=new xLuaEnv();
}
return _Instance;
}
}
#endregion
#region CreateLuaEnv
private LuaEnv _env;
private xLuaEnv()
{
_env=new LuaEnv();
_env.AddLoader(_ProjectLoader);
}
#endregion
#region Loader
private byte[] _ProjectLoader(ref string filepath)
{
string path = Application.dataPath;
path = path.Substring(0, path.Length - 6) +
"DataPath/Lua/" + filepath+".lua";
if (File.Exists(path))
{
return File.ReadAllBytes(path);
}
return null;
}
#endregion
#region FreeLuaEnv
public void Free()
{
_env.Dispose();
_Instance = null;
}
#endregion
#region RunLua
public object[] DoString(string code)
{
return _env.DoString(code);
}
#endregion
//返回Lua环境的全局变量
public LuaTable Global
{
get
{
return _env.Global;
}
}
}
-------------------Lua调用c#静态类-------------
using UnityEngine;
namespace HX
{
public static class TestStatic
{
public static int ID = 99;
public static string Name
{
set;
get;
}
public static string Output()
{
return "static";
}
public static void Default(string str = "abc")
{
Debug.Log(str);
}
}
}
namespace C2L
{
public class LuaCallStatic : MonoBehaviour
{
private void Start()
{
xLuaEnv.Instance.DoString("require('C2L/LuaCallStatic')");
}
private void OnDestroy()
{
xLuaEnv.Instance.Free();
}
}
}
--Lua调用静态类
--规则"CS.命名空间.类名.成员变量"
print(CS.HX.TestStatic.ID)
--给静态属性赋值
CS.HX.TestStatic.Name="admin"
print(CS.HX.TestStatic.Name)
--静态成员方法调用
--规则"CS.命名空间.类名.方法名()"
print(CS.HX.TestStatic.Output())
--静态成员含参方法调用
--使用默认值
CS.HX.TestStatic.Default()
--使用Lua传递的值
CS.HX.TestStatic.Default("def")
-----------------Lua调用c#对象
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Npc
{
public string Name;
public int HP
{
get;
set;
}
public Npc()
{
}
public Npc(string name)
{
Name = name;
}
public string Output()
{
return Name;
}
}
public class LuaCallObject : MonoBehaviour
{
private void Start()
{
xLuaEnv.Instance.DoString("require('C2L/LuaCallObject')");
}
private void OnDestroy()
{
xLuaEnv.Instance.Free();
}
}
--Lua实例化类
--c# Npc obj=new Npc()
--通过调用构造函数创建对象
local obj = CS.Npc()
obj.HP=100
print(obj.HP)
local obj1 = CS.Npc("admin")
print(obj1.Name)
-- 表方法希望调用成员变量(表:函数())
--为什么是冒号,对象引用成员变量时,会隐性调用this,
--等同于Lua中的self
--成员方法调用的时候才会用冒号
print(obj1:Output())
--Lua实例化GameObject
--c# GameObject obj=new GameObject('LuaCreateGo')
local go = CS.UnityEngine.GameObject('LuaCreateGo')
go:AddComponent(typeof(CS.UnityEngine.BoxCollider))
---------------------------Lua调用c#结构体
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public struct TestStruct
{
public string Name;
public string Output()
{
return Name;
}
}
public class LuaCallStruct : MonoBehaviour
{
private void Start()
{
xLuaEnv.Instance.DoString("require('C2L/LuaCallStruct')");
}
private void OnDestroy()
{
xLuaEnv.Instance.Free();
}
}
--创建结构体的实例 和对象调用保持一致
local obj = CS.TestStruct()
obj.Name="admin"
print(obj.Name)
print(obj:Output())
---------------Lua 调用 c#重载
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TestOverload
{
public static void Test(int id)
{
Debug.Log("数字类型:"+id);
}
public static void Test(string name)
{
Debug.Log("字符串类型:"+name);
}
public static void Test(int id,string name)
{
Debug.Log("两个数值:"+id+","+name);
}
}
public class LuaCallOverload : MonoBehaviour
{
private void Start()
{
xLuaEnv.Instance.DoString("require('C2L/LuaCallOverload')");
}
private void OnDestroy()
{
xLuaEnv.Instance.Free();
}
}
--数字重载函数
CS.TestOverload.Test(99)
--字符串重载函数
CS.TestOverload.Test("admin")
--字符串重建多种类型
CS.TestOverload.Test(88,"admin")
-------------------------Lua调用c#重写
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Father
{
public string Name = "father";
public void Talk()
{
Debug.Log("这是父类中的方法");
}
public virtual void Overide()
{
Debug.Log("这是父类中的虚方法");
}
}
public class Child : Father
{
public override void Overide()
{
Debug.Log("这是子类中的重写方法");
}
}
public class LuaCallBase : MonoBehaviour
{
private void Start()
{
xLuaEnv.Instance.DoString("require('C2L/LuaCallBase')");
}
private void OnDestroy()
{
xLuaEnv.Instance.Free();
}
}
--调用Father
local father = CS.Father()
print(father.Name)
father:Overide()
--调用child
local child = CS.Child()
print(child.Name)
child:Talk()
child:Overide()
--------------------------Lua 调用c#枚举
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public enum TestEnum
{
LoL=0,
Dota2
}
public class LuaCallEnum : MonoBehaviour
{
private void Start()
{
xLuaEnv.Instance.DoString("require('C2L/LuaCallEnum')");
}
private void OnDestroy()
{
xLuaEnv.Instance.Free();
}
}
--c#调用枚举 TestEnum.LOL
--CS.命名空间.枚举名.枚举值
--枚举返回的是userdata自定义的数据类型,
--获得其他语言数据类型时,就是userdata
print(type(CS.TestEnum.LoL))
print(CS.TestEnum.Dota2)
--转换获得枚举值
print(CS.TestEnum.__CastFrom(0))
print(CS.TestEnum.__CastFrom('Dota2'))
---------------------------Lua 调用 c#扩展类
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class TestExtend
{
public void Output()
{
Debug.Log("类本身带的方法");
}
}
//类扩展需要给扩展方法编写的静态类添加[LuaCallCSharp],否则Lua无法调用
[LuaCallCSharp]
public static class MyExtend
{
public static void Show(this TestExtend obj)
{
Debug.Log("类扩展实现的方法");
}
}
public class LuaCallExtend : MonoBehaviour
{
private void Start()
{
xLuaEnv.Instance.DoString("require('C2L/LuaCallExtend')");
}
private void OnDestroy()
{
xLuaEnv.Instance.Free();
}
}
--获取对象
local obj = CS.TestExtend()
obj:Output()
obj:Show()
---------------------------Lua调用c#委托
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
[CSharpCallLua]
public delegate void DelegateLua();
public class TestDelegate
{
public static DelegateLua Static;
public DelegateLua Dynamic;
public static void StaticFunc()
{
Debug.Log("c#静态成员函数");
}
}
public class LuaCallDelegate : MonoBehaviour
{
private void Start()
{
xLuaEnv.Instance.DoString("require('C2L/LuaCallDelegate')");
}
private void OnDestroy()
{
xLuaEnv.Instance.Free();
}
}
--c#给委托赋值
--TestDelegate.Static=TestDelegate.StaticFunc
--TestDelegate.Static+=TestDelegate.StaticFunc
--TestDelegate.Static-=TestDelegate.StaticFunc
--TestDelegate.Static()
CS.TestDelegate.Static = CS.TestDelegate.StaticFunc
CS.TestDelegate.Static()
--Lua中如果添加了函数到静态委托变量中后,
--在委托不再使用后,释放添加的委托函数
CS.TestDelegate.Static=nil
----------------------------------------
local func = function
print('这是Lua函数')
end
--覆盖添加委托
CS.TestDelegate.Static=func
--加减操作一定要先确定已经添加过回调函数
CS.TestDelegate.Static=CS.TestDelegate.Static+func
CS.TestDelegate.Static=CS.TestDelegate.Static-func
--调用以前应确定委托有值
CS.TestDelegate.Static()
CS.TestDelegate.Static=nil
--调用前判定
--if(CS.TestDelegate.Static~=nil)
--then
-- CS.TestDelegate.Static()
--end
--根据委托判定赋值方法
--if(CS.TestDelegate.Static~=nil)
--then
-- CS.TestDelegate.Static=func
--else
-- CS.TestDelegate.Static=CS.TestDelegate.Static+func
--end
local obj=CS.TestDelegate()
obj.Dynamic=func
obj.Dynamic()
obj.Dynamic=nil
-----------------------------Lua调用c#事件
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public delegate void EventLua();
public class TestEvent
{
public static void StaticFunc()
{
Debug.Log("这是静态函数");
}
[CSharpCallLua]
public static event EventLua Static;
public static void CallStatic()
{
if (Static!=null)
{
Static();
}
}
public event EventLua Dynamic;
public void CallDynamic()
{
if (Dynamic!=null)
{
Dynamic();
}
}
}
public class LuaCallEvent : MonoBehaviour
{
private void Start()
{
xLuaEnv.Instance.DoString("require('C2L/LuaCallEvent')");
}
private void OnDestroy()
{
xLuaEnv.Instance.Free();
}
}
--c#添加事件 TestEvent.Static+=TestEvent.StaticFunc
--Lua添加事件
CS.TestEvent.Static("+",CS.TestEvent.StaticFunc)
CS.TestEvent.CallStatic()
CS.TestEvent.Static("-",CS.TestEvent.StaticFunc)
--添加动态成员变量
local func = function
print("来自Lua的回调函数")
end
local obj = CS.TestEvent()
obj:Dynamic("+",func)
obj:CallDynamic()
obj:Dynamic("-",func)
----------------------------Lua调用c#泛型
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TestGenericType
{
public void Output(T data)
{
Debug.Log("泛型方法:"+data.ToString());
}
public void Output(float data)
{
Output(data);
}
public void Output(string data)
{
Output(data);
}
}
public class LuaCallGenericType : MonoBehaviour
{
private void Start()
{
xLuaEnv.Instance.DoString("require('C2L/LuaCallGenericType')");
}
private void OnDestroy()
{
xLuaEnv.Instance.Free();
}
}
local obj = CS.TestGenericType()
obj:Output(99)
obj:Output("admin")
local go = CS.UnityEngine.GameObject("LuaCreat")
--xLua实现了typeof关键字,所以可以用类型API代替泛型
go:AddComponent(typeof(CS.UnityEngine.BoxCollider))
------------------------Lua调用c#输入输出参数
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class TestOutRef
{
public static string Func1()
{
return "Func1";
}
public static string Func2(string str1,out string str2 )
{
str2 = "Func2 out";
return "Func2";
}
public static string Func3(string str1,ref string str2 )
{
str2 = "Func3 ref";
return "Func3";
}
public static string Func4(ref string str1, string str2 )
{
str1 = "Func4 ref";
return "Func4";
}
}
public class LuaCallOutRef : MonoBehaviour
{
private void Start()
{
xLuaEnv.Instance.DoString("require('C2L/LuaCallOutRef')");
}
private void OnDestroy()
{
xLuaEnv.Instance.Free();
}
}
local r1=CS.TestOutRef.Func1()
print(r1)
--c# out返回值的变量,会赋值给Lua的第二个接受返回值变量
local out2
local r2,out1=CS.TestOutRef.Func2("admin",out2)
print(r2,out1,out2)
--c# ref返回值的变量,会赋值给Lua的第二个接受返回值变量
local ref2
local r3,ref1=CS.TestOutRef.Func3("root",ref2)
print(r3,ref1,ref2)
--即使out ref作为第一个参数, 其结果依然会以Lua的多个返回值进行返回
local ref4
local r4,ref3=CS.TestOutRef.Func4(ref4,"test")
print(r4,ref3,ref4)
-------------------Lua将变量反射给c#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class CsharpCallVariable : MonoBehaviour
{
private void Start()
{
xLuaEnv.Instance.DoString("return require('L2C/CsharpCallVariable')");
//Lua Env中提供了一个成员变量Global, 它可以用于c#获取Lua的全局变量
//Global的数据类型是c#实现的LuaTable,LuaTable是xLua实现的c#和Lua中表对应的数据结构
//xLua会将Lua中的全局变量以Table的方式全部存储在Global中
//通过运行环境,导出全局变量,类型是LuaTable
//LuaTable是c#的数据对象,用于和Lua中的全局变量存储的table对应
LuaTable g = xLuaEnv.Instance.Global;
//从Lua中将全局变量提取出来
//参数:Lua中全局变量的名称
//类型:Lua中全局变量的名称对应的类型
//返回值:变量的值
int num = g.Get("num");
float rate = g.Get("rate");
bool isWoman = g.Get("isWoman");
string name = g.Get("name");
Debug.Log(num);
Debug.Log(rate);
Debug.Log(isWoman);
Debug.Log(name);
}
private void OnDestroy()
{
xLuaEnv.Instance.Free();
}
}
--隐性做了{num=100, rate=99.9, isWoman=false, name="admin"}
num=100
rate=99.9
isWoman=false
name="admin"
----------------------Lua将函数反射给c#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public delegate void Func1();
public delegate void Func2(string name);
public delegate string Func3();
[CSharpCallLua]
public delegate void Func4(out string name,out int id);
public class CsharpCallFunction : MonoBehaviour
{
private void Start()
{
xLuaEnv.Instance.DoString("return require('L2C/CsharpCallFunction')");
LuaTable g = xLuaEnv.Instance.Global;
//Lua的函数,会导出为c#的委托类型
Func1 func1= g.Get("func1");
func1();
//向Lua中传递函数数据
Func2 func2= g.Get("func2");
func2("admin");
//接受Lua函数的返回值
Func3 func3= g.Get("func3");
Debug.Log(func3()+",被c#打印");
//Lua多返回值
Func4 func4= g.Get("func4");
string name;
int id;
func4(out name, out id);
Debug.Log(name+","+id);
}
private void OnDestroy()
{
xLuaEnv.Instance.Free();
}
}
func1=function()
print("这是Lua中的func1")
end
func2=function(name)
print("这是Lua中的func2,参数是:"..name)
end
func3=function()
return "这是Lua中的func3"
end
func4=function()
return "这是Lua中的func4",88
end
----------------------Lua将元表,结构体反射给c#
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public delegate void OneStringParams(string name);
public delegate void TransSelf(LuaTable table);
//针对结构体后写的
public delegate string OneStringReturn();
[CSharpCallLua]
public delegate void TransMy(LuaCore table);
//Lua的Table导出到c#的结构体,可以实现c#运行时无GC
[GCOptimize]
public struct LuaCore
{
public int ID;
public string Name;
public bool IsWoman;
public OneStringParams Func1;
public OneStringReturn Func2;
public TransMy Func3;
public TransMy Func4;
}
public class CsharpCallTable : MonoBehaviour
{
private void Start()
{
xLuaEnv.Instance.DoString("return require('L2C/CsharpCallTable')");
UseLuaStruct();
UseLuaTable();
}
public void UseLuaTable()
{
LuaTable g = xLuaEnv.Instance.Global;
//获取全局变量Core,因为他在Lua中是表,所以取出来是LuaTable
LuaTable core = g.Get("Core");
//获取Name
//参数:table中的索引名
//类型:索引对应值的类型
Debug.Log(core.Get("Name"));
core.Set("Name","admin");
OneStringParams osp = core.Get("Func1");
osp("admin");
//相当于":"调用 0
TransSelf ts = core.Get("Func4");
ts(core);
}
public void UseLuaStruct()
{
LuaTable g = xLuaEnv.Instance.Global;
//将Lua的Table导出为core
LuaCore core= g.Get("Core");
Debug.Log(core.Name);
core.Func4(core);
}
private void OnDestroy()
{
xLuaEnv.Instance.Free();
}
}
Core={}
Core.ID=123
Core.Name="root"
Core.IsWoman=false
Core.Func1=function(name)
print("这是Core表的Func1函数,接收到c#数据"..name)
end
Core.Func2=function()
print("这是Core表的Func2函数")
end
Core.Func3=function(self)
print("这是Core表的Func3函数,Core表的成员变量Name是"..self.Name)
end
function Core:Func4()
print("这是Core表的Func4函数,Core表的成员变量Name是"..self.Name)
end