在Unity中使用Lua脚本

摘要:
那么为什么要统一使用Lua呢?还有一些次要的原因:方便进行功能的热更新;Lua语言的深度和广度很小,易于学习和使用,可以降低项目成本。我看过一个网友的计划,每次脚本运行时都会创建一个新的LuaState。我个人认为这个计划非常不合适。出于以上原因,我认为游戏的Lua代码都运行在LuaState之上。这样,我们Lua代码的实现方法就是编写和编写一个完全一致的C#脚本组件,可以说实现了与引擎的无缝集成。

前言:为什么要用Lua
首先要说,所有编程语言里面,我最喜欢的还是C#,VisualStudio+C#,只能说太舒服了。所以说,为什么非要在unity里面用Lua呢?可能主要是闲的蛋疼。。。。。另外还有一些次要原因:
方便做功能的热更新;
Lua语言的深度和广度都不大,易学易用,可以降低项目成本。

C#与Lua互相调用的方案
坦白来将,我并没有对现在C#与Lua互相调用的所有库进行一个仔细的调研,大概搜了一下,找到这样几个:
slua:https://github.com/pangweiwei/slua
Nlua:http://nlua.org/
UniLua:https://github.com/xebecnan/UniLua
uLua插件
以上这些方案的具体内容,不是本文的重点,这里就不说了,感兴趣的同学,点开自己去看就行了。

最后我选用了uLua,主要原因是:uLua方案比较成熟,它并没有太多自己的代码,主要是把LuaInterface和Lua解释器整合了一下,都是比较成熟的代码,相对会稳定一些。另外,个人很欣赏LuaInterface这个库。接下来我们就看一下uLua。:)
uLua插件的使用非常简单,基本上看一下他自带的几个例子就明白了。

游戏逻辑粘合层设计
uLua插件解决了语言层面的问题:C#与LUA两种语言代码互相调用,以及参数传递等相关的一系列底层问题。而我们游戏逻辑开发中,到底如何使用LUA是上层的一个问题。下面给出我摸索的一个方案,个人认为:够简单,够清晰,是很薄很薄的一层,不可能更薄了。

使用几个LuaState?
曾经看过一个网友的方案,每次运行脚本就new一个LuaState,个人认为这种方案十分不妥。整个游戏的Lua代码应该运行在一个LuaState之上,原因有二:
运行在同一LuaState的Lua代码才能互相调用啊。相信一个游戏总会有一定的代码量的,如果不同的lua文件之中的代码,完全独立运行,不能互相调用或者互相调用很麻烦,则游戏逻辑组织平添很多障碍;
混合语言编程中原则之一就是:尽量减少代码执行的语言环境切换,因为这个的代价往往比代码字面上看上去要高很多。我的目标是:既然用了Lua,就尽量把UI事件响应等游戏上层逻辑放到Lua代码中编写。
基于以上原因,我觉得游戏的Lua代码全都跑在一个LuaState之上。这也是本文方案的基础。

实现LuaComponent
首先说一下我的目标:
既然C#对于Unity来说是脚本层了,那么Lua应该和C#脚本代码具有相同的逻辑地位;
Lua整合的代码应该很少,应尽量保持简单;
基于以上的目标,我实现了LuaComponet类,它的实现类似MonoBehavior,只不过我们没有C++源代码,只能由C#层的MonoBehavior来转发一下调用。这样,我们的Lua代码的实现方式就是写和写一个C#脚本组件完全一致了,可以说达到了和引擎天衣无缝的整合。:)OK,先上代码!

using UnityEngine;  
using System.Collections;  
using LuaInterface;  
   
/// <summary>  
/// Lua组件 - 它调用的Lua脚本可以实现类似MonoBehaviour派生类的功能  
/// </summary>  
[AddComponentMenu("Lua/LuaComponent")]  
public class LuaComponent : MonoBehaviour  
{  
    private static LuaState s_luaState; // 全局的Lua虚拟机  
   
    [Tooltip("绑定的LUA脚本路径")]  
    public TextAsset m_luaScript;  
   
