找回密码
 立即注册
查看: 389|回复: 0

Unity热更新ToLua框架学习

[复制链接]
发表于 2023-4-10 17:18 | 显示全部楼层 |阅读模式
一、Lua语言学习
二、ToLua框架
三、使用ToLua框架对Lua&C#进行交互

四、使用Lua对GameObj进行操作
五、将游戏对象打包&解包
六、将Lua脚本文件进行打包&解包

七、热更新实践
八、使用服务器进行热更新

一、Lua语言学习

Lua是很轻量级的脚本语言,Table也是个很神奇的东西,前面刚开始看的时候以为是Python(╯▔皿▔)╯废话不多说,是菜鸟就点进去吧 Lua菜鸟教程

二、ToLua框架

1.ToLua下载及安装

TuLua下载地址:TuLua
下载后解压并使用Unity以新项目打开,文件加载后进入项目里会有提示是否自动生成常用类型注册文件,点击确定:


现在已经进入TuLua框架了
2.TuLua框架认识

Unity菜单栏里多了Lua选项


资源包里有一下几个文件夹:


1.Editor:里面只有一个CustomSettingsC#脚本,这个脚本是自定义配置文件,相当于一个注册文件,将我们C#类、委托等在里面进行注册后,Lua就能和C#交互了。
2.Lua:存放Lua脚本的一个文件夹,可以把Lua脚本文件放进里面。
3.Plugins:插件目录
4.Source:LuaConst.cs–Lua路径存在的一些配置文件 Generate文件夹:存放一些在CustomSettings里面注册后生成的绑定代码脚本,这些脚本就是将Lua和C#进行绑定交互的。
5.ToLua:
BaseType:基础类型绑定代码
Core:ToLua提供的一些核心功能
Examples:Tolua实例代码,提供学习基础用
Mic:杂项
Reflection:反射相关代码

三、使用ToLua框架对Lua&C#进行交互

尝试在输出面板上使用Lua&C#交互并输出“HelloWorld”:
先创建一个Scripts文件夹,再创建2个脚本LuaTest.cs和Mian.cs


LuaTest.cs:创建一个函数,放回字符串
  1. using System.Collections;using System.Collections.Generic;using UnityEngine;using LuaInterface;publicclassLuaTest: MonoBehaviour
  2. {publicstatic string TestPrint(){return"Hello World";}}
复制代码
然后把LuaTest.cs这个脚本在前面所说的CustomSettings脚本里面去进行注册:


在上图函数进行注册导出:我在函数最末端进行了注册导出


然后点击Unity菜单栏的Lua–>clear warp files,随后会自动弹出是否重新自动生成类型注册,点击确定:


在Source-Generate里面就会生成我妈注册后的交互文件:可以不用管他,现在可以使用Lua对C#进行调用了


在Lua文件夹里面创建一个Lua文件:Test.Lua


打开,可以用专门的Edit打开,也可以用记事本打开,由于我没去下载Edit,我就用记事本了,输入以下代码:
  1. local h = LuaTest.TestPrint()//因为LuaTest这个类上面已经ToLua导出,所以Lua可以直接调用并接收返回值。print(h)//将字符串输出
