通过Xlua实现unity热更新的一个小例子
一.介绍
热更新是指在更新游戏资源或者逻辑的时候不需要开发者将游戏再打包、发布、玩家重新下载安装包,而是可以通过将需要更新的资源打包成AssetBundle文件发布到服务器,然后游戏程序通过下载服务器的AssetBundle替换本地文件来达到游戏更新的流程。
打包出来的unity制作的游戏,如果在代码(代码用的是c#语言)上有需要更改的地方,必须重新打包,因为c#语言需要重新编译为dll文件,而如果是lua语言编写,则不需要重新编译。
如果想让lua和c#之间互相调用,可以借用一些开源的Lua插件,比如xLua,toLua。在例子中使用的是xLua插件
二.准备工作
1.xLua
xLua下载方式:https://github.com/Tencent/xLua/releases
下载xlua_v2.1.14.zip
![72916a84f39ca86bd4701339df216db5.png](https://i-blog.csdnimg.cn/blog_migrate/a1089c8866ebc650307b86963a753299.png)
![867ab0cdb0d8bf17af0c5d41f129a565.png](https://i-blog.csdnimg.cn/blog_migrate/337e75833bb98094b20bf0a0e22940ee.png)
将Tools文件夹复制到工程项目,与Assets同级
![3489cb9015becbe56fbe769628dde432.png](https://i-blog.csdnimg.cn/blog_migrate/98eb5d4b211e06d5d44df61c8c0ef86f.jpeg)
将Assets文件夹中的文件复制到工程项目的Assets文件夹里
2.将自己的电脑变成一个本地的服务器
打开控制面板-选择管理工具-IIS-在网站处右键添加网站-填写相关项-启动新建的网站
![0f65b5f60230e009f1d8c3fb04179c32.png](https://i-blog.csdnimg.cn/blog_migrate/d27b6eb85df22b7a52bd14961512828a.jpeg)
![3fec5ef987f74d771e68c3fcd7e38745.png](https://i-blog.csdnimg.cn/blog_migrate/dfd313916559e7f12dab5e7d11b47515.jpeg)
![9496237e7bcabb52d2c5cf1ff61b9842.png](https://i-blog.csdnimg.cn/blog_migrate/5a9db058c533df189e6528eb2da9afa2.jpeg)
其中应用程序池要选择DefaultAppPool,物理路径为你希望服务器所在的路径,端口可以填一个二位数字
![1266ae440bcc0ac33a2606ac91fa65ec.png](https://i-blog.csdnimg.cn/blog_migrate/e58ccff4b55fbdb76b01ede244021442.jpeg)
设置完成后,在浏览器中搜索localhost:端口即可查看服务器存放的文件
![b43c40ce8e7177c0ffbf1e55b700381f.png](https://i-blog.csdnimg.cn/blog_migrate/217bce7c57990f112ea725ee57f24c00.png)
3.安装执行Lua的IDE
下载地址:https://www.runoob.com/lua/lua-environment.html
4.在unity中加入HOTFIX_ENABLE标签
选择Edit->Project Setting->Player 右边Inspector列表中
![525a44d7e42202bedb2fd6ad32419c19.png](https://i-blog.csdnimg.cn/blog_migrate/b2cd2dd72a7a03b312e358fa874c018a.jpeg)
三.打包AssetBundle
1.将需要打包成AB文件的资源添加AssetBundle标签
![37ef9b4eddef0c71d590e16c45e5a296.png](https://i-blog.csdnimg.cn/blog_migrate/5942f2e016f8f9e4266f895bf80bd230.png)
其中,要打在同一个AB包的资源,标签要求一致
2.编写打包代码
using UnityEditor;
using System.IO;
public class CreateAssetBundles
{
[MenuItem("Assets/BuildAssetBundles")]
static void BuildAllAssetBundles()
{
//要写两个'/',一个是转义符,
string dir = "F://Web Server//AssetBundles";
if (Directory.Exists(dir) == false)
{
Directory.CreateDirectory(dir);
}
//参数 打包路径,压缩方式,打包平台
BuildPipeline.BuildAssetBundles(dir, BuildAssetBundleOptions.None, BuildTarget.StandaloneWindows64);
}
}
这里我服务器的路径为F:Web Server
上述脚本可在Editor模式下运行,不需要挂在GameObject上
![9f4706b8b6ff6cf5c7a17a56e7897958.png](https://i-blog.csdnimg.cn/blog_migrate/f1d96dcaf89f7a2c64b0bb4fd8f0dfb6.png)
点击BuildAssetBundles即可将需要打包成AB文件的资源上传到服务器
![d4e943210abd4bc08f8e0533aa76340e.png](https://i-blog.csdnimg.cn/blog_migrate/dee05c5e2d14aa6c495eafafe28d7033.png)
![4bbfb7d35409a6cd610e3b8664258ff8.png](https://i-blog.csdnimg.cn/blog_migrate/9e305aec181ffbec6db42f2f77ed5cf2.png)
四.热更新
给步骤三中的cube添加一个用来测试热更新的脚本,这个脚本会在游戏时让cube的scale扩大到原来的2倍,rotation的x,y,z都增加30。类上方添加特性Hotfix使其可热更新。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
[Hotfix]
public class ChangeSelf : MonoBehaviour
{
public Vector3 currentScale;
public Quaternion currentRotation;
public Vector3 laterScale;
public Quaternion laterRotation;
// Start is called before the first frame update
void Start()
{
currentScale = this.transform.localScale;
currentRotation = this.transform.localRotation;
ChangeScale(currentScale);
ChangeRotation(currentRotation);
this.transform.localScale = laterScale;
this.transform.localRotation = laterRotation;
}
public void ChangeScale(Vector3 scale)
{
laterScale = scale * 2;
//this.transform.localScale = laterScale;
}
public void ChangeRotation(Quaternion rotation)
{
laterRotation = Quaternion.Euler(rotation.eulerAngles.x+30, rotation.eulerAngles.y+30, rotation.eulerAngles.z+30);
//this.transform.localRotation = laterRotation;
}
}
在服务器中添加热更新的lua脚本,路径:
![bdb494568abd86aca3aaecd69f204941.png](https://i-blog.csdnimg.cn/blog_migrate/f05bd4104e5d9a876c3b33f6bb595255.png)
代码:
--hotfix中的参数分别为要进行热更新的类,要更新的方法,方法所在的类(function中的第一个参数指方法所在的类,其他参数为要更新的方法的参数)
xlua.hotfix(CS.ChangeSelf,'ChangeScale',function(self,scale)
self.laterScale = scale * 0.5
end
)
local Quaternion = CS.UnityEngine.Quaternion
local rot = CS.UnityEngine.Quaternion() --新建一个对象
xlua.hotfix(CS.ChangeSelf,'ChangeRotation',function(self,rotation)
self.laterRotation = Quaternion.Euler(rotation.eulerAngles.x+60,rotation.eulerAngles.y+60,rotation.eulerAngles.z+60)
end
)
注意:在SciTE中编写完lua脚本,保存的后缀是lua,如果要被unity识别,需要增加txt的后缀
上述的热更新旨在让cube的scale缩小到原来的一半,rotation的x,y,z都增加60
五.加载
1.加载AssetBundle
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Networking;
public class LoadAssetBundles : MonoBehaviour
{
// Start is called before the first frame update
IEnumerator Start()
{
//这里的地址是服务器端的地址 localhost:后的数字是端口
string url = @"http://localhost:81/AssetBundles/luatestcube.unity3d";
while (Caching.ready == false)
{
yield return null;
}
using (var www = WWW.LoadFromCacheOrDownload(@"http://localhost:81/AssetBundles/luatestcube.unity3d", 2))
{
yield return www;
if (!string.IsNullOrEmpty(www.error))
{
Debug.Log(www.error);
yield return null;
}
var assetBundle = www.assetBundle;
//var asset = assetBundle.mainAsset;
GameObject cubePrefab = assetBundle.LoadAsset<GameObject>("luatestcube");
Instantiate(cubePrefab);
}
}
}
该脚本可挂在空物体上
![03ab387b1ff71d693c72548663560ab5.png](https://i-blog.csdnimg.cn/blog_migrate/eb1054fe5bc4f2ae9912c50991bf42d0.png)
2.加载lua脚本
using System.Collections;
using System.Collections.Generic;
using System.Text;
using UnityEngine;
using XLua;
public class HotFixTests : MonoBehaviour
{
private LuaEnv m_kLuaEnv;
// Start is called before the first frame update
void Start()
{
//xlua虚拟机
m_kLuaEnv = new LuaEnv();
//查找指定路径下lua热更新文件
string path = Application.persistentDataPath + "/ChangeSelf.lua.txt";
//用协程下载读取文件内容
StartCoroutine(DownloadFile(path));
}
public IEnumerator DownloadFile(string path)
{
WWW www = new WWW(path);
yield return www;
if (www.isDone)
{
System.IO.StreamReader sr = new System.IO.StreamReader(path, Encoding.UTF8);
if (sr != null)
{
//执行lua中的语句
m_kLuaEnv.DoString(sr.ReadToEnd());
}
}
}
}
using System.Collections;
using System.Collections.Generic;
using System.IO;
using UnityEngine;
public class WWWTexts : MonoBehaviour
{
private string urlPath = @"http://localhost:81/Xlua/ChangeSelf.lua.txt";//资源网络路径
private string file_SaveUrl;//资源保存路径
private FileInfo file;
// Start is called before the first frame update
void Start()
{
file_SaveUrl = Application.persistentDataPath + "/ChangeSelf.lua.txt";
file = new FileInfo(file_SaveUrl);
StartCoroutine(DownFile(urlPath));
}
IEnumerator DownFile(string url)
{
WWW www = new WWW(url);
yield return www;
if (www.isDone)
{
Debug.Log("下载完成");
byte[] bytes = www.bytes;
CreatFile(bytes);
}
}
/// <summary>
/// 创建文件
/// </summary>
/// <param name="bytes"></param>
void CreatFile(byte[] bytes)
{
//将服务器的文件按字节写入保存路径所在文件
Stream stream;
stream = file.Create();
stream.Write(bytes, 0, bytes.Length);
stream.Close();
stream.Dispose();
}
}
这两个脚本挂在同一个空物体上
![27c9eb74d460dd03858541a3bbab40bd.png](https://i-blog.csdnimg.cn/blog_migrate/6dc6712b20f53c60f846070bdd10ebea.png)
3.运行
每次修改完c#脚本或者lua脚本后,都要先点击第一个,再点击第三个,然后才能运行。
![699cb0ec28dcc7b503f0b1de27420f29.png](https://i-blog.csdnimg.cn/blog_migrate/3f85e634036ca6754727e57af715390d.png)
如果成功执行会输出一下两条日志
![54c0c3dc7bdb72f4a1f6aa6474a2de67.png](https://i-blog.csdnimg.cn/blog_migrate/e5280036871f8a2b315f5131e9e67a8f.png)
然后就能运行unity了,运行后发现cube的scale和rotation都实现了lua脚本中的更改,而不是c#脚本中的更改。