    public LuaTable LuaModule  
    {  
        get;  
        private set;  
    }  
    LuaFunction m_luaUpdate;    // Lua实现的Update函数,可能为null  
   
    /// <summary>  
    /// 找到游戏对象上绑定的LUA组件(Module对象)  
    /// </summary>  
    public static LuaTable GetLuaComponent(GameObject go)  
    {  
        LuaComponent luaComp = go.GetComponent<luacomponent>();  
        if (luaComp == null)  
            return null;  
        return luaComp.LuaModule;  
    }  
   
    /// <summary>  
    /// 向一个GameObject添加一个LUA组件  
    /// </summary>  
    public static LuaTable AddLuaComponent(GameObject go, TextAsset luaFile)  
    {  
        LuaComponent luaComp = go.AddComponent<luacomponent>();  
        luaComp.Initilize(luaFile);  // 手动调用脚本运行,以取得LuaTable返回值  
        return luaComp.LuaModule;  
    }  
   
    /// <summary>  
    /// 提供给外部手动执行LUA脚本的接口  
    /// </summary>  
    public void Initilize(TextAsset luaFile)  
    {  
        m_luaScript = luaFile;  
        RunLuaFile(luaFile);  
   
        //-- 取得常用的函数回调  
        if (this.LuaModule != null)  
        {  
            m_luaUpdate = this.LuaModule["Update"] as LuaFunction;  
        }  
    }  
   
    /// <summary>  
    /// 调用Lua虚拟机,执行一个脚本文件  
    /// </summary>  
    void RunLuaFile(TextAsset luaFile)  
    {  
        if (luaFile == null || string.IsNullOrEmpty(luaFile.text))  
            return;  
   
        if (s_luaState == null)  
            s_luaState = new LuaState();  
   
        object[] luaRet = s_luaState.DoString(luaFile.text, luaFile.name, null);  
        if (luaRet != null && luaRet.Length >= 1)  
        {  
            // 约定:第一个返回的Table对象作为Lua模块  
            this.LuaModule = luaRet[0] as LuaTable;  
        }  
        else 
        {  
            Debug.LogError("Lua脚本没有返回Table对象:" + luaFile.name);  
        }  
    }  
   
    // MonoBehaviour callback  
    void Awake()  
    {  
        RunLuaFile(m_luaScript);  
        CallLuaFunction("Awake", this.LuaModule, this.gameObject);  
    }  
   
    // MonoBehaviour callback  
    void Start()  
    {  
        CallLuaFunction("Start", this.LuaModule, this.gameObject);  
    }  
   
    // MonoBehaviour callback  
    void Update()  
    {  
        if (m_luaUpdate != null)  
            m_luaUpdate.Call(this.LuaModule, this.gameObject);  
    }  
   
    /// <summary>  
    /// 调用一个Lua组件中的函数  
    /// </summary>  
    void CallLuaFunction(string funcName, params object[] args)  
    {  
        if (this.LuaModule == null)  
            return;  
   
        LuaFunction func = this.LuaModule[funcName] as LuaFunction;  
        if (func != null)  
            func.Call(args);  
    }  
}  



这段代码非常简单,实现以下几个功能点:

  • 管理一个全局的LuaState;
  • 负责将MonoBehavior的调用转发到相应的LUA函数;
  • 提供了GetComponent()、AddComponent()对应的LUA脚本版本接口;这点非常重要。


LUA代码约定
为了很好的和LuaComponent协作,Lua脚本需要遵循一些约定:

  • LUA脚本应该返回一个Table,可以是LUA的Module,也可以是任何的Table对象;
  • 返回的Table对象应该含有MonoBehaviour相应的回调函数;

例如:

[AppleScript] 纯文本查看 复制代码
1
2
3
4
5
6
7
8
9
require "EngineMain" 
   
localdemoComponent ={} 
   
function demoComponent:Awake(gameObject ) 
    Debug.Log(gameObject.name.."Awake") 
end 
   
returndemoComponent 



LuaComponent回调函数中,主动将GameObject对象作为参数传递给Lua层,以方便其进行相应的处理。

Lua组件之间的互相调用(在Lua代码中)
基于以上结构,就很容易实现Lua组件之间的互相调用。在Demo工程中,有一个“Sphere”对象,绑定了如下脚本:

[AppleScript] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
require "EngineMain" 
   
localsphereComponent ={} 
   
sphereComponent.text="Hello World" 
   
function sphereComponent:Awake(gameObject ) 
    Debug.Log(gameObject.name.."Awake") 
end 
   
returnsphereComponent 



还有另外一个“Cube”对象,绑定了如下脚本,用来演示调用上面这个Lua组件的成员:

[AppleScript] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
require "EngineMain" 
   
localdemoComponent ={} 
   
function demoComponent:Awake(gameObject ) 
    Debug.Log(gameObject.name.."Awake") 
end 
   
function demoComponent:Start(gameObject ) 
    Debug.Log(gameObject.name.."Start") 
   
    --演示LuaComponent代码互相调用 
    localsphereGO =GameObject.Find("Sphere") 
    localsphereLuaComp =LuaComponent.GetLuaComponent(sphereGO) 
    Debug.log("Sphere.LuaDemoB:"..sphereLuaComp.text) 
   
end 
   
returndemoComponent 




最后,顺带总结一下:在设计上次游戏逻辑框架时,比较好的思路是:在透彻的理解Unity自身架构的前提下,在其架构下进行下一层设计,而不是想一种新的框架。因为Unity本身就是一个框架。更多内容请参见作者博客:http://blog.csdn.net/neil3d/article/details/44200821

免责声明:文章转载自《在Unity中使用Lua脚本》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇VSCode插件开发全攻略(八)代码片段、设置、自定义欢迎页简单自定义控件在view下可以运行在传统模式下运行显示空白下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

emlog模板制作帮助手册

emlog基本模板文件 必须文件: main.css: CSS(样式表)文件 log_list.php: 日志列表页模板 echo_log.php: 日志页模板 page.php: 页面(page)模板 footer.php: Footer模板 header.php: Header模板 t.php: 碎语模板 module.php: 包含widgets、评...

Redis——redis使用redis-dump,redis-load导出导入数据——【三】

来源 https://www.cnblogs.com/dadonggg/p/8662455.html https://blog.csdn.net/chenxinchongcn/article/details/78666374 安装过程 安装ruby 安装redis-dump之前首先需要安装ruby $ yum install ruby rubygems...

利用thrift在c++、java和python之间相互调用

转自:http://blog.csdn.net/andy_yf/article/details/7487384 thrift做为跨语言调用的方案有高效,支持语言较多,成熟等优点;代码侵入较强是其弱点。 下面记录以C++做服务器,C++,java和python做客户端的示例,这个和本人现在工作环境吻合,使用多线程长连接的socket来建立高效分布式系统的跨语...

Centos7(Liunx)安装PHP7.4版本

yum安装: yum相当于是自动化安装,你不用管软件的依赖关系,在yum安装过程是帮你把软件的全部依赖关系帮你一键完成。而且现在Centos7的服务启动已经换成systemctl命令来控制了。通过yum安装会帮你自动注册服务,你可以通过systemctl start xxx.service启动服务,方便快捷。但是缺点是yum安装你没办法干预,安装的...

UE4/Unity绘制地图基础元素-面和体

前言 绘制地图基础元素-线(上篇) 绘制地图基础元素-线(下篇) 搞定地图画线之后,接下来就是绘制面和体了: 面作为地图渲染的基本元素之一,在地图中可以代表各种形式的区域,例如海面、绿地等。面数据通常以离散点串形式存储,因此渲染时最关注的是如何将其展现为闭合的图形。 体可以理解为带有高度的面,在地图中代表各种建筑,通常是由其顶部面数据和高度数据处理得到。...

Delphi指针的用法

DELPHI指针的使用 大家都认为,C语言之所以强大,以及其自由性,很大部分体现在其灵活的指针运用上。因此,说指针是C语言的灵魂,一点都不为过。同时,这种说法也让很多人产生误解,似乎只有C语言的指针才能算指针。Basic不支持指针,在此不论。其实,Pascal语言本身也是支持指针的。从最初的Pascal发展至今的Object Pascal,可以说在指针运用...