在上一篇文章中我们通过一个小的案例,介绍了ToLua在Unity中的基本使用方法,而这次,我们将通过一个更为复杂的例子,继续深入了解ToLua的使用方法及其原理。

ToLua文件目录

我们首先来了解一下ToLua的文件目录。

Tolua集成主要分为两部分,一部分是运行时需要的代码包括一些手写的和自动生成的绑定代码,另一部分是编辑器相关代码,主要提供代码生成、编译lua文件等操作,具体就是Unity编辑器中提供的功能。

接下来我们具体介绍一下Tolua文件列表中文件的用途:

1.Editor

Editor下Custom/CustomSettings.cs 自定义配置文件,用于定义哪些类作为静态类型、哪些类需要导出、哪些附加委托需要导出等

我们需要注册到Lua中的类型也都需要在这里导入,在Tolua中已经为我们提供了Unity大部分基础类型,若我们需要导入自己的类型或Tolua没有导入的类型可以在其中添加,如下图所示:

2.Source

在Source文件夹中有Generate文件夹及LuaConst.cs脚本,Generate中主要是生成用于交互的绑定代码wrap脚本,LuaConst.cs是一些lua路径等配置文件。 若在CustomSettings中做了修改,需要在菜单栏的Lua选项中,重新生成的Wrap文件,当重新生成Wrap文件后,会发现我们新添加类型也生成了相应的Wrap文件,如下图所示:

Clear wrap files后会自动重新生成wrap文件

3.ToLua

Tolua文件夹中有如下文件

1)BaseType: 一些基础类型的绑定代码

2)Core: 提供的一些核心功能,包括封装的「LuaFunction」「LuaTable」 「LuaThread」「LuaState」「LuaEvent」、调用tolua原生代码等等。

3)Examples: Tolua示例

4)Misc: 杂项,包含LuaClient,LuaCoroutine(协程),LuaLooper(用于tick),LuaResLoader(用于加载lua文件)

5)Reflection: 反射相关

我们这里只了解一下Tolua中的文件结构及相关文件的作用,具体的脚本绑定及生成流程我们不做过多赘述,如需要了解可以查询相关资料,若有较多反馈,在之后我们可以开一篇新的文章,具体介绍tolua


Tolua跳一跳

现在我们已经大致了解了Tolua这个方案,接下来我们通过一个Demo,来看在Unity中,我们如何使用Tolua开发项目。

本文中以一个仿照微信跳一跳的小游戏作为案例来讲解,案例非常简单,但希望读者有unity基础,本文主要讲解Tolua的用法,代码逻辑方面的讲解可能会相对偏少

一.开发前准备

在之前,我们已经导入了Tolua资源。在这个项目中,我们需要使用到DoTween插件,可以在Asset Store自行下载。

二.Lua虚拟机管理器

  • 我们需要用C#脚本来开启Lua虚拟机并调用Lua模块,那么不同的逻辑就会有不同的C#脚本来开启虚拟机并调用Lua模块,这无疑是很耗费性能且繁琐的,所以我们可以自己做一些封装,先将C#脚本中所必须的方法做一个缓存,如下代码所示: LuaManager.cs
public class LuaManager : MonoBehaviour {private static LuaManager _instance;public static LuaManager Instance{get{return _instance;}}private LuaClient _luaClient;public LuaClient LuaClient{get{return _luaClient;}}void Awake () {_instance = this;//跨场景不销毁DontDestroyOnLoad(this.gameObject);_luaClient = this.gameObject.AddComponent<LuaClient>();}
}
  • 在代码中,我们直接使用LuaClient,LuaClient我们可以理解成是ToLua内部对自己的一种封装,可以视为tolua环境的一个启动。我们需要将LuaClient中的protected LuaState luaState = null;改为public,同时我们可以在LuaClient中再封装一个调用Lua模块函数的方法。
public virtual void CallFunc(string func, GameObject obj){LuaFunction luaFunc = luaState.GetFunction(func);luaFunc.Call(obj);luaFunc.Dispose();luaFunc = null;}

然后我们在场景中创建一个空物体,添加LuaManager.cs脚本

三.自建C#方法工具类

