Unity热更新 xLua
xLua是Unity3D下Lua编程解决方案,自2016年初推广以来,已经应用于十多款腾讯自研游戏,因其良好性能、易用性、扩展性而广受好评。现在,腾讯已经将xLua开源到GitHub。
2016年12月末,xLua刚刚实现新的突破:全平台支持用Lua修复C#代码bug。
目前Unity下的Lua热更新方案大多都是要求要热更新的部分一开始就要用Lua语言实现,不足之处在于:
- 接入成本高,有的项目已经用C#写完了,这时要接入需要把需要热更的地方用Lua重新实现;
- 即使一开始就接入了,也存在同时用两种语言开发难度较大的问题;
- Lua性能不如C#;
xLua热补丁技术支持在运行时把一个C#实现(函数,操作符,属性,事件,或者整个类)替换成Lua实现,意味着你可以:
- 平时用C#开发;
- 运行也是C#,性能秒杀Lua;
- 有bug的地方下发个Lua脚本fix了,下次整体更新时可以把Lua的实现换回正确的C#实现,更新时甚至可以做到不重启游戏; 这个新特性iOS,Android,Window,Mac都测试通过了,目前在做一些易用性优化。
xLua插件下载地址:https://github.com/Tencent/xLua
xLua的使用
创建工程并导入xLua插件
通过xLua插件运行lua程序
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class MyHelloWorld : MonoBehaviour {
void Start () {
// 创建lua环境
LuaEnv luaenv = new LuaEnv();
// 运行Lua代码
luaenv.DoString("print('Hello World')");
// 关闭Lua环境
luaenv.Dispose();
}
}
可以看到,输出了打印,前缀有Lua的标识表示这是由Lua中的方法执行的
反过来,也可以使用lua调用C#中的程序
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class MyHelloWorld : MonoBehaviour {
void Start () {
// 创建lua环境
LuaEnv luaenv = new LuaEnv();
// 运行Lua代码
//luaenv.DoString("print('Hello World')");
luaenv.DoString("CS.UnityEngine.Debug.Log('Hello World')");
// 关闭Lua环境
luaenv.Dispose();
}
}
这个时候,打印前就没有Lua标识符了,表示这是由C#中代码执行的
上面是C#和Lua之间的简单调用,但是在实际工作中,我们不可能这么写。我们的做法是写好Lua文件后,在C#中加载这个文件,然后使用其中的函数功能。
首先我们创建好一个Lua文件,然后在C#中加载后使用
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class MyHello : MonoBehaviour {
void Start () {
TextAsset t = Resources.Load<TextAsset>("helloworld.lua");
LuaEnv luaenv = new LuaEnv();
luaenv.DoString(t.ToString());
luaenv.Dispose();
}
}
注意:在加载的时候,我们使用的是TextAsset文本格式,它默认识别的后缀为.txt,所以我们上面创建的lua文件后缀不是.lua,但是为了让我们方便的看出它是一个lua文件,所以取名的时候使用.lua.txt。
除了上面的加载方法外,更常用的方法是使用require加载
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class MyHello : MonoBehaviour {
void Start () {
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'helloworld'");
luaenv.Dispose();
}
}
require实际上是调一个个的loader去加载,有一个成功则不再往下尝试,全部失败则报文件找不到。目前Lua除了原生的loader外,还添加了从Resources加载的loader,需要注意的是Resources只支持有限的后缀,放在Resources下的lua文件需要加上.txt后缀。
自定义loader
我们发现上面的lua文件都是放在Resources文件夹下,因为原生的loader会在这个下面去加载。在我们的项目中,可能我们的lua文件放在自定义的文件夹下,这个时候就需要我们自定义loader,在xLua加自定义loader是很简单的,只涉及到一个接口:
public delegate byte[] CustomLoader(ref string filepath);
public void LuaEnv.AddLoader(CustomLoader loader)
通过AddLoader可以注册个回调,该回调参数是字符串,lua代码里头调用require时,参数将会透传给回调,回调中就可以根据这个参数去加载指定文件,如果需要支持调试,需要把filepath修改为真实路径传出。该回调返回值是一个byte数组,如果为空表示该loader找不到,否则则为lua文件的内容。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
using System.IO;
public class CreateNewLoader : MonoBehaviour {
void Start () {
LuaEnv luaenv = new LuaEnv();
// 自定义loader
luaenv.AddLoader(MyLoader);
luaenv.DoString("require 'newloaderText'");
luaenv.Dispose();
}
private byte[] MyLoader(ref string filePath)
{
string absPath = Application.streamingAssetsPath + "/" + filePath + ".lua.txt";
return System.Text.Encoding.UTF8.GetBytes(File.ReadAllText(absPath));
}
}
上面代码中我们定义的lua文件为“newloaderText.lua”,该文件位于“StreamingAssets”文件夹下,该文件夹与Assets文件夹同级,所以在后面设置路径的时候使用系统自带的函数“Application.streamingAssetsPath”可以找到该文件夹。当然,我们也可以自定义文件夹的位置,后面的路径改一下就行。
上面的执行过程,注册回调后,调用require的时候,将“newloaderText”传递给回调函数"MyLoader",在此回调函数中我们加载到指定文件然后传回来使用。
C#访问Lua 获取全局变量
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class CSharpCallLua : MonoBehaviour {
void Start () {
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'CSharpCallLua'");
// 获取lua中的全局变量
int num = luaenv.Global.Get<int>("num");
string name = luaenv.Global.Get<string>("name");
bool isPause = luaenv.Global.Get<bool>("isPause");
Debug.Log("num:" + num);
Debug.Log("name:" + name);
Debug.Log("isPause:" + isPause);
luaenv.Dispose();
}
}
使用函数LuaEnv.Global就能访问,其中,luaenv.Global.Get<int>("num")中,<int>指的是要转换成的类型,"num"是在lua中定义的变量名
C#访问Lua 获取全局table
- 映射到普通class或struct:定义一个class或者struct,有对应于table的字段的public属性,而且有无参数构造函数即可,比如对于{f1 = 100, f2 = 100}可以定义一个包含public int f1;public int f2;的class。这种方式下xLua会帮你new一个实例,并把对应的字段赋值过去。table的属性可以多于或者少于class的属性。可以嵌套其它复杂类型。
注意:lua的table中的字段名和C#的class中的字段名要一一对应(名字也要相同),否则取不到值。此种方式为值拷贝,修改class的字段值不会同步到table,反过来也不会。使用此种方式,不能访问lua的函数。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class CSharpCallLua : MonoBehaviour {
void Start () {
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'CSharpCallLua'");
// 获取lua中的全局table
Person p = luaenv.Global.Get<Person>("Person");
Debug.Log("name:" + p.name);
Debug.Log("age:" + p.age);
luaenv.Dispose();
}
class Person
{
public string name;
public int age;
}
}
- 映射到interface:这种方式依赖于生成代码(如果没生成代码会抛InvalidCastException异常),代码生成器会生成这个interface的实例,如果get一个属性,生成代码会get对应的table字段,如果set属性也会设置对应的字段。甚至可以通过interface的方法访问lua的函数。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class CSharpCallLua : MonoBehaviour {
void Start () {
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'CSharpCallLua'");
// 获取lua中的全局table(映射到interface)
Person_1 p1 = luaenv.Global.Get<Person_1>("Person");
Debug.Log("name:" + p1.name);
Debug.Log("age:" + p1.age);
p1.eat("apple");
luaenv.Dispose();
}
[CSharpCallLua]
interface Person_1
{
string name { get;set;}
int age { get; set; }
void eat(string str);
}
}
注意:在lua中定义函数的时候,第一个参数是arg,需要写上,名字随意取都行,这里写的self。在C#中定义接口的时候,要加上标签[CSharpCallLua]
- 映射到Dictionary<>,List<>
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class CSharpCallLua : MonoBehaviour {
void Start () {
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'CSharpCallLua'");
// 获取lua中的全局table(通过Dictionary)
Dictionary<string, object> dict = luaenv.Global.Get<Dictionary<string, object>>("Person");
foreach(string key in dict.Keys)
{
print("key:" + key + " value:" + dict[key]);
}
luaenv.Dispose();
}
}
注意:映射到Dictionary<>的时候,只映射了Lua中键值对的形式,普通的值没有映射过来
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class CSharpCallLua : MonoBehaviour {
void Start () {
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'CSharpCallLua'");
// 获取lua中的全局table(通过List)
List<object> list = luaenv.Global.Get<List<object>>("Person");
foreach(object o in list)
{
print(o);
}
luaenv.Dispose();
}
}
注意:映射到List<>的时候,只映射了Lua中值的形式,键值对的形式没有映射过来
映射到LuaTable类:这种方式不常用,也不建议使用
C#访问Lua 获取全局函数
- 映射到delegate:这种是建议的方式,性能好很多,而且类型安全。缺点是要生成代码(如果没生成代码会抛InvalidCastException异常)。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class CSharpCallLua : MonoBehaviour {
void Start () {
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'CSharpCallLua'");
// 访问lua中的全局函数(映射到delegate)
Add add = luaenv.Global.Get<Add>("add");
int res1 = 0; int res2 = 0;
int res = add(3, 4, out res1, out res2);
print("res:" + res);
print("res1:" + res1);
print("res2:" + res2);
add = null;
luaenv.Dispose();
}
[CSharpCallLua]
delegate int Add(int a, int b, out int res1, out int res2);
}
注意:使用delegate需要添加特性[CSharpCallLua],如果lua中函数返回多值,在C#中只能接收一个值,其它值从左往右映射到c#的输出参数,输出参数包括返回值,out参数,ref参数。
- 映射到LuaFunction:这个性能不好,不建议使用
Lua访问C#
在C#这样new一个对象:
var newGameObj = new UnityEngine.GameObject();
对应到Lua是这样:
local newGameObj = CS.UnityEngine.GameObject()
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
public class LuaCallCSharp : MonoBehaviour {
void Start () {
LuaEnv luaenv = new LuaEnv();
luaenv.DoString("require 'LuaCallCS'");
luaenv.Dispose();
}
}
Lua访问C#静态属性和方法
如果需要经常访问的类,可以先用局部变量引用后访问,除了减少敲代码的时间,还能提高性能
Lua访问C#成员属性和方法
读成员属性
testobj.DMF
写成员属性
testobj.DMF = 1024
Unity热更新 xLua的更多相关文章
- 另类Unity热更新大法:代码注入式补丁热更新
对老项目进行热更新 项目用纯C#开发的? 眼看Unity引擎热火朝天,无数程序猿加入到了Unity开发的大本营. 一些老项目,在当时ulua/slua还不如今天那样的成熟,因此他们选择了全c#开发:也 ...
- unity热更新方案对比
Unity应用的iOS热更新 • 什么是热更新 • 为何要热更新 • 怎样在iOS 上对Unity 应用进行热更新 • 支持Unity iOS 热更新的各种Lua 插件的对照 什么是热更新 • ...
- Unity热更新对比
https://www.jianshu.com/p/f9d90edf4a7c Unity 热更新为啥用Lua 详解 ILRuntime的优势 同市面上的其他热更方案相比,ILRuntime主要有以下优 ...
- Unity 热更新实例一、C#Light 和UI系统使用实例
接下来我会运用热更新的机制,逐步制作一些例子来阐释脚本系统如何和Unity结合. 脚本不限于使用C#Lite,但是C#Lite会有一些便利之处,请往下看. 结合机制也不限于这一种,但是C#Lite的设 ...
- Unity热更新技术整理
一.热更新学习介绍 1.什么是热更新 举例来说: 游戏上线后,玩家下载第一个版本(70M左右或者更大),在运营的过程中,如果需要更换UI显示,或者修改游戏的逻辑,这个时候,如果不使用热更新,就需要重新 ...
- 【转】unity 热更新思路和实现
声明:本文介绍的热更新方案是我在网上搜索到的,然后自己修改了一下,相当于是借鉴了别人的思路,加工成了自己的,在此感谢无私分享经验的朋友们. 想要使用热更新技术,需要规划设计好资源比较策略,资源版本,确 ...
- [Unity热更新]tolua# & LuaFramework(一):基础
一.tolua# c#调用lua:LuaState[变量名/函数名] 1.LuaState a.执行lua代码段 DoString(string) DoFile(.lua文件名) Require(.l ...
- Unity热更新 AssetBundle
在游戏开发中,常常需要用到热更新技术.比如:一个手机游戏开发好后,用户安装到手机上.如果此时我们要更新一个新的功能,如果没有热更新,那么需要用户卸载掉手机上的游戏,然后安装新的包,这样做十分麻烦,而且 ...
- C#版的eval,C#Light开源嵌入式脚本,unity热更新不再愁
目前最新版本AlphaV0.06 完全的c#语法,可用于一切能运行C#的场合,wp windows xamarin mono asp.net unity3d 内嵌了int uint bool stri ...
随机推荐
- 第一份c语言作业
2.1 你对软件工程专业或者计算机科学与技术专业了解是怎样? •答案: 软件工程专业是个年轻的专业,紧跟这个信息化的新时代.我学习它是因为感兴趣,经过一周的学习,我了解了一些 该专业课程主要是c语言程 ...
- linux学习19 shell脚本基础-bash脚本编程基础及配置文件
一.shell脚本编程 1.编程语言的分类,根据运行方式 a.编译运行:源代码 --> 编译器(编译) --> 程序文件 C语言: b.解释运行:源代码 --> 运行时启动解释器,由 ...
- 理解SqlMapConfig.xml文件
SqlMapConfig.xml mybatis的全局配置文件SqlMapConfig.xml,配置内容如下: properties(属性) settings(全局配置参数) typeAliases( ...
- 关于STM32的I2C硬件DMA实现
关于STM32的I2C硬件DMA实现 网上看到很多说STM32的I2C很难用,但我觉得还是理解上的问题,STM32的I2C确实很复杂,但只要基础牢靠,并没有想象中的那么困难. 那么就先从基础说起,只说 ...
- 长春理工大学第十四届程序设计竞赛D Capture The Flag——哈希&&打表
题目 链接 题意:给定一个字符串 $s$,求不同于 $s$ 的字符串 $t$,使得 $Hash(s) = Hash(t)$,其中 $\displaystyle Hash(s) = \sum_0^{le ...
- docker自定义镜像仓库
创建私有仓库 vim /etc/docker/daemon.json //使用私有仓库运行容器 , 宿主机ip { "insecure-registries" : ["1 ...
- ubuntu系统升级PHP版本
https://blog.csdn.net/qq_16885135/article/details/79747045 升级PHP7.2 https://www.cnblogs.com/lalal ...
- Go语言编程中字符串切割方法小结
1.func Fields(s string) []string,这个函数的作用是按照1:n个空格来分割字符串最后返回的是[]string的切片 复制代码代码如下: import ( "fm ...
- linux环境中关闭tomcat,通过shutdown.sh无法彻底关闭--线程池
最近测试环境上测试的项目通过shutdown.sh始终无法彻底关闭. 之前临时解决方法两种: 第一:通过ps -ef|grep tomcat查看到tomcat的进程直接使用kill来杀死进程. 第二: ...
- GDB之调试器用法
GDB 完成的作用: 启动程序,可以按照工程师自定义的要求随心所欲的运行程序 让被调试的程序在工程师指定的断点处停住,断点可以是条件表达式 当程序被停住时,可以检查此时程序中所发生的事,并追索上文 动 ...