复制代码
之后,我们在之前创建的Mian.cs里面对这个Lua脚本进行调用:
  1. using System.Collections;using System.Collections.Generic;using UnityEngine;using LuaInterface;publicclassMain: MonoBehaviour
  2. {private LuaState lua = null;\\创建一个虚拟机
  3.     voidStart(){//调用Lua必定的步骤开始:newLuaResLoader();//加载Lua文件
  4.         lua =newLuaState();//虚拟机初始化
  5.         lua.Start();//开始虚拟机
  6.         LuaBinder.Bind(lua);//绑定虚拟机//调用Lua必定的步骤结束。
  7.         lua.DoFile("Test.lua");//调用运行Test.Lua脚本}}
复制代码
将此脚本挂载在场景里,开始:


已将字符串输出。
以上就是实现Lua&C#交互的基本步骤。
Lua语言挺简洁的,很容易看懂,要是使用的是热更新对游戏进行开发,那岂不是所有的逻辑处理都得到Lua里进行,而C#这边的文件基本要保持不变,虽然过程繁琐,但是熟悉了也就清晰了。Lua的Table很神奇,组合了数据和哈希表,还能当类使用。那么问题来了,怎么用Lua对Unity实现更多操作啊,后续看吧。

四、使用Lua脚本对GameObj进行操作

目标:使用Lua对场景的Sphere进行控制
创建场景,Sphere,Plane


创建Lua脚本Controller.lua:
  1. controller ={}--//创建一个类
  2. local this= controller        --//模拟C#中的this--//对Unity中的类和方法进行加载
  3. local GameObject = UnityEngine.GameObject       
  4. local Input = UnityEngine.Input
  5. local Rigidbody = UnityEngine.Rigidbody
  6. local Color = UnityEngine.Color
  7. local Sphere
  8. local rigi
  9. local force
  10. --//模拟Unity中Start方法
  11. function this.Start()
  12.         Sphere = GameObject.Find("Sphere")
  13.         Sphere :GetComponent("Renderer").material.color =Color(0.1,1,1)
  14.         Sphere :AddComponent(typeof(Rigidbody))
  15.         rigi = Sphere :GetComponent("Rigidbody")
  16.         force =5
  17. end
  18. --//模拟Unity中Update方法
  19. function this.Update()
  20.         local h = Input.GetAxis("Horizontal")
  21.         local v = Input.GetAxis("Vertical")
  22.         rigi :AddForce(Vector3(h,0,v)*force)
  23. end
复制代码
创建C#脚本Controller.cs:
  1. using System.Collections;using System.Collections.Generic;using UnityEngine;using LuaInterface;publicclassController: MonoBehaviour
  2. {private LuaState lua = null;private LuaFunction luaFunc = null;//创建一个存放Lua类里面函数的载体voidStart(){newLuaResLoader();
  3.         lua =newLuaState();
  4.         lua.Start();
  5.         LuaBinder.Bind(lua);
  6.         lua.DoFile("Controller.lua");CallFunc("controller.Start");//调用下面CallFunc函数去调用Controller.lua脚本中的Start函数}privatevoidUpdate(){CallFunc("controller.Update");//调用下面的CallFunc函数}voidCallFunc(string func){
  7.         luaFunc = lua.GetFunction(func);//此处的func因该为lua脚本中的table类的名字,而不是lua脚本文件名的名字
  8.         luaFunc.Call();//开始调用Lua脚本里面的函数}}
复制代码
将Controller.cs挂载在Sphere上,运行Uinty,Sphere成功改变颜色,并且有了Rigidbody,也可对小球进行移动控制。


以上的两个脚本,Lua脚本Controller.lua模拟了unity的Start和Update函数,且这两个函数在C#脚本Controller.cs中被对应的Start和Update调用,实现了lua处理逻辑,c#调用,从而实现了更新时只更改Lua脚本,不更改C#脚本的功能,从而可以实现热更新。

五、将游戏对象打包&解包

1.重新创建一个场景AssertBundleTest,创建一个Sphere,做成预制体:


2.点击预制体:在右下角可以设置生成的AssertBundle的名字和后缀:假设我们要生成sphere.uinity3d:


3.创建打包生成AssertBundle的脚本:在Editor里创建一个脚本CreateAssetBundle.cs
  1. using System.Collections;using System.Collections.Generic;using UnityEditor;using UnityEngine;using UnityEngine.Windows;publicclassCreateAssetBundle{[MenuItem("AssetBundle/Build AssetBundles")]//在菜单栏创建新选项,点击执行下面函数staticvoidBuildAssetBundle(){
  2.         string streamPath = Application.streamingAssetsPath;//保持包的路径if(Directory.Exists(streamPath))//若包路径存在,删除掉
  3.             Directory.Delete(streamPath);
  4.         Directory.CreateDirectory(streamPath);//创建新的包路径
  5.         AssetDatabase.Refresh();//刷新
  6.         BuildPipeline.BuildAssetBundles(streamPath, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows64);//开始打包:参数1.打包路径,参数2.打包格式,参数3.打包平台}}
复制代码
4.编译脚本,在菜单栏中点击AssetBundle–>Build AssetBundles,会生成如下文件:


总共生成4个文件,我们打开文件夹看看:


以上就是打包后的结果。
5.将打包的sphere.unity3d进行解包:创建C#脚本LoadAssetBundle.cs:
  1. using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.Windows;publicclassLoadAssetBundle: MonoBehaviour
  2. {voidStart(){
  3.         string path = Application.streamingAssetsPath +"/sphere.unity3d";//要解包的资源路径
  4.         AssetBundle myAssetBundle = AssetBundle.LoadFromMemory(File.ReadAllBytes(path));//加载解包
  5.         Object[] obj = myAssetBundle.LoadAllAssets<GameObject>();//遍历包里的所有OBJ
  6.         foreach (var o in obj)Instantiate(o);//实例化}}
复制代码
删除掉场景中的sphere,将上面脚本挂载在相机,运行,资源被解包了:


解包的方式有4中:
(1).LoadFormMemory:从内存中加载
(2).LoadFormFile:从本地加载
(3).WWW:从本地或服务器加载
(4).UnityWebRequest:从服务器端加载
根据不同需求可以对选择不同的加载方式。
6.打包中的依赖关系:
我们给5中创建的sphere加一个材质,就用我的证件照吧,创建Textures文件夹,将我的图片放进去:


拖进Sphere,自动生成Materials,我们将Textures中的me和Materials中的me打包在一起:



名字一样就可打包在一起。
然后AssetBundle–>Build AssetBundles,生成以下文件:


多了me.unity3d,打开看下:


包含了me.png和me.mat。
再看看sphere.unity3d:


增加了依赖:me.unity3d。
这就是他们的依赖关系,一旦有依赖,就会在Dependencies中表示出来。
7.对有依赖关系的物体进行解包:
若我对有依赖关系的sphere直接解包,会生成以下模样,会没有材质贴图:


所以我要先对他的依赖进行解包,再对sphere解包:修改一下解包的脚本:LoadAssetBundle.cs:
1.加载总依赖资源包StreamingAssets
2.根据名称找到加载目标的依赖项
3.加载所有依赖项资源
  1. using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.Windows;publicclassLoadAssetBundle: MonoBehaviour
  2. {voidStart(){
  3.         string path = Application.streamingAssetsPath +"/sphere.unity3d";
  4.         AssetBundle myAssetBundle = AssetBundle.LoadFromMemory(File.ReadAllBytes(path));
  5.         Object[] obj = myAssetBundle.LoadAllAssets<GameObject>();LoadDependencies();//解包依赖项
  6.         foreach (var o in obj)Instantiate(o);}voidLoadDependencies(){
  7.         string path = Application.streamingAssetsPath +"/StreamingAssets";//获取总依赖性资源包路径
  8.         AssetBundle assetBundle = AssetBundle.LoadFromFile(path);//加载总依赖项资源包
  9.         AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");//获取资源包依赖信息
  10.         string[] DependenciesName = manifest.GetAllDependencies("sphere.unity3d");//获取shhere.unitye3d的依赖项
  11.         foreach (var name in DependenciesName)
  12.             AssetBundle.LoadFromFile(Application.streamingAssetsPath +"/"+ name);//加载解包依赖项}}
复制代码
实现了有依赖关系的加载



六、将Lua脚本文件进行打包&解包

尝试将一、中的的Lua脚本Controller.lua进行打包:
再设置AssertBundle名字的时候出现这个错误


原因是Unity是无法识别Lua文件,需要更改下Lua文件后缀,将其改为Controller.lua.bytes
重新AssetBundle–>Build AssetBundles:已有Lua的AssetBundle生成:


由于Lua的后缀是bytes,所以解包的时候要进行处理:修改下LoadAssetBundle:
  1. using System.Collections;using System.Collections.Generic;using UnityEngine;using UnityEngine.Windows;publicclassLoadAssetBundle: MonoBehaviour
  2. {voidStart(){
  3.         string path = Application.streamingAssetsPath +"/sphere.unity3d";
  4.         AssetBundle myAssetBundle = AssetBundle.LoadFromFile(path);
  5.         Object[] obj = myAssetBundle.LoadAllAssets<GameObject>();LoadDependencies();
  6.         foreach (var o in obj)Instantiate(o);LoadLua();//加载Lua文件}voidLoadDependencies(){
  7.         string path = Application.streamingAssetsPath +"/StreamingAssets";
  8.         AssetBundle assetBundle = AssetBundle.LoadFromFile(path);
  9.         AssetBundleManifest manifest = assetBundle.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
  10.         string[] DependenciesName = manifest.GetAllDependencies("sphere.unity3d");
  11.         foreach (var name in DependenciesName)
  12.             AssetBundle.LoadFromFile(Application.streamingAssetsPath +"/"+ name);}voidLoadLua(){
  13.         string path = Application.streamingAssetsPath +"/lua.unity3d";
  14.         AssetBundle assetBundle = AssetBundle.LoadFromFile(path);
  15.         TextAsset text = assetBundle.LoadAsset("Controller.lua") as TextAsset;if(File.Exists(Application.dataPath +"/Lua/Controller.lua")){
  16.             File.Delete(Application.dataPath +"/Lua/Controller.lua");}
  17.         File.WriteAllBytes(Application.dataPath +"/Lua/Controller.lua", text.bytes);}}
复制代码
将一、中的Controller.cs挂载到二、中的预制体里,将LoadAssetBundle挂载在相机,点击运行:
运行的时候,LoadAssetBundle.cs将Lua文件Controller.lua从AssetBundle中加载出来,也将挂载了Controller.cd的sphere加载出来,从而可以控制sphere行走,实现了Lua文件的解包。



七、热更新实践

将三、中的Lua脚本文件,预制体,材质和贴图都删掉:


点击开始后:


依然有小球生成,这个小球就是从AssetBundle包生成出来的,若我们将AssetBundle存放在服务器里,就可以进行热更新了。

八、使用服务器进行热更新

参考了:
https://blog.csdn.net/u013617851/article/details/81945906, https://blog.csdn.net/linxinfa/article/details/88246345
在Jarjin的LuaFramword_UGUI框架里,有实现从服务器进行热更新的放下,下载下来实践一下:
把Build好的AssetBundle放进服务器软件:


把本地的StreamingAssers删掉,先运行下看看:


出错,没有资源包,我们把热更新的资源包地址更改下,改为我服务器的地址:在LuaFramework/Scrips/ConstDefine/AppConst.cs下修改:


重新运行游戏:


服务器也被访问了:


我看一下他是怎么去实现从服务器端进行热更新的:
在LuaFrameWork.GameManager里面,每次一开始运行游戏的时候,都会执行


在Init()里,有释放资源动作,其中就有更新资源包的功能,他会去加载StreamingSetting里面的file.txt文档,进行文件检查,看是否本地缺少文件,如果缺少文件,就会自动下载更新:



从开始学习Lua的基本语法,达到能看懂Lua代码的目的,到学习使用tolua框架,已经初步了解了此框架的大概用法。现在学的东西都很基础,等后面熟悉了再去看看这个框架的结构和原理。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

×
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-23 08:03 , Processed in 0.074349 second(s), 23 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表