在Unity中使用Lua脚本:语言层和游戏逻辑粘合层处理

前言:为什么要用Lua

首先要说,所有编程语言里面,我最喜欢的还是C#,VisualStudio+C#,只能说太舒服了。所以说,为什么非要在Unity里面用Lua呢?可能主要是闲的蛋疼。。。。。另外还有一些次要原因:

  • 方便做功能的热更新;
  • Lua语言的深度和广度都不大,易学易用,可以降低项目成本。

C#与Lua互相调用的方案

坦白来将,我并没有对现在C#与Lua互相调用的所有库进行一个仔细的调研,大概搜了一下,找到这样几个:

  1. slua:https://github.com/pangweiwei/slua
  2. Nlua:http://nlua.org/
  3. UniLua:https://github.com/xebecnan/UniLua
  4. uLua插件

以上这些方案的具体内容,不是本文的重点,这里就不说了,感兴趣的同学,点开自己去看就行了。

最后我选用了uLua,主要原因是:uLua方案比较成熟,它并没有太多自己的代码,主要是把LuaInterface和Lua解释器整合了一下,都是比较成熟的代码,相对会稳定一些。另外,个人很欣赏LuaInterface这个库。接下来我们就看一下uLua。:)

uLua的基本使用

uLua插件的使用非常简单,基本上看一下他自带的几个例子就明白了。

游戏逻辑粘合层设计

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

使用几个LuaState?

曾经看过一个网友的方案,每次运行脚本就new一个LuaState,个人认为这种方案十分不妥。整个游戏的Lua代码应该运行在一个LuaState之上,原因有二:

  1. 运行在同一LuaState的Lua代码才能互相调用啊。相信一个游戏总会有一定的代码量的,如果不同的lua文件之中的代码,完全独立运行,不能互相调用或者互相调用很麻烦,则游戏逻辑组织平添很多障碍;
  2. 混合语言编程中原则之一就是:尽量减少代码执行的语言环境切换,因为这个的代价往往比代码字面上看上去要高很多。我的目标是:既然用了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;

///

/// Lua组件 - 它调用的Lua脚本可以实现类似MonoBehaviour派生类的功能
/// 

[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

    ///

    /// 找到游戏对象上绑定的LUA组件(Module对象)
    /// 

    public static LuaTable GetLuaComponent(GameObject go)
    {
        LuaComponent luaComp = go.GetComponent();
        if (luaComp == null)
            return null;
        return luaComp.LuaModule;
    }

    ///

    /// 向一个GameObject添加一个LUA组件
    /// 

    public static LuaTable AddLuaComponent(GameObject go, TextAsset luaFile)
    {
        LuaComponent luaComp = go.AddComponent();
        luaComp.Initilize(luaFile);  // 手动调用脚本运行,以取得LuaTable返回值
        return luaComp.LuaModule;
    }

    ///

    /// 提供给外部手动执行LUA脚本的接口
    /// 

    public void Initilize(TextAsset luaFile)
    {
        m_luaScript = luaFile;
        RunLuaFile(luaFile);

        //-- 取得常用的函数回调
        if (this.LuaModule != null)
        {
            m_luaUpdate = this.LuaModule["Update"] as LuaFunction;
        }
    }

    ///

    /// 调用Lua虚拟机,执行一个脚本文件
    /// 

    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);
    }

    ///

    /// 调用一个Lua组件中的函数
    /// 

    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相应的回调函数;

例如:

require "EngineMain"

local demoComponent = {}

function demoComponent:Awake( gameObject )
	Debug.Log(gameObject.name.."Awake")
end

return demoComponent

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

Lua组件之间的互相调用(在Lua代码中)

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

require "EngineMain"

local sphereComponent = {}

sphereComponent.text = "Hello World"

function sphereComponent:Awake( gameObject )
	Debug.Log(gameObject.name.."Awake")
end

return sphereComponent

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

require "EngineMain"

local demoComponent = {}

function demoComponent:Awake( gameObject )
	Debug.Log(gameObject.name.."Awake")
end

function demoComponent:Start( gameObject )
	Debug.Log(gameObject.name.."Start")

	--演示LuaComponent代码互相调用
	local sphereGO = GameObject.Find("Sphere")
	local sphereLuaComp = LuaComponent.GetLuaComponent(sphereGO)
	Debug.log("Sphere.LuaDemoB:"..sphereLuaComp.text)

end

return demoComponent

完整版DEMO下载地址:

http://download.csdn.net/detail/neil3d/8583799

http://pan.baidu.com/s/1bnk3ATD 密码: pqih

最后,顺带总结一下:在设计上次游戏逻辑框架时,比较好的思路是:在透彻的理解Unity自身架构的前提下,在其架构下进行下一层设计,而不是想一种新的框架。因为Unity本身就是一个框架。

时间: 2024-10-06 11:54:15

在Unity中使用Lua脚本:语言层和游戏逻辑粘合层处理的相关文章

在Unity中使用Lua脚本

前言:为什么要用Lua首先要说,所有编程语言里面,我最喜欢的还是C#,VisualStudio+C#,只能说太舒服了.所以说,为什么非要在unity里面用Lua呢?可能主要是闲的蛋疼.....另外还有一些次要原因:方便做功能的热更新:Lua语言的深度和广度都不大,易学易用,可以降低项目成本. C#与Lua互相调用的方案坦白来将,我并没有对现在C#与Lua互相调用的所有库进行一个仔细的调研,大概搜了一下,找到这样几个:slua:https://github.com/pangweiwei/sluaN

