Unity 项目中怎样正确的使用 Lua?
Unity 项目中怎样正确的使用 Lua? 来使用哥开发的slua吧,http://www.slua.net开源, 和ulua相比快的没有朋友,没有反射,没有额外gc alloc,采用静态代码生成, 可以用于游戏核心逻辑, 完整支持4.6+ UI系统.
同ulua/tolua的性能对比,请看这里 slua benchmark
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
最近刚开始使用Unity引擎,集成Lua的部分写了一篇总结,供参考。
Unity手游开发札记——Lua语言集成 - Funny David的文章 - 知乎专栏
基本的结论是:
1. 做商业游戏在Unity中集成Lua语言是一件不太能避免的事情,只是Lua语言应用的范围是怎样的要仔细确定;
2. 个人在安卓设备上测试的结论和前面钱康来的基本一致,ToLua#在接口调用方面比SLua的稍微好一点,猜测的原因之一是由于SLua在返回值中添加一个bool值来标识函数调用的成功与否;
3. ToLua#和SLua之外都有对应的框架,个人感觉KSFramwork这套框架实现的部分功能不错,单是感觉ToLua#性能稍好,因此在自己的项目中做了一个“”杂交“”。。。 因为项目需要,比较了下uLua和sLua
测试工程见 GitHub - qiankanglai/unity_lua_benchmark: sLua/uLua Performance Test
放几张结果图
ps. 下面的图我为了美观都是对数坐标...
pss. uLua的工程目录我无力吐槽………………
过去半年时间,我们团队完成了一个运营七年的端游项目的口袋版移植工作,还原了mmo的主要元素和玩法。前几天刚刚上线。
我们立项的时候就决定绝大多数代码都使用lua完成。这个主要是为了配合端游的更新,我们做的是互通版,pc端和手机端共用同一个服务器。
而结果证明lua是可以承载一个mmo的运行效率需求的。
我们做的时候有这么一个划分,凡是效率相关的(比如地图排序,寻路),平滑显示相关的(比如人物移动,头顶血条位置)都用c#写。
凡是涉及到服务器通信,逻辑状态的,都在lua写。后面我们为了赶进度,战斗部分并没有重新设计,也是直接lua移植,这个其实会影响运行效率,不过还在可以接受的范围之内,反正我们追求的也并不是最顶级的效率,很多东西都可以用设计而不是代码来优化。
我们使用的是tolua(原ulua)。全局一个luastate。由一个管理类维护。整个游戏的状态由lua来维护。每个可显示对象对应一个actor,这个是一个c#对象,由lua中的逻辑对象(如playermonster)持有并维护。
从现在开发使用的情况来看,c#的开发效率其实是要比lua高的,借助于编译检查和代码补全,写起来非常爽(其实就是vs用着爽)。不过作为一个重度游戏(或者说包体积大于300兆的游戏)更新整包成本其实是非常大的,你可以一个月更新一个版本,但是不能一个月让玩家下一个版本。因为一些bug引发了内存泄露,崩溃,严重逻辑问题这些都是非常蛋疼的,而且绝不是简单的一句之前多测试就可以解决的。从这点来说lua的优势就非常大了。我之前认为游戏不能屈从于热更新,但是现在看来热更新就是大于天的需求,越是依赖于用户粘性的游戏其需求就越大。 Unity3D中使用Lua之UniLua方便动态更新游戏用。
开源项目地址:
xebecnan/UniLua最新支持到Lua5.2,C#版的Lua
基础用法:
大部分的使用是可以参考标准的 Lua 官方文档和 Lua 教程的。 Lua 本身的语法是一样的。C API 和 C# API 之间有个对应关系。例如 lua_pushnumber() 这个 C API 对应到 UniLua 里就是 lua.PushNumber()
所有标准 lua 中 lua.h 和 lauxlib.h 里定义的接口,都对应 LuaAPI.cs 里定义的 ILuaAPI 和
LuaAuxLib.cs 里定义的 ILuaAuxLib 接口。xebecnan/UniLua从 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:
Assets/Behaviour/LuaScriptController.csframework/main.lua// 创建 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) );
}
} xebecnan/UniLua从 Lua 调用 C# 函数 ( 使用 C# 来扩展 Lua 功能 )目前的示例程序是使用 FFI 库来实现的 从 Lua 调用 C# 函数。 FFI 因为用到了反射机制来调用 C# 函数,性能会比较低。应该尽量避免使用,如果没有找到更好的办法,准备之后把这个FFI实现废弃掉。其实直接用 C# 实现一个库的形式,来让 lua 调用这种传统的做法效率会比较高,也是推荐采用的方式。而且也并不会麻烦太多。
比如我现在要实现一个叫 libfoo 的库, 里面提供两个方法: add(a, b) 和 sub(a, b)
库的实现
using UniLua;
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);
return 1;
}
public static int Add(ILuaState lua)
{
var a = lua.L_CheckNumber( 1 ); // 第一个参数
var b = lua.L_CheckNumber( 2 ); // 第二个参数
var c = a + b; // 执行加法操作
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 - b; // 执行减法操作
lua.PushNumber( c ); // 将返回值入栈
return 1; // 有一个返回值
}
}
库的初始化
// 创建 Lua 虚拟机
var Lua = LuaAPI.NewState();
// 加载基本库
Lua.L_OpenLibs();
Lua.L_RequireF( LibFoo.LIB_NAME// 库的名字
, LibFoo.OpenLib // 库的初始化函数
, false // 不默认放到全局命名空间 (在需要的地方用require获取)
);
库的使用 (在 lua 代码中)
// 获取库
local libfoo = require "libfoo.cs"
// 调用库的方法
print(libfoo.add(42, 1))
print(libfoo.sub(42, 22)) xebecnan/UniLuaUTF-8 supportC# 采用 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) 只用来做了配置解析……
C#下的Lua效率很低…大概是C的1/100…不适合做主逻辑 更新:顶楼上,早已转入slua了(http://www.slua.net)
__________________________________________________________
参考:uLua最新的Unity+Lua热更新解决方案!!!【技术综合贴】 呵呵,看了下来感觉untiy里面根本没有用lua的必要啊 这块变化较大,目前(2016年)主要有两个主要的备选方案:slua和tolua。
在Unity中使用Lua脚本:语言层和游戏逻辑粘合层处理
Unity热更新方案(uLua vs sLua)
GitHub - qiankanglai/unity_lua_benchmark: Benchmark for Lua in Unity
页:
[1]
2