  • 在使用Tolua开发中,ToLua提供的方法有限,有时我们可能找不到很好的方法来代替C#中的功能,或者不清楚某个功能的使用方法,这时候我们可以在C#中封装好一些功能,然后导入到Lua中,便可以直接在Lua中使用。这里可以创建一个C#脚本,我们命名为Util.cs,因为Lua中数值只存在number类型,如果需要我们可以封装int和float类型
public static int Int(object o){return Convert.ToInt32(o);}
public static float Float(object o){return (float)Math.Round(Convert.ToSingle(o), 2);}
  • 同时还有我们可能需要使用到的Dotween的部分方法
    public static void DoMove(GameObject obj, Vector3 vec, float time){obj.transform.DOMove(vec, time);}public static void DoScale(GameObject obj, float f, float time){obj.transform.DOScale(f, time);}public static void DoScale(GameObject obj, Vector3 vec, float time){obj.transform.DOScale(vec, time);}

之前我们介绍过,如果需要添加导入lua的类型,需要在CustomSettings.cs中添加

可以看到我们在其中加入了 _GT(typeof(Util)),然后重新生成wrap文件

如图所示,Source/Generate中生成了UtilWrap文件

四.开始界面

再之前的动图中可以看到我们的项目中的开始界面,只搭建了背景及一个开始按钮,读者也可以自行扩展。 * 这里我们需要用到Button事件,我们同样可以通过封装一个C#脚本给lua提供一个按钮事件

BtnEvent.cs

public class UIEvent : MonoBehaviour {public static void AddButtonOnClick(GameObject game, LuaFunction function){if (game == null)return;Button btn = game.GetComponent<Button>();btn.onClick.AddListener(delegate () {function.Call(game);});}
}
  • 接下来就是在lua中的调用了。 我们可以在Project面板中找到lua文件夹,我们可以把我们的Lua脚本文件放在这个文件夹下(当然,也可以根据自己的习惯修改Lua文件夹,但我们查找Lua文件的路径就需要修改),文件夹下有名为Main.lua的Lua文件,在这个脚本中,我们可以定义我们所需的全局类型:
--主入口函数。从这里开始lua逻辑
--这里定义我们所需的全局类型
function Main()GameObject = UnityEngine.GameObjectTransform = UnityEngine.TransformParticleSystem = UnityEngine.ParticleSystemColor = UnityEngine.ColorUtil = Util.New()SceneManagement = UnityEngine.SceneManagementInput = UnityEngine.InputKeyCode = UnityEngine.KeyCodeTime = UnityEngine.TimeCamera = UnityEngine.CameraAudioSource = UnityEngine.AudioSourceResources = UnityEngine.Resourceswww = UnityEngine.WWWprint("logic start")
end--场景切换通知
function OnLevelWasLoaded(level)collectgarbage("collect")Time.timeSinceLevelLoad = 0
endfunction OnApplicationQuit()
end
  • 然后我们创建Login.lua,我们的开始界面逻辑将会写在这个脚本中:
Login = {}--定义Login类
local this = Login
require('Music')--加载Music模块
local ui
local manager
function this.Awake(object)manager = GameObject.Find('Manager')manager : AddComponent(typeof(AudioSource))coroutine.start(Music.PlaySound)--开启协程ui = objectlocal loginBtn = ui.transform : Find("Login").gameObjectUIEvent.AddButtonOnClick(loginBtn, LoginOnClick)
endfunction LoginOnClick()--场景切换SceneManagement.SceneManager.LoadScene("Jump")end

在这个lua脚本中,可以看出,其实Lua中调用unity的方法和C#十分相似,想必有Unity基础的读者很容易看明白以上的代码。
其中要注意的是,unity中的物体无法绑定lua脚本,所以无法通过如c#中定义public的值可以在Inspector面板进行赋值,所以代码中我们必须通过GameObject.Find()或者Transform:Find()来找到物体
还有一个重要的地方,需要注意我们调用类的方法、属性、字段时「.」和「:」的区别

  • 我们希望在游戏中有背景音乐,所以在这里,我们使用协程来下载一首音乐,并挂之前创建的在Manager上
--协程下载
--这里使用Tolua中提供的coroutine.www
Music = {}
local this = Music
function this.PlaySound()local audio = GameObject.Find('Manager') : GetComponent('AudioSource')local url = www('https://etnly.oss-cn-shanghai.aliyuncs.com/%E5%B2%A1%E9%83%A8%E5%95%93%E4%B8%80%20-%20%E9%81%BA%E3%82%B5%E3%83%AC%E3%82%BF%E5%A0%B4%E6%89%80%EF%BC%8F%E6%96%9C%E5%85%89.ogg')coroutine.www(url)audio.clip = url : GetAudioClip()audio : Play()
end
  • 接下来我们来看如何在C#中调用刚才写好的lua模块:
public class Login : MonoBehaviour {void Start () {LuaManager.Instance.LuaClient.luaState.DoFile("Login.lua");LuaManager.Instance.LuaClient.CallFunc("Login.Awake", this.gameObject);}}

四.角色逻辑

开始游戏界面完成后,我们就着手于游戏主场景,首先我们可以新建场景,使用plane、cube等基础物体,搭建一个简单的跳一跳场景。

