前言:为什么要用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 >= )
{
// 约定:第一个返回的Table对象作为Lua模块
this.LuaModule = luaRet[] 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" 
   
local demoComponent = {} 
   
function demoComponent:Awake( gameObject ) 
    Debug.Log(gameObject.name.."Awake") 
end 
   
return demoComponent 

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

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

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

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

[AppleScript] 纯文本查看 复制代码
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
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 

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

在Unity中使用Lua脚本的更多相关文章

  1. Unity3D热更新之LuaFramework篇[07]--怎么让unity对象绑定Lua脚本

    前言 在上一篇文章 Unity3D热更新之LuaFramework篇[06]--Lua中是怎么实现脚本生命周期的 中,我分析了由LuaBehaviour来实现lua脚本生命周期的方法. 但在实际使用中 ...

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

    第一步(环境准备工作): 工具: ●LuaForWindows_v5.1.4-46.exe傻瓜式安装. 作用:此工具可以在windows环境下编译运行Lua脚本程序.安装完成后会有两个图标:Lua和S ...

  3. 在redis中使用lua脚本

    在实际工作过程中,可以使用lua脚本来解决一些需要保证原子性的问题,而且lua脚本可以缓存在redis服务器上,势必会增加性能. 不过lua也会有很多限制,在使用的时候要注意. 在Redis中执行Lu ...

  4. redis中使用lua脚本

    lua脚本 Lua是一个高效的轻量级脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能 使用脚本的好处 1.减少网络开销,在Lua脚 ...

  5. 新姿势!Redis中调用Lua脚本以实现原子性操作

    背景:有一服务提供者Leader,有多个消息订阅者Workers.Leader是一个排队程序,维护了一个用户队列,当某个资源空闲下来并被分配至队列中的用户时,Leader会向订阅者推送消息(消息带有唯 ...

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

    [COCOS2DX-LUA 脚本开发之一]在Cocos2dX游戏中使用Lua脚本进行游戏开发(基础篇)并介绍脚本在游戏中详细用途! 分类: [Cocos2dx Lua 脚本开发 ] 2012-04-1 ...

  7. 怎样在Cocos2d-x中使用Lua脚本

    版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/u013321328/article/details/25699545 笔者使用的是Cocos2d-x ...

  8. 服务降级 托底预案 Nginx中使用Lua脚本检测CPU使用率,当达到阀值时开启限流,让用户排队

    https://mp.weixin.qq.com/s/FZAcQQAKomGEe95kln1HCQ 在京东我们是如何做服务降级的 https://mp.weixin.qq.com/s/FZAcQQAK ...

  9. Redis中的原子操作(2)-redis中使用Lua脚本保证命令原子性

    Redis 如何应对并发访问 使用 Lua 脚本 Redis 中如何使用 Lua 脚本 EVAL EVALSHA SCRIPT 命令 SCRIPT LOAD SCRIPT EXISTS SCRIPT ...

随机推荐

  1. Visual Studio 20年

    这是一个暴露年龄的话题,如果一个程序员从第一个版本开始使用Visual Studio的话,现在应该是40多岁的中年大叔了.我的程序员生涯是从Visual basic 6.0 (vb6)开始的,一晃就过 ...

  2. CSS 随笔

    1.动态修改div的大小 Html: <div> Hello </div> css: div { resize:both; overflow:auto; } 2. box-si ...

  3. 图算法之——dijkstra算法

    一.算法特点 目标:找出加权图中前往X的最短路径 适用于:无环有向加权图,且各边的权值为正 二.算法思路 三.算法示例演示 如下图,请找出结点v1到其他各个结点的最短路径: 首先创建一个字典(散列表) ...

  4. Centos下添加用户并赋权

    创建新用户 创建一个用户名为:linuxidc [root@localhost ~]# adduser linuxidc 为这个用户初始化密码,linux会判断密码复杂度,不过可以强行忽略: [roo ...

  5. Vue.js模拟百度下拉框

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  6. Angularjs中的Controller

    概念:一个应用(APP,本身也是一个大模块)是由若干个模块(module)组成的,每个模块实现一个功能.利于代码的复用. 书写格式: <!DOCTYPE html> <html ng ...

  7. (转)centos6.5 bind-DNS服务器bind的搭建详解

    centos6.5环境DNS-本地DNS服务器bind的搭建 域名系统(英文:Domain Name System,缩写:DNS)是因特网的一项服务.它作为将域名和IP地址相互映射的一个分布式数据库, ...

  8. tkinter widget

    tkinter messagebox

  9. 销售人员的分析,也可以用类似RFM的思路吗?

    本文转自知乎 作者:接地气的陈老师 ————————————————————————————————————————————————————— 有同学问:“销售人员的分析,也可以用类似RFM的思路吗, ...

  10. py库: pyautogui (自动测试模块,模拟鼠标、键盘动作)

    PyAutoGUI 是一个人性化的跨平台 GUI 自动测试模块 pyautogui 库 2017-10-4 pip install pyautogui python pip.exe install p ...