[Unity3D]Unity3D游戏开发之Lua与游戏的不解之缘终结篇:UniLua热更新全然解读

        

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

喜欢我的博客请记住我的名字:秦元培,我的博客地址是blog.csdn.net/qinyuanpei

转载请注明出处,本文作者:秦元培, 本文出处:http://blog.csdn.net/qinyuanpei/article/details/40213439

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

  

       大家好,我是秦元培。欢迎大家关注我的博客,我的博客地址是blog.csdn.net/qinyuanpei。在之前的三篇系列文章《Unity3D游戏开发之Lua与游戏的不解之缘》中,博主带领大家一起领略了Lua在游戏开发中强大而迷人的作用,通过UniLua这个开源项目我们将Lua引入了Unity3D的世界。并且在此基础上我们写出了Lua'与Unity3D交互的第一个演示样例程序。今天呢。我们来说说Unity3D配合AssetBundle和;Lua实现热更新。

        首先,我们来了解一下什么是热更新吧!所谓热更新是指在不停机的状态下对系统进行更改,比如我们的Windows能够在不重新启动的状态下完毕补丁的更新、Webserver在 不重新启动的前提下完毕对数据和文件的替换等都是热更新的经典实例。那么对于Unity3D而言,什么是热更新呢?假设我们终于公布的Unity3D游戏是一个Web游戏。那么每次游戏载入的过程中实现对资源码的更新就是热更新。

假设我们终于公布的Unity3D游戏是一个client游戏,那么我们在重重新启动client以后实现对资源码的更新就是热更新。为什么这么说呢?由于Web游戏须要保证玩家能够及时高速地进入游戏。因此在游戏载入完之前,我们必须完毕对游戏资源和代码的更新。但是对于client游戏而言,玩家能够在等待本次更新结束后再进入游戏。并且大部分的client程序在更新完后都会要求玩家重新启动client,所以对于client游戏而言。热更新并非是严格意义上的热更新。

那么。我们为什么要进行热更新呢?答案是为了缩短用户获取新版本号client的流程、改进用户体验。这事实上就是博主在前文中提到的传统单机游戏依靠光盘载体进行发售所面临的问题。玩家为了获取最新版本号的游戏,须要下载全新的client并将它安装到计算机或者手机设备上。在互联网产品开发中有一种称为高速迭代的理念,试想假设我们每次对client进行局部的调整,就须要玩家去下载新版本号的client。试问这种用户体验真得能让用户惬意吗?所以如今为了方便用户、留住用户、进而从留住的用户身上赚到钱,我们总能在游戏产品中找到热更新的影子。我们知道在Unity3D中能够通过AssetBundle来实现对游戏中资源的更新,在http://blog.csdn.net/janeky/article/details/17666409这篇文章中作者janeky已经给出了较为完美地解决方式,由于博主使用的Unity3D免费版无法使用AssetBundle的功能。而博主本人不愿意使用破解版,由于这是一个程序猿的良心。所以本文很多其它的是从代码更新的这个角度来讲Unity3D的热更新。对于资源的热更新大家建议大家还是去看janeky的这篇文章吧。好了,以下正式開始Unity3D代码级的热更新之旅!

       在Unity官方的API中官方给出了一种基于反射的思路。即将C#脚本存储为文本文件。然后将其转化为byte字节,再通过反射技术取得该类型及其方法。理论上这样当然没有问题,但是我们知道由于IOS是一个封闭的系统,设计者出于安全的考虑不同意在该平台下使用反射技术。那么问题来了,反射并非一个完美地解决方式。

关于反射技术实现Unity3D的热更新。大家能够參考这篇文章:http://blog.csdn.net/janeky/article/details/25923151

好了。以下我们来说说博主是怎样通过Lua实现Unity3D的热更新的吧。

我们知道在Lua提供的C#接口中有一个DoString()的方法。该方法能够直接利用Lua虚拟机运行字符串中的脚本。

所以,我们能够通过在本地读取Lua脚本来运行脚本中的命令,假设我们脚本中的命令能够直接对Unity3D进行操作,那么我们就能够通过Lua脚本来更新游戏中的代码逻辑。那么,我们怎么能让Lua脚本操作Unity3D呢?在前一篇文章中,我们介绍了一种Require的方法,该方法能够将C#库引入到Lua脚本中并通过Lua来运行C#库中的方法。

顺着这种思路。博主便有了以下的设想:


在这个设想中,我们首先须要将Unity API封装成一个C#类库,在这个类库中我们将会涉及动态载入场景和动态创建场景。由于我们更新游戏的逻辑的时候将会用到这些方法。这些方法通过封装后我们便能够在Lua脚本通过Require方式来引用,进而我们就能够通过Lua脚本来动态地进行设计。我们设计一个固定的位置来存储Lua脚本更新文件。这样我们仅仅须要对照本地版本号和server版本号是否同样就能够知道我们是否须要更新。这里我们通过WWW来从远程server上下载最新的Lua脚本更新文件。下载下来的Lua脚本处于项目外部,我们无法使用Resource.Load()这个方案来载入,但是我们能够通过WWW来载入一个本地文件。这样我们就实现了Lua脚本文件的更新。

当然,我们能够使用AssetBundle来更新Lua脚本文件,但是博主的免费版不支持AssetBundle,所以博主想出了这样一个曲线救国的方法。当Lua脚本文件更新后。我们就能够在游戏主逻辑里通过DoString()方法来运行脚本文件里的代码。在游戏主逻辑里基本的任务是比較当前版本号号和server版本号号来推断是否须要更新,假设须要更新就下载Lua脚本更新文件然后运行脚本中的代码。这样我们就实现了client程序的更新。好了,以下我们继续曾经一篇文章中的项目为例来将博主的这个设想变成现实。

        首先,我们在CSharpLib.cs这个类中添加以下两个方法并完毕方法的注冊:

	/// <summary>
	/// 设置场景中物体的坐标
	/// </summary>
	/// <returns>返回当前坐标</returns>
	/// <param name="lua">Lua.</param>
	public static int SetPosition(ILuaState lua)
	{
		//物体的名称
		string mName=lua.L_CheckString(1);
		//传入參数x,y,z
		float mX=(float)lua.L_CheckNumber(2);
		float mY=(float)lua.L_CheckNumber(3);
		float mZ=(float)lua.L_CheckNumber(4);
		//获取物体
		GameObject go=GameObject.Find(mName);
		//获取Transform
		Transform mTrans=go.transform;
		//设置游戏体的位置
		mTrans.position=new Vector3(mX,mY,mZ);
		//返回游戏体当前坐标
		lua.PushNumber(mTrans.position.x);
		lua.PushNumber(mTrans.position.y);
		lua.PushNumber(mTrans.position.z);
		return 3;
	}

	/// <summary>
	/// 使用本地预设创建一个物体
	/// </summary>
	/// <returns>The resource.</returns>
	/// <param name="lua">Lua.</param>
	public static int CreateResource(ILuaState lua)
	{
		//传入资源名称
		string mName=lua.L_CheckString(1);
		//载入本地资源
		GameObject go=(GameObject)Resources.Load(mName);
		//传入坐标參数x,y,z
		float mX=(float)lua.L_CheckNumber(2);
		float mY=(float)lua.L_CheckNumber(3);
		float mZ=(float)lua.L_CheckNumber(4);
		//创建一个新物体
		Object.Instantiate(go,new Vector3(mX,mY,mZ),Quaternion.identity);
	    //返回该物体的名称
		lua.PushString(go.name);
		return 1;
	}
好了,这样我们就完毕了一个简单的C#类库,以下我们来在主逻辑代码中添加一个更新脚本的方法UpdateScripts():

void UpdateScript()
	{
		StartCoroutine("Download");
	}

	/// <summary>
	/// 下载Lua脚本更新文件
	/// </summary>
	IEnumerator Download()
	{
		//从本地载入Lua脚本更新文件,假设文件已经从server下载下来
		WWW _WWW=new WWW(mUpdateFilesPath);
		yield return _WWW;
		//读取server版本号
		mLua.L_DoString(_WWW.text);
	}

这里的代码逻辑非常easy。就是读取脚本更新本地文件然后运行脚本。当中mUpdateFilePath是脚本更新文件路径:

		//初始化路径
		mUpdateFilesPath="file://D:\\lua_update.txt";

这里博主设想的是在本地存储一个版本号号,每次更新前先获取server端的版本号号,假设两个版本号号不同则须要从server上下载更新脚本文件进行更新。只是博主这里没有想到什么好方法来获取版本号号。所以这里就仅仅写了更新。那么,我们来看看更新脚本文件都做了哪些事情吧!

local csharplib=require"CSharpLib.cs"

csharplib.SetPosition("Cube",2,1,0)
csharplib.CreateResource("Sphere",0,0,0)
csharplib.CreateResource("Cube",1,1,0)
首先我们通过Require引入了CSharpLib.cs 这个类库。接下来,我们将场景中名称为Cube的物体的位置设为(2,1,0)、 利用本地的两个Prefab资源创建了一个Cube和一个Sphere。

那么,我们的设想能不能实现呢?我们一起来看终于效果吧!

运行Lua脚本更新前:


运行Lua脚本更新后:



        如我们所愿。Lua脚本成功地对场景实现了一次更新。可能有的朋友会问,这里用的是本地资源,假设我想用server上的资源怎么办呢?答案是博主最不愿意提及的AssetBundle,即利用AssetBundle载入远程资源,然后用Lua实现更新。这些逻辑能够加入到CSharpLib这个类库中。

大家能够设想一下,假设有一天我们能够将Unity的全部方法都封装起来,那么我们就能够直接用Lua来创建场景了。假设要更新client,仅仅要更换Lua文件就能够了。怎么样是不是非常easy呢?但是Unity不开源啊,这些想法终究仅仅是想法啦。

好了,今天的内容就是这样了,欢迎大家关注我的博客,谢谢大家!


每日箴言:成熟,不是你绷起脸。显得多么老道。不是你知道多少大是大非。懂得多少大道理,而是你能理解身边发生的小事都可能有它的不得已。





-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

喜欢我的博客请记住我的名字:秦元培,我的博客地址是blog.csdn.net/qinyuanpei

转载请注明出处。本文作者:秦元培, 本文出处:http://blog.csdn.net/qinyuanpei/article/details/40213439

-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------




posted @ 2017-08-04 13:30  yfceshi  阅读(354)  评论(0编辑  收藏  举报