  • 接下来,我们用Lua实现角色的跳跃,这里我们可以使用刚体来让角色实现向前跳跃的动作:
local player
local rigidbody
function this.Awake(obj)player = objrigidbody = player : GetComponent('Rigidbody')
end
function this.StartJump(time)--跳跃逻辑,这里的time可以理解为我们按下按钮的时间 rigidbody : AddForce(Vector3(1, 1, 0) * time * 7,UnityEngine.ForceMode.Impulse)
end 

同样的,首先我们要找到角色,然后获取其中的刚体组建。

  • 在跳一跳游戏中,我们通过某一个按键(这里我们使用空格键)让角色开始跳跃,角色跳跃的力度是更具按下屏幕或者按钮的时间来决定的,所以这里我们需要获取到我们按下按钮的时间
function this.Update()if Input.GetKeyDown(KeyCode.Space) thenstartTime = Time.time--获取按下空格时的时间endif Input.GetKeyUp(KeyCode.Space) thenendTime = Time.time - startTime--计算按下空格至松开的时间this.StartJump(endTime)end
end

然后实现角色在蓄力(也就是按住按钮)时的动作,以及粒子效果,如下图所示

  • 我们的角色是由一个圆柱体和一个球体组成,所以在蓄力时,我们需要压缩圆柱体同时球体的位置也要下移:
if Input.GetKey(KeyCode.Space) then--角色压缩效果if body.transform.localScale.y < 0.11 and body.transform.localScale.y > 0.05 thenbody.transform.localScale = body.transform.localScale + Vector3(1, -1, 1) * 0.05 * Time.deltaTimehead.transform.localPosition = head.transform.localPosition + Vector3(0, -1, 0) * 0.05 * Time.deltaTimeend
end

在这里,body时角色的身体,head时头部,我们都需要先找到物体然后在对其进行操作

  • 蓄力时的粒子效果,读者可以自行在unity中编辑,这里就不做过多赘述,相关代码如下:

particle : GetComponent(‘ParticleSystem’) : Play()

我们应该在按下空格时就开启粒子,所以这里应该放在判断按下空格中

  • 当我们松开空格时,角色需要恢复之前的大小及位置,粒子效果也需要停止,同时角色跳跃。
--DoTween恢复角色
Util.DoScale(body, 0.1, 0.5)
Util.DoLocalMoveY(head, 0.8, 0.5)
particle : GetComponent('ParticleSystem') : Stop()

这里我们用DoTween来恢复角色大小。

  • 关于角色的逻辑,最后一步就是判定角色是否跳到了下一个盒子
--如果跳到此盒子,便给该盒子添加脚本,并移动摄像机
function this.OnCollisionStay(object)if object.transform.tag == 'Cube' thenif(object : GetComponent('BoxControl') == nil) thenthis.CameraMove()object : AddComponent(typeof(BoxControl))endelseif object.transform.tag == 'Plane' then--重新开始游戏Time.timeScale = 0ui : SetActive(true)Continue.ReStart(ui)endendfunction this.CameraMove()--DoTween控制摄像机移动效果Util.DoMove(Camera.main, (player.transform.position + cameraRelativePosition), 1)
end

在这里我们角色如果跳到了下一个盒子,就会给当前盒子添加一个脚本,并移动摄像机,如果没有跳到则打开一个ui界面,并调用一个叫Continue的模块

Continue.lua

Continue = {}
local this = Continue
function this.ReStart(obj)local reStartBtn = obj.transform : Find("ReStart").gameObjectUIEvent.AddButtonOnClick(reStartBtn, ReStartOnClick)
endfunction ReStartOnClick ()SceneManagement.SceneManager.LoadScene("Jump")
end

最后就是角色控制的C#代码,和之前开始界面类似,不过这里多了Update和OnCollisionStay

void Start () {     LuaManager.Instance.LuaClient.luaState.DoFile("Player.lua");LuaManager.Instance.LuaClient.CallFunc("Play.Awake", this.gameObject);
}
void Update () {LuaManager.Instance.LuaClient.CallFunc("Play.Update", gameObject);
}
private void OnCollisionStay(Collision collision)   {        LuaManager.Instance.LuaClient.CallFunc("Play.OnCollisionStay", collision.gameObject);
}

上面提到了会给跳到的盒子加入一个脚本,那么接下来我们就来看关于盒子的逻辑

五.盒子逻辑