Java中使用Lua脚本语言(转)

Lua是一个实用的脚本语言,相对于Python来说,比较小巧,但它功能并不逊色,特别是在游戏开发中非常实用(WoW采用的就是Lua作为脚本的).Lua在C\C++的实现我就不多说了,网上随便一搜,到处都是这方面的介绍,我想说的是如何在Java下使用Lua以提高编程效率.增强你的程序可扩展性. 首先,要在Java上使用Lua脚本,必须有关于Lua脚本解释器以及Java程序可以访问这些脚本的相关API,即相关类库.我使用的是一个叫做LuaJava的开源项目,可以在: http://www.keple

【COCOS2DX-LUA 脚本开发之一】在Cocos2dX游戏中使用Lua脚本进行游戏开发(基础篇)并介绍脚本在游戏中详细用途!

[COCOS2DX-LUA 脚本开发之一]在Cocos2dX游戏中使用Lua脚本进行游戏开发(基础篇)并介绍脚本在游戏中详细用途! 分类: [Cocos2dx Lua 脚本开发 ] 2012-04-16 10:08 30803人阅读 评论(18) 收藏 举报 游戏脚本luaanimationpython 本站文章均为李华明Himi原创,转载务必在明显处注明:转载自[黑米GameDev街区] 原文链接: http://www.himigame.com/iphone-cocos2dx/681.htm

在Unity3d中解析Lua脚本的方法

由于近期项目中提出了热更新的需求,因此本周末在Lua的陪伴下度过.对Lua与Unity3d的搭配使用,仅仅达到了一个初窥门径的程度,记录一二于此.水平有限,欢迎批评指正. 网络上关于Lua脚本和Unity3d的配合使用的资料不多,例子工程大多相同.大概了解到针对性的插件有uLua.UniLua.KopiLua三种.试用了前两种,抛开效率与安全性不说,感觉uLua试用起来比较简单,本文只介绍uLua的使用步骤. uLua的原理是在Unity3d中解析字符串形式的Lua脚本,让Lua与C#相互传递参

(五)Lua脚本语言入门

---恢复内容开始--- 写完这篇Lua脚本语言入门,自己就要尝试去用Lua脚本语言写esp8266了,,自己现在挺心急的,因为朋友使用esp8266本来说自己帮忙写好程序的,但是用的单片机不一样自己没有,没有办法测试,用AT指令就显得不方便,还要根据单片机改程序,,而且自己以前用感觉AT指令发信息那块,麻烦,,,,自己知道用脚本去操作8266要比AT指令灵活和稳定的多,真想赶紧学会用Lua脚本去操作8266,那样的话就可以很方便的帮到朋友了......本来答应了,,,,,,,竟然食言了....

C++中嵌入Lua脚本环境搭建

第一步(环境准备工作): 工具: ●LuaForWindows_v5.1.4-46.exe傻瓜式安装. 作用:此工具可以在windows环境下编译运行Lua脚本程序.安装完成后会有两个图标:Lua和SciTE.Lua是命令行,SciTE是图形运行环境,两个都可以编译运行,看个人喜好. ●VS2012大家都会,此处省略若干字... 第二步(在VS2012下新建并运行C++中嵌入Lua脚本程序): ●打开VS2012,新建一个控制台的C++空项目 ●配置Lua的安装路径和引用相关Lua库 1.右击新

(一)Lua脚本语言入门

今天开始自己的Lua语言学习,Lua脚本语言,是介于应用程序和开发其应用程序的底层编程语言之间,,它很方便调用其它语言,它只是在载入时对其进行编译,而不像我们写的单片机程序是预编译的,先编译好然后写入单片机,它只是在调用时编译,所以脚本语言的灵活性很高,,,,,对于自己为什么又开始学习脚本语言了,,因为需要了 ........ 先打印个 hello world 一,变量 Lua有五种变量类型nil,Boolean,string,Number,table 看一个例子,,,,,,,,,先哄我外甥玩,

第二十三篇:在SOUI中使用LUA脚本开发界面

像写网页一样做客户端界面可能是很多客户端开发的理想. 做好一个可以实现和用户交互的动态网页应该包含两个部分:使用html做网页的布局,使用脚本如vbscript,javascript做用户交互的逻辑.当需求变化时,只需要在服务端把相关代码调整一下,用户即可看到新的内容(界面). 传统的客户端程序开发流程和网页开发可能完全不同. 首先是界面的布局,在老式的界面布局过程中,程序员先在界面上放好各种控件,然后需要自己通过相应的代码来维护界面在不同状态下控件的显示状态及位置.当界面中元素很多时,单纯布局

在redis中使用lua脚本

在实际工作过程中,可以使用lua脚本来解决一些需要保证原子性的问题,而且lua脚本可以缓存在redis服务器上,势必会增加性能. 不过lua也会有很多限制,在使用的时候要注意. 在Redis中执行Lua脚本有两种方法:eval和evalsha eval EVAL script numkeys key [key ...] arg [arg ...] 其中: <1> script:你的lua脚本 <2> numkeys:  key的个数 <3> key:redis中各种数据