在Unity中使用Lua脚本
前言:为什么要用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相应的回调函数;
例如:
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”对象,绑定了如下脚本:
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组件的成员:
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脚本的更多相关文章
- Unity3D热更新之LuaFramework篇[07]--怎么让unity对象绑定Lua脚本
前言 在上一篇文章 Unity3D热更新之LuaFramework篇[06]--Lua中是怎么实现脚本生命周期的 中,我分析了由LuaBehaviour来实现lua脚本生命周期的方法. 但在实际使用中 ...
- C++中嵌入Lua脚本环境搭建
第一步(环境准备工作): 工具: ●LuaForWindows_v5.1.4-46.exe傻瓜式安装. 作用:此工具可以在windows环境下编译运行Lua脚本程序.安装完成后会有两个图标:Lua和S ...
- 在redis中使用lua脚本
在实际工作过程中,可以使用lua脚本来解决一些需要保证原子性的问题,而且lua脚本可以缓存在redis服务器上,势必会增加性能. 不过lua也会有很多限制,在使用的时候要注意. 在Redis中执行Lu ...
- redis中使用lua脚本
lua脚本 Lua是一个高效的轻量级脚本语言,用标准C语言编写并以源代码形式开放, 其设计目的是为了嵌入应用程序中,从而为应用程序提供灵活的扩展和定制功能 使用脚本的好处 1.减少网络开销,在Lua脚 ...
- 新姿势!Redis中调用Lua脚本以实现原子性操作
背景:有一服务提供者Leader,有多个消息订阅者Workers.Leader是一个排队程序,维护了一个用户队列,当某个资源空闲下来并被分配至队列中的用户时,Leader会向订阅者推送消息(消息带有唯 ...
- 【COCOS2DX-LUA 脚本开发之一】在Cocos2dX游戏中使用Lua脚本进行游戏开发(基础篇)并介绍脚本在游戏中详细用途!
[COCOS2DX-LUA 脚本开发之一]在Cocos2dX游戏中使用Lua脚本进行游戏开发(基础篇)并介绍脚本在游戏中详细用途! 分类: [Cocos2dx Lua 脚本开发 ] 2012-04-1 ...
- 怎样在Cocos2d-x中使用Lua脚本
版权声明:本文为博主原创文章.未经博主同意不得转载. https://blog.csdn.net/u013321328/article/details/25699545 笔者使用的是Cocos2d-x ...
- 服务降级 托底预案 Nginx中使用Lua脚本检测CPU使用率,当达到阀值时开启限流,让用户排队
https://mp.weixin.qq.com/s/FZAcQQAKomGEe95kln1HCQ 在京东我们是如何做服务降级的 https://mp.weixin.qq.com/s/FZAcQQAK ...
- Redis中的原子操作(2)-redis中使用Lua脚本保证命令原子性
Redis 如何应对并发访问 使用 Lua 脚本 Redis 中如何使用 Lua 脚本 EVAL EVALSHA SCRIPT 命令 SCRIPT LOAD SCRIPT EXISTS SCRIPT ...
随机推荐
- CS229 6.3 Neurons Networks Gradient Checking
BP算法很难调试,一般情况下会隐隐存在一些小问题,比如(off-by-one error),即只有部分层的权重得到训练,或者忘记计算bais unit,这虽然会得到一个正确的结果,但效果差于准确BP得 ...
- 初级安全入门——XSS注入的原理与利用
XSS的简单介绍 跨站脚本攻击(Cross Site Scripting),为不和层叠样式表(Cascading Style Sheets,CSS)的缩写混淆,故将跨站脚本攻击缩写为XSS.恶意攻击者 ...
- JS自学总结的零散知识点
1.使用new关键字的时候后面不能接这种变量而是接一个结构constructor 例如由function引导的结构 而不是像如下这样 var car={ lunzi : 4}; 这个只是一个变量而不是 ...
- springmvc接收前台(如ajax)传来的数组list,set等图文详解
ref:https://blog.csdn.net/wabiaozia/article/details/50803581 前言: 相信很人都被springmvc接收数组问题折磨过,查过几个解决 ...
- Python之网络编程(Socket)
1.网络通信原理与互联网协议 详见:https://www.cnblogs.com/JackLi07/p/9218039.html 2.socket层 以上是tcp/ip五层协议的结构图,我们没有看到 ...
- Spring和SpringBoot比较,解惑区别
1.概述: 对于Spring和SpringBoot到底有什么区别,我听到了很多答案,刚开始迈入学习SpringBoot的我当时也是一头雾水,随着经验的积累.我慢慢理解了这两个框架到底有什么区别,我相信 ...
- ajax,jsonp跨域访问数据
访问高德aip天气接口 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> & ...
- android 开发 实现多个动态权限的方法(并且兼容6.0以下的版本权限授权)
android开发权限授权因为版本的不同有不同的授权方式,6.0以下的版本使用的是在注册表中添加权限的静态授权(这种授权权限提示只会出现在app安装的时候),而6.0以上(包含6.0)就需要动态授权的 ...
- 6、申请发布(Distribution)证书和描述文件
发布(Production)证书用于正式发布环境下使用,用于提交到Appstore审核发布. 申请发布(Production)证书 在“Certificates, Identifiers & ...
- dubbo 学习资料
入门: http://www.tuicool.com/articles/FnE3em http://www.cnblogs.com/xuyatao/p/6869231.html 最好 http://w ...