  • 首先,当角色跳到当前盒子上,我们就应该把这个脚本绑定到该盒子,再之前角色的逻辑中已经有说明。我们可以在图中可以看到,当按下空格,角色压缩是盒子也会同时被压缩,松开空格后盒子复原

if Input.GetKey(KeyCode.Space) then--盒子压缩效果if currentBox.transform.localScale.y < 0.51 and currentBox.transform.localScale.y > 0.3 thencurrentBox.transform.localScale = currentBox.transform.localScale + Vector3(0, -1, 0) * 0.15 * Time.deltaTimecurrentBox.transform.localPosition = currentBox.transform.localPosition + Vector3(0, -1, 0) * 0.15 * Time.deltaTimeend
end
if Input.GetKeyUp(KeyCode.Space) then--DoTween恢复盒子Util.DoLocalMoveY(currentBox, 0.25, 0.2)Util.DoScale(currentBox, Vector3(oldScale.x, oldScale.y, oldScale.z), 0.2)
end
  • 然后当角色跳到新的盒子上,就应该根据当前位置,生成一个新的大小和颜色随机的盒子,具体代码逻辑如下
function this.GenerateBox()boxPrefab = Resources.Load('Prefabs/Cube')local newBox = GameObject.Instantiate(boxPrefab)--盒子随机位置、大小、颜色randomScale = Util.Random(0.5, 1)newBox.transform.position = currentBox.transform.position + Vector3(Util.Random(1.5, maxDistance), 0, 0)plane.transform.localPosition = plane.transform.localPosition + Vector3(Util.Random(1.5, maxDistance), 0, 0)newBox.transform.localScale = Vector3(randomScale, 0.5, randomScale)newBox : GetComponent('Renderer').material.color = Color(Util.Random(0.0, 1.0), Util.Random(0.0, 1.0), Util.Random(0.0, 1.0))
end
  • 最后一步,就是删除之前的盒子,我们可以判断盒子是否在摄像机范围内(在本项目中,我们摄像机范围内应该只有两个盒子,当前和新生成的盒子),不在摄像机范围内的盒子,我们可以将其删除
function this.Update(obj)if this.IsInView(obj.transform.position) thenGameObject.Destroy(obj, 1)end
end
--判断盒子是否在摄像机范围内,如果不在,便将其销毁
function this.IsInView(worldPos)local cameraTrans = Camera.main.transformlocal viewPos = Camera.main : WorldToViewportPoint(worldPos)local dir = (worldPos - cameraTrans.position).normalizedlocal dot = Vector3.Dot(cameraTrans.forward, dir)if dot > 0 and viewPos.x > 0 and viewPos.x < 1 and viewPos.y > 0 and viewPos.y < 1 thenreturn falseendreturn true
end

BoxControl.cs

void Start () {       LuaManager.Instance.LuaClient.luaState.DoFile("BoxControl.lua");LuaManager.Instance.LuaClient.CallFunc("Box.Awake", this.gameObject);}
void Update () {LuaManager.Instance.LuaClient.CallFunc("Box.Update", this.gameObject);}

到此为止,我们使用Tolua制作的跳一跳小游戏就完成了。项目虽小,但包含了Tolua的大部分功能的常见使用方法。

我们的项目工程会放在github中,大家可自行下载测试及扩展,也可以根据本篇文章制作其他项目来熟悉Tolua的使用。

完整工程在此 :

Etnly/ToLuaDemo​github.com/Etnly/ToLuaDemo正在上传…重新上传取消

Unity3D热更新技术点——ToLua(下)相关推荐

  1. Unity3D热更新技术点——ToLua(上)

    注: 本文主要介绍tolua的基本原理及其在unity中的使用,希望阅读本文的读者有lua基础,可通过Lua教程 (其中也有IDE的推荐等)或其他途径先进行lua 的学习 热更新 在介绍tolua前, ...

