Unity 项目中怎样正确的使用 Lua?
在 Unity 中, 我们通常写的 C# 已经就算是 script 了, 所以我觉得使用 Lua 的唯二理由就是一为了可以热更新, 二是一些 Lua 写的逻辑可能服务器端也要用到. 不知道我理解的是否正确?那么如果决定使用 Lua 的话, 是不是意味着是将原来所有要用 C# 写的逻辑全改成 Lua 的? 如果是的话岂不是需要将所有的 C# API 全做一份 Lua 绑定的? 这样工程量似乎很大啊. 而且我搜索到的常用的 Lua 实现(比如 UniLua) 只是做了 Lua 的 C# 实现. (也可能是作者没有将那部分开源出来?)所以现在我不知道大家都是怎样在 Unity 中使用 Lua. 请哥哥教我
来使用哥开发的slua吧,
开源, 和ulua相比快的没有朋友,
没有反射,没有额外gc alloc,采用静态代码生成, 可以用于游戏核心逻辑, 完整支持4.6+ UI系统.同ulua/tolua的性能对比,请看这里 20万次 测试用例 数据如下(slua0.6 vs cstolua1.7, slua0.7再次飙升速度)slua 0.6 对比 slua 0.7主要功能如下:速度就是快, 这是slua的核心目标避免额外gc alloc, 去掉性能杀手90%以上UnityEngine接口导出(主要去掉了flash,平台相关的接口);100% UnityEngine.UI接口导出 (4.6+ 版本)支持UnityEvent/UnityAction, 使用lua function支持delegation,使用lua function (支持iOS)支持coroutine支持导出自定义类所有enum导出为number所有数组返回值导出为lua table使用luajit 64bit(完整支持armv7,armv7s,arm64), 可用lua5.3替换支持il2cpp/il2cpp 64
因为项目需要,比较了下uLua和sLua测试工程见 放几张结果图ps. 下面的图我为了美观都是对数坐标...pss. uLua的工程目录我无力吐槽………………
更新:顶楼上,早已转入slua了()__________________________________________________________参考:
只用来做了配置解析…… C#下的Lua效率很低…大概是C的1/100…不适合做主逻辑
呵呵,看了下来感觉untiy里面根本没有用lua的必要啊
最近刚开始使用Unity引擎,集成Lua的部分写了一篇总结,供参考。基本的结论是:1. 做商业游戏在Unity中集成Lua语言是一件不太能避免的事情,只是Lua语言应用的范围是怎样的要仔细确定;2. 个人在安卓设备上测试的结论和前面钱康来的基本一致,ToLua#在接口调用方面比SLua的稍微好一点,猜测的原因之一是由于SLua在返回值中添加一个bool值来标识函数调用的成功与否;3. ToLua#和SLua之外都有对应的框架,个人感觉KSFramwork这套框架实现的部分功能不错,单是感觉ToLua#性能稍好,因此在自己的项目中做了一个“”杂交“”。。。
这块变化较大,目前(2016年)主要有两个主要的备选方案:slua和tolua。
表示你们的工具不如鹅厂内部工具给力啊,还需要努力
c#light ,你值得拥有...不错 就是为了热更新 我在自己的项目里花几天就移植了部分功能过去 关键是一有不妥 都是c#代码也容易转换回来 目前来看除了有些特性不支持,没遇到什么大坑
不足之处是效率不是很让人满意 与非脚本的沟通也有点生硬
已有帐号?
无法登录?
社交帐号登录Unity(82)
Unity3d 中使用Lua之UniLua
方便动态更新游戏用。
开源项目地址:
最新支持到Lua5.2,C#版的Lua
基础用法:
大部分的使用是可以参考标准的 Lua 官方文档和 Lua 教程的。 Lua 本身的语法是一样的。C API 和 C# API 之间有个对应关系。例如 lua_pushnumber() 这个 C API 对应到 UniLua 里就是 lua.PushNumber()
所有标准 lua 中 lua.h 和 lauxlib.h 里定义的接口,都对应
里定义的 ILuaAPI 和
里定义的 ILuaAuxLib 接口。
从 C# 调用 Lua
最朴素的从 C# 调用 lua 的一个全局函数的写法:
Lua.GetGlobal( &foo& ); // 加载 lua 中定义的一个名叫 foo 的全局函数到堆栈
Debug.Assert( Lua.IsFunction(-1) ); // 确保加载成功了, 此时栈顶是函数 foo
Lua.PushString( &test& ); // 将第一个参数(字符串 &test&)入栈
Lua.PushInteger( 42 ); //将第二个参数(整数 42)入栈
Lua.Call(2, 0); // 调用函数 foo, 指明有2个参数,没有返回值
// 上面的代码相当于 lua 里一个这样的调用 foo(&test&, 42)
稍微复杂一点的例子可以参考实例程序里的一些简单写法:参考这个文件 Assets/Behaviour/LuaScriptController.cs:
// 创建 Lua 虚拟机
var Lua = LuaAPI.NewState();
// 加载基本库
Lua.L_OpenLibs();
// 加载 Lua 脚本文件
var LuaScriptFile = &framework/main.lua&;
var status = Lua.L_DoFile( LuaScriptFile );
// 捕获错误
if( status != ThreadStatus.LUA_OK )
throw new Exception( Lua.ToString(-1) );
// 确保 framework/main.lua 执行结果是一个 Lua Table
if( ! Lua.IsTable(-1) )
throw new Exception(
&framework main's return value is not a table& );
// 从 framework/main.lua 返回的 table 中读取 awake 字段指向的函数
// 并保存到 AwakeRef 中 (可以将 AwakeRef 视为这个函数的句柄)
var AwakeRef = StoreMethod( &awake& );
// 不再需要 framework/main.lua 返回的 table 了,将其从栈上弹出
Lua.Pop(1);
//----------------------------------------------------
// 在需要的时候可以这样调用 AwakeRef 指向的 lua 函数
CallMethod( AwakeRef );
//----------------------------------------------------
// StoreMethod 和 CallMethod 的实现
private int StoreMethod( string name )
Lua.GetField( -1, name );
if( !Lua.IsFunction( -1 ) )
throw new Exception( string.Format(
&method {0} not found!&, name ) );
return Lua.L_Ref( LuaDef.LUA_REGISTRYINDEX );
private void CallMethod( int funcRef )
Lua.RawGetI( LuaDef.LUA_REGISTRYINDEX, funcRef );
var status = Lua.PCall( 0, 0, 0 );
if( status != ThreadStatus.LUA_OK )
Debug.LogError( Lua.ToString(-1) );
Lua 调用 C# 函数 ( 使用 C# 来扩展 Lua 功能 )
目前的示例程序是使用 FFI 库来实现的 从 Lua 调用 C# 函数。 FFI 因为用到了反射机制来调用 C# 函数,性能会比较低。应该尽量避免使用,如果没有找到更好的办法,准备之后把这个FFI实现废弃掉。其实直接用 C# 实现一个库的形式,来让 lua 调用这种传统的做法效率会比较高,也是推荐采用的方式。而且也并不会麻烦太多。
比如我现在要实现一个叫 libfoo 的库, 里面提供两个方法: add(a, b) 和 sub(a, b)
using UniL
public static class LibFoo
public const string LIB_NAME = &libfoo.cs&; // 库的名称, 可以是任意字符串
public static int OpenLib(ILuaState lua) // 库的初始化函数
var define = new NameFuncPair[]
new NameFuncPair(&add&, Add),
new NameFuncPair(&sub&, Sub),
lua.L_NewLib(define);
public static int Add(ILuaState lua)
var a = lua.L_CheckNumber( 1 ); // 第一个参数
var b = lua.L_CheckNumber( 2 ); // 第二个参数
var c = a + // 执行加法操作
lua.PushNumber( c ); // 将返回值入栈
return 1; // 有一个返回值
public static int Sub(ILuaState lua)
var a = lua.L_CheckNumber( 1 ); // 第一个参数
var b = lua.L_CheckNumber( 2 ); // 第二个参数
var c = a - // 执行减法操作
lua.PushNumber( c ); // 将返回值入栈
return 1; // 有一个返回值
库的初始化
// 创建 Lua 虚拟机
var Lua = LuaAPI.NewState();
// 加载基本库
Lua.L_OpenLibs();
Lua.L_RequireF( LibFoo.LIB_NAME
// 库的名字
, LibFoo.OpenLib
// 库的初始化函数
// 不默认放到全局命名空间 (在需要的地方用require获取)
库的使用 (在 lua 代码中)
local libfoo = require &libfoo.cs&
// 调用库的方法
print(libfoo.add(42, 1))
print(libfoo.sub(42, 22))
UTF-8 support
C# 采用 UTF-16 作为字符串的内部编码,而 Lua 本身没有实现比较完善的编码支持。为了处理这个问题,我实现了一个简单的编码库 enc lib。使用方法如下:
-- Assuming your source code is in utf-8.
-- convert from utf-8:
local utf8_str = '测试字符串'
local print_safe_str = enc.decode(utf8_str, 'utf8')
print(print_safe_str)
-- convert to utf-8:
local original_str = enc.encode(print_safe_str, 'utf8')
assert(utf8_str == original_str)
更多资料:
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:24849次
排名:千里之外
原创:44篇
转载:83篇
(2)(37)(88)如何单步调试Unity中的Lua脚本_百度知道Unity3D热更新LuaFramework入门实战(2)----资源热更新_碧俐千仞_新浪博客
Unity3D热更新LuaFramework入门实战(2)----资源热更新
热更新涉及资源热更新和代码热更新(其实lua代码也是资源),那接下来看看如何动态加载一个模型,然后热更成其他素材。这一部分涉及资源打包、动态创建资源等内容。
By 罗培羽 (微博 @罗培羽)
1、创建物体
为了调试的方便,笔者先将框架配置为本地模式,待测试热更新时再改成更新模式。
配置为本地模式
先测试个简单的创建物体,新建一个名为go的物体,然后设置它的坐标为(1,1,1)。这段代码虽然不涉及资源加载,但能展示“把物体添加到场景中”的过程。Main.lua的代码如下:
function Main()
local go = UnityEngine.GameObject ('go')
go.transform.position = Vector3.one
动态创建一个名为go的空物体&
要热更新资源,便需要制作资源。这里制作一个名为tankPrefab的坦克模型预设,然后存到Assets/Tank目录下。接下来对它做打包,然后动态加载。
坦克预设&
广告时间:这个坦克模型是来自笔者即将出版的一本书《Unity3D网络游戏实战》。该书通过一个完整的多人坦克对战实例,详细介绍网络游戏开发过程中涉及到的知识和技巧。书中还介绍了服务端框架、客户端网络模块、UI系统的架构等内容。相信透过本书,读者能够掌握Unity3D网络游戏开发的大部分知识,也能够从框架设计中了解商业游戏的设计思路,感谢大家支持。
2、资源打包
LuaFramework在打包方面并没有做太多的工作,我们需要手动打包。打开Assets/LuaFramework/Editor/Packager.cs,按照示例的写法,加上下面这一行:将Assets/Tank目录下的所有预设(.prefab)打包成名为tank的包。
修改打包代码
点击“Build Windows Resource”,即可在StreamingAssets中看到打包好的文件。
如下图所示,Unity3D资源包里面包含多个资源,就像一个压缩文件一样。在动态加载的时候,便需要有加载包文件、或取包中的资源两步操作(框架已经帮我们做好了这部分工作,直接调用API即可)。
Unity3D的资源包
3、动态加载模型
编写如下lua代码(main.lua),使用框架提供的资源管理器(resMgr)加载tank包的TankPrefab文件,加载完成后回调OnLoadFinish方法。在OnLoadFinish中使用Instantiate实例化对象。
--主入口函数。从这里开始lua逻辑
function Main()
LuaHelper = LuaFramework.LuaH
resMgr = LuaHelper.GetResManager();
resMgr:LoadPrefab('tank', { 'TankPrefab' }, OnLoadFinish);
--加载完成后的回调--
function OnLoadFinish(objs)
local go = UnityEngine.GameObject.Instantiate(objs[0]);
LuaFramework.Util.Log("Finish");
完成后运行游戏,即可看到动态加载出来的模型。
动态加载出来的模型
4、加载资源的过程
只有理解了动态加载,即LoadPrefab的过程,才能算是真正的理解了热更新。LoadPrefab为ResourceManager中定义的方法,在Assets\LuaFramework\Scripts\Manager\ResourceManager.cs中实现,建议配合代码看下面的解释。
LoadPrefab的流程如下所示,先是判定当前是否正在加载该资源包,如果没有则调用OnLoadAsset加载资源包、然后解包获取资源、调用回调函数。
&LoadPrefab的流程
ResourceManager类定义了m_AssetBundleManifest、m_Dependencies、m_LoadedAssetBundles、m_LoadRequests这4个变量,只要理解了这几个变量的用途,也就能够理解了资源加载的全过程了。这4个变量的类型如下:
ResourceManager定义的几个变量
m_AssetBundleManifest
理解m_AssetBundleManifest之前,需要先理解Unity3D的依赖打包。前面的tank.unity3D中,坦克的预设、坦克的贴图等资源都被打包到一起,没有依赖关系(只要打包时不给贴图单独打包,Unity3D会自动将预设相关的资源都打包进来)。如下图所示。
前面加载坦克制作的资源包
假如有两个坦克预设共用一套贴图,如果像上面那样打包,每个坦克预设各自包含一份贴图,资源会比较大。更好的办法是将公共贴图放到一个包里,每个坦克预设不再包含贴图(如下图)。这种打包方式下,加载TankPrefab前,需要先加载依赖包common.unity3D,坦克预设才能找到贴图。
依赖打包
打包后,Unity3D会产生一个名为AssetBundle.manifest的文件(框架会将该文件放在StreamingAssets中),该文件包含所有包的依赖信息。所以在加载资源前需要先加载这个文件,m_AssetBundleManifest便是指向这个包的变量。相关代码如下:
m_AssetBundleManifest便是指向AssetBundle.manifest的变量
加载这个包后,便可以使用下面的语句获取某个包所依赖的所有包名,然后加载它们。
string[] dependencies = m_AssetBundleManifest.GetAllDependencies(包名);
注:更多Unity3D的依赖打包的解释,可以参见这篇文章:
m_LoadedAssetBundles
字典类型的m_LoadedAssetBundles保存了所有已经加载资源包。如果某个包已经被加载过,那下次需要用到它时,直接从字典中取出即可,减少重复加载。简化后的代码如下:
IEnumerator OnLoadAsset(XXX)
AssetBundleInfo bundle = GetLoadedAssetBundle(XXX);
if(!bundle)
OnLoadAssetBundle(名字);
回调函数处理
其中GetLoadedAssetBundle方法会判断资源包是否存在于m_LoadedAssetBundles中,并返回资源包。OnLoadAssetBundle为重新加载资源包的方法。
加载资源包后,只需通过 bundle.LoadAssetAsync(资源名,类型)便可加载所需的资源。
m_Dependencies
m_Dependencies记录了所有已加载资源的依赖包,以便在GetLoadedAssetBundle方法中判断资源包是否被完全加载(主体包和所有依赖包都被加载才算完成加载)。简化后的代码如下:
IEnumerator OnLoadAssetBundle(包名)
//获取依赖包
string[] dependencies = m_AssetBundleManifest.GetAllDependencies(abName);
m_Dependencies.Add(abName, dependencies); //更新依赖表
//加载依赖包
for (int i = 0; i & dependencies.L i++)
OnLoadAssetBundle(XXX)
//然后加载主体资源
download = http://WWW.LoadFromCacheOrDownload(包名)
//更新加载表
m_LoadedAssetBundles.Add(XXX)
AssetBundleInfo GetLoadedAssetBundle(包名)
//判断加载表
AssetBundleInfo bundle = m_LoadedAssetBundles[包名];
if (bundle == null)
//判断依赖包
foreach (string 依赖包名 in m_Dependencies[包名])
if (m_LoadedAssetBundles[依赖包名]== null)
m_LoadRequests
m_LoadRequests是一个&类型的字典,LoadAssetRequest的定义如下,它用于异步加载后的回调。填写图片摘要(选填)
LoadAssetRequest&
由于采用异步加载,加载资源的过程中,程序有可能发起同一个请求,多次加载似乎有些浪费,如下图所示。
两次加载同一资源
更好的办法是,在收到第2次请求时先做判断,如果该资源正在加载,那先记录请求2的回调函数,待资源加载完成,调用所有请求该资源的回调函数,如下图所示。m_LoadRequests便记录每种资源的请求,使程序可以判断该资源是否正在加载,并从m_LoadRequests获取各个资源请求的回调函数。
&记录请求2的回调函数
简化后的代码如下:
void LoadAsset(包名)
If(m_LoadRequests[abName] == null)
m_LoadRequests[包名].Add(回调函数等);
OnLoadAsset();
m_LoadRequests[包名].Add(回调函数等);
IEnumerator OnLoadAsset(XXX)
foreach( request in
m_LoadRequests[包名] )
Request.回调函数();
5、资源热更新
“资源热更新”和上一篇的“代码热更新”完全相同,开启更新模式后,将新的资源文件复制到服务器上,框架即可自动下载更新的资源。这里不再复述。
笔者也是刚刚接触LuaFramework不久,想看看是否适合于工作室接下来的项目,文章错误之处在所难免,请大家多加包涵。继续是广告时间:
《十六年的长度,记录中国独立游戏》笔者的一篇文章,盘点了1998年以来我国一些独立游戏,以及独立游戏作者的经历。相信对有志于开发独立游戏的读者有所帮助。
博客等级:
博客积分:0
博客访问:11,559
关注人气:0
荣誉徽章: