🎬 博客主页:https://xiaoy.blog.csdn.net
热更新
方面系列知识,就从这一篇开始吧!热更新实战案例学习使用
开始学习吧!本篇文章会正式使用xLua完成一个简单的热更新实战案例,关于xLua的基本配置及导入的详细信息可以参考下面这篇文章:
Unity 热更新技术 |(六)xLua框架学习最新系列完整教程
在File -> Bulid Setting -> Player Settings -> Player -> Other Setting -> Scripting Define Symbols
下添加 HOTFIX_ENABLE
添加完之后点击Apply,菜单栏中会出现一个新的选项Hotfix Injece In Editor。
若是未出现该选项,则重启该Unity 项目即可。
若是会看到报错,原因是因为有重复文件导致的,删除下图里的xlua,Xlua.Mini3个文件即可,Plugins/x86_64路径下的xlua也删掉即可
检查Unity 中XLua热更新环境
using XLua
。[Hotfix]
。[LuaCallCSharp]
。LuaEnv
Generate Code
。Hotfix Inject In Editor
,进行Lua注入。using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using XLua;
using System.IO;
[Hotfix]
public class HotfixTest : MonoBehaviour
{
public LuaEnv luaEnv;
// Start is called before the first frame update
void Start()
{
luaEnv = new LuaEnv();
luaEnv.DoString("CS.UnityEngine.Debug.Log('hello world')");
Debug.Log("Unity_Hello World!");
luaEnv.Dispose();
}
}
运行结果如下:
关于AB包更多使用详情信息可查看文章:Unity 热更新技术 | (二) AssetBundle - 完整系列教程学习
记得要将AssetBundles-Browser解压后的Editor文件夹Copy到Unity的项目中。目录结构如下图所示。
此处直接介绍具体步骤,不再对AssetsBundle做过多赘述,有不明白的可以参考上述文章。
本次实战场景为玩家在场景中可以四处移动和旋转,按住鼠标左键可以向前方发射子弹。
先搭建一个简易场景并编写相关功能代码,场景及脚本如下:
public class PlayerController : MonoBehaviour
{
public float moveSpeed = 10f;
public float turnSpeed = 100f;
public float fireTime = 0.5f;
private float ver;
private float hor;
private float timer;
void Update()
{
InputControl();
}
private void InputControl()
{
ver = Input.GetAxis("Vertical");
hor = Input.GetAxis("Horizontal");
transform.position += transform.forward * ver * Time.deltaTime * moveSpeed;
transform.Rotate(transform.up * hor * Time.deltaTime * turnSpeed);
if (Input.GetMouseButton(0))
{
if (timer <= 0)
{
//发射子弹
//
timer = fireTime;
}
else
{
timer -= Time.deltaTime;
}
}
}
}
添加一个简易的UI界面UIDemo,用于游戏开始和结束,也拖成预制体。
首先将玩家和子弹对象 拖成预制体,并设置AB包的名称。
我这里给玩家和子弹加了两个材质,也一并打到AB包中。
包括UI预制体以及新添加了两张图片素材也一并打成AB包,如下图所示:
然后点击Window -> AssetBundles Browser 进行打包。
更多AB内容可以查看文章:Unity 热更新技术 | (二) AssetBundle - 完整系列教程
这里就直接进行Build了。
Build之后会出现几个AB包文件,后续就是加载这几个文件进行AB包加载并获取其中的对象。
创建加载AB包的脚本方法 ABLoadManager.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class ABLoadManager : MonoBehaviour
{
private AssetBundle ab;
private static ABLoadManager instance;
public static ABLoadManager GetInstance()
{
if (instance != null)
{
return instance;
}
else
{
return new ABLoadManager();
}
}
private void Awake()
{
instance = this;
}
/// <summary>
/// 根据AB包名字加载AB包
/// </summary>
public void LoadAB(string packageName)
{
//加载指定AB包
ab = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/"+ packageName);
//加载主包
AssetBundle abMain = AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/" + "StandaloneWindows");
//加载主包中的固定文件
AssetBundleManifest abManifest = abMain.LoadAsset<AssetBundleManifest>("AssetBundleManifest");
//从固定文件中得到依赖信息
string[] strs = abManifest.GetAllDependencies(packageName);
//得到依赖包的名字并加载
foreach (var s in strs)
{
AssetBundle.LoadFromFile(Application.streamingAssetsPath + "/" + s);
}
}
/// <summary>
/// 根据预制体名称获取对象
/// </summary>
/// <param name="objName"></param>
/// <returns></returns>
public GameObject GetABGameObject(string objName)
{
GameObject abGO = ab.LoadAsset(objName, typeof(GameObject)) as GameObject;
return abGO;
}
然后在GameManager.cs 中先对UI预制体进行加载:
public class GameManager : MonoBehaviour
{
private Transform canvas;
private void Awake()
{
canvas = GameObject.Find("Canvas").transform;
}
void Start()
{
ABLoadManager.GetInstance().LoadAB("ui");
Instantiate(ABLoadManager.GetInstance().GetABGameObject("UIDemo"), canvas);
}
}
之后在GameController.cs中添加UI相关的方法,代码如下所示:
public class GameController : MonoBehaviour
{
public Button btn_Start;
public Button btn_Quit;
public Image image_BG;
private bool isPlay;
//public LuaEnv luaenv;
private void Awake()
{
isPlay = false;
btn_Start.onClick.AddListener(StartGame);
btn_Quit.onClick.AddListener(QuitGame);
}
private void StartGame()
{
if (isPlay) return;
isPlay = true;
//加载AB包,获取图片素材
ABLoadManager.GetInstance().LoadAB("texture");
image_BG.sprite = ABLoadManager.GetInstance().GetABSprite("common_btn");
btn_Start.image.sprite = ABLoadManager.GetInstance().GetABSprite("common_btn");
btn_Quit.image.sprite = ABLoadManager.GetInstance().GetABSprite("common_btn");
//加载AB包,实例化玩家
ABLoadManager.GetInstance().LoadAB("module");
GameObject player = Instantiate(ABLoadManager.GetInstance().GetABGameObject("Player"), new Vector3(0, 0, 1), Quaternion.identity);
}
private void QuitGame()
{
#if UNITY_EDITOR //如果是在编辑器环境下
UnityEditor.EditorApplication.isPlaying = false;
#else//在打包出来的环境下
Application.Quit();
#endif
}
}
其中GameManager和ABLoadManager挂载到场景中,GameController挂载到UIDemo上面。
此时AB包的内容就算是暂时做完了,运行看一下效果:
打包成exe文件查看效果:
这样在开始运行之后,会从uiAB包中加载UIDemo并实例化,然后点击开始之后会从texture AB包中加载素材并赋值给Image组件,然后从module AB包中加载并实例化玩家。
当玩家按下鼠标左键时,从AB包中加载子弹并实例化发射子弹。
这样一个简单的Demo就制作好了,下面开始正式进行资源热更新和代码热更新的操作。
先来讲一下资源热更新,一般是用于项目中的资源替换,比如UI素材替换,预制体替换(材质/网格等)。
资源热更新使用Unity的AssetsBundle就可以做到,下面就用一个简单的案例来演示一下资源的热更新。
目的:利用AB包对项目中的UI素材和子弹的材质进行替换。
在上面我们已经构建了一个简单的场景并且可以运行了,下面在Unity中把子弹的颜色由原来的红色材质替换为绿色材质。
只需要在子弹预制体中将绿色的材质球拖到子弹上即可
然后再找一个新的素材拖到项目中,将名字改为common_btn,并将原来的那张图片删除或者改个别的名字,记得将新的素材也要设置AB包名称。
然后重新打开Window -> AssetBundle Browser 并进行Build。
此时打开我们刚才已经打包成exe的文件夹,将刚才Build的AB包进行替换。
替换前后对比如下
替换前:
替换后:
此时我们可以看到,在打包后的文件夹中只需要对AB包进行替换就可以完成一次资源的热更新,而无需重新从Unity重新打包即可生效。
在正常的游戏项目中如果遇到某些活动,游戏中的UI界面就会发生变化,此时就用到了热更新中的资源热更新。
只需要在玩家打开游戏时检查游戏的版本号,若需要进行更新,则从服务器中下载对应的AB包,然后将原来的包进行替换就可以实现该效果了。
资源热更新也可以通过代码热更新来实现,只需要将对应资源的调用代码进行更新替换同样也可以做到资源的热更新。
下面就来看看代码热更新怎样做到吧,这块属于本文的重点内容。
若是整个游戏项目都使用Lua脚本完成,那在打包后的游戏中直接对Lua脚本替换就可以实现代码的一次热更新。
因为Lua语言的特性如此,所以不向C#一样需要重新编译后才可以实现功能。
若项目原来使用的C#实现,现在需要使用到热更新,那就需要下面的方法来进行了,也就是利用了xLua的一个热补丁特性。
xLua所有的配置都支持三种方式:打标签;静态列表;动态列表。
配置有两必须两建议:
更多内容可以来XLua文章查看:Unity 热更新技术 |(六)xLua框架学习最新系列完整教程
下面的测试用例使用打标签(Hotfix)的方式练习,就是在需要进行热更的类上面添加 [Hotfix]
特性。
using System.IO;
using UnityEngine;
using XLua;
public class LuaManager : MonoBehaviour
{
public static LuaManager _instance;
public static LuaManager Instance
{
get
{
return _instance;
}
}
[CSharpCallLua]
public delegate void LuaDelegate(string paras);
/// <summary>
/// 定义一个Delegate,Lua结果将传参回调给该Delegate
/// </summary>
public static LuaDelegate LuaFunc;
/// <summary>
/// 定义一个Lua虚拟机,建议全局唯一
/// </summary>
public static LuaEnv luaEnv;
public LuaEnv GetLuaEnv()
{
return luaEnv;
}
void Awake()
{
_instance = this;
LuaEnvInit();
}
public void LuaEnvInit()
{
luaEnv = new LuaEnv();
luaEnv.AddLoader(MyLoader);
///lua脚本的主入口
luaEnv.DoString("require 'updateInfo'");
//获取Lua中全局function,然后映射到delegate
luaEnv.Global.Get("LuaFunc", out LuaFunc);
}
private byte[] MyLoader(ref string filepath)
{
string abspath = Application.dataPath + "/Resources/lua/" + filepath + ".lua.txt";
return System.Text.Encoding.UTF8.GetBytes(File.ReadAllText(abspath));
}
}
--将GameManager脚本中的Test方法替换为下列ChangeCode方法
xlua.hotfix(CS.GameManager,'Test',function(self)
CS.UnityEngine.Debug.Log("Hello Lua!")
end)
然后在GameManager.cs中添加一个Test方法,如下所示:
此时点击运行Generate Code在执行Hotfix Inject In Editor,开始运行,效果如下:
这样lua脚本更新GameManager脚本中的Test方法输出"Hello Lua!"
GameManager中的Test()中的内容已经由hello.lua替换成功啦!
这也算是一个最最入门的热更新完成啦~
using UnityEngine;
using XLua;
[Hotfix]
public class PlayerController : MonoBehaviour
{
public float moveSpeed = 5f;
public float turnSpeed = 500f;
public float fireTime = 0.5f;
private float ver;
private float hor;
private float timer;
private void Start()
{
Test();
}
void Update()
{
InputControl();
}
private void InputControl()
{
ver = Input.GetAxis("Vertical");
hor = Input.GetAxis("Horizontal");
transform.position += transform.forward * ver * Time.deltaTime * moveSpeed;
transform.Rotate(transform.up * hor * Time.deltaTime * turnSpeed);
if (Input.GetMouseButton(0))
{
if (timer <= 0)
{
//从AB包中 加载 并实例化子弹
GameObject go = Instantiate(ABLoadManager.GetInstance().GetABGameObject("Bullet"), transform.GetChild(0).position, Quaternion.identity);
go.GetComponent<Rigidbody>().AddForce(transform.forward * 10,ForceMode.Impulse);
Destroy(go, 10f);
timer = fireTime;
}
else
{
timer -= Time.deltaTime;
}
}
Jump();
}
public void Test()
{
Debug.Log("1测试热补丁。");
}
public void Jump()
{
Debug.Log("2测试热补丁。");
}
}
xlua.util提供了auto_id_map函数,执行一次后你就可以像以前那样直接用类,方法名去指明修补的函数。
可以在xLua源码中找到util.lua.txt 并copy到Assets\Resources\lua中。
创建updateInfo.lua.txt编写脚本引入util,require 'util’即可使用。
在lua文件夹中创建名为updateInfo.lua.txt,代码如下:
require "util"
--将PlayerController脚本中的Test()替换为下列ChangeCode方法
xlua.hotfix(CS.PlayerController,'Test',function(self)
ChangeCode(self)
print("Fixed PlayerController!")
end)
function ChangeCode(self)
-- 改变玩家的射速和旋转速度
self.fireTime = 0.05
self.turnSpeed = 1000
end
--将PlayerController脚本中的Jump()替换为下列ChangeCode方法
xlua.hotfix(CS.PlayerController,'Jump',function(self)
JumpCode(self)
end)
function JumpCode(self)
-- 给玩家增加一个按下鼠标右键跳跃的功能
local inputSpace = CS.UnityEngine.Input.GetMouseButtonDown(1)
if inputSpace then
self:GetComponent("Rigidbody").velocity = self.transform.up * 10;
end
end
此时运行游戏,就可以发现玩家的射速、旋转速度已经发生变化,并且还多了一个按下鼠标右键跳跃的功能!
更新前:
更新后:
若是出现报错(要排除没有手动Generate Code -> Hotfix Inject In Editor),可查看相关文章
https://www.freesion.com/article/9584121931/
https://blog.csdn.net/qq_43420347/article/details/106441441
https://github.com/tencent/xlua/issues/383