  2. Unity3D 热更新方案(集合各位专家的汇总)

    http://blog.csdn.net/guofeng526/article/details/52662994 热更新"这个词,在Unity3D的应用下,是有些语义错误的,但是作为大家都熟 ...

  3. Unity热更新技术整理

    一.热更新学习介绍 1.什么是热更新 举例来说: 游戏上线后,玩家下载第一个版本(70M左右或者更大),在运营的过程中,如果需要更换UI显示,或者修改游戏的逻辑,这个时候,如果不使用热更新,就需要重新 ...

  4. iOS代码质量要求_Unity移动端代码热更新技术学习总结

    为什么需要热更新 游戏总是伴随着不断的开发与维护,我们不能要求玩家每次都将游戏客户端卸载重装,所以需要热更新技术来在不需要重装客户端的情况下下载更新游戏里的代码(其实资源也需要热更新,但是因为操作系统 ...

  5. vue init webpack缺少标识符_Vue脚手架热更新技术探秘

    前言 热替换(Hot Module Replacement)或热重载(Hot Reload)是指在不停机状态下,实时更新,在前端利于来说,在各大框架中及库中都有体现,比如NG从5开始就提供了热更新,R ...

  6. Android热更新技术的研究与实现Sophix

    所以阿里爸爸一直在进步着呢,知道技术存在问题就要去解决问题,这不,从Dexposed-->AndFix-->HotFix-->Sophix,技术是越来越成熟了. Android热更新 ...

  7. 热更新技术简易原理及技术推荐

    为了照顾萌新童鞋,最开始还是对热更新的概念做一个通俗易懂的介绍. 热更新用通俗的讲就是软件不通过应用商店的软件版本更新审核,直接通过应用自行下载的软件数据更新的行为.在用户下载安装App之后,打开Ap ...

  8. 苹果为什么要禁止 JSPatch 等热更新技术?

    声明:本文为作者投稿,CSDN 基于原文进行了完善,版权为原作者所有,未经作者允许,请勿转载. [CSDN有奖征稿啦]技术之路,共同进步,有优质移动开发.VR/AR/MR.物联网原创技术文章欢迎发送邮 ...

  9. 一文解释到底什么是“APP热更新技术”?

    首先,热更新技术作为一种App软件开发者常用的更新方式,简而言之就是用户在下载安装APP之后,会有APP的即时更新. 自从2017年苹果App Store针对热更新的下架事件发生之后,诸多开发者也在不 ...

最新文章

  1. adb install -r ,-d 等的解释
  2. How to list/dump dm thin pool metadata device?
  3. 【知识积累】C#中结构体和类的区别
  4. python函数可选参数传递_Python中函数的参数传递
  5. 汉仪尚巍手书_汉仪尚巍手书免费版
  6. MII/MDIO接口详解(转)
  7. spring dao层注解_Spring– DAO和服务层
  8. 【mybatis】mybatis自定义动态字段查询,mybatis实现动态字段查询,如果某个条件为null,则不查询某个字段,否则就查询某个字段...
  9. pdo_mysql未安装_php pdo_mysql未安装问题解决方法
  10. 浅谈数据库发展史和 OceanBase 的诞生
  11. 戴尔修复已存在12年之久的驱动漏洞,影响数百万个人电脑
  12. java设计模式-工厂系列
  13. 51单片机多种方式点亮LED
  14. 51单片机汇编学习笔记1——内部结构
  15. 惠普m1216硒鼓清零步骤_hpm1213nf清零
  16. 16款最佳的 jQuery Time Picker 时间选择插件
  17. python绘制折线图显示单位_如何使用python语言pygal模块创建折线图并显示
  18. “梦中行千里不如现实行一步。”创业亦是如此丨国仁网络资讯
  19. vue和php前后端分离
  20. 【软件工程-UML建模】

热门文章

  1. AR项目总结之业务架构图
  2. 三创数据分析题库及个人作答
  3. python爬虫式_Python网络爬虫(增量式爬虫)
  4. 番茄工作法图解:简单易行的时间管理方法
  5. centos linux 安装jq,在centos中安装jq时遇到问题
  6. 外卖订单详情界面android,小程序外卖订单界面
  7. 【转发】程序员和设计师必备:全球高清无版权图片网站推荐
  8. OSS中Bucket绑定域名并配置SSL证书
  9. j-flash烧写NXP的S32k1**系列单片机(jlink)
  10. 微信小程序调用后端接口