大家好,我是秦元培。欢迎大家关注我的博客,我的博客地址是:blog.csdn.net/qinyuanpei。终于在各种无语的论文作业中解脱了,所以立即抓紧时间来这里更新博客。博主本来计划在Unity3D游戏开发之从《魂斗罗》游戏说起(上)——目标追踪这篇文章后再写一篇《Unity3D游戏开发之从《魂斗罗》游戏说起(下)》,只是眼下博主的项目进度有些缓慢,所以想等项目稳定下来以后再和大家分享。
作为大家等待博主更新博客的回报,我们今天来说一说Unity3D中的游戏场景异步载入。那么什么是游戏场景异步载入呢?异步载入就是系统在载入数据(如地图数据、模型数据、玩家数据等)时能够运行其他的任务。事实上就是我们熟悉的多线程啦。相反地,在单线程中。我们通常须要等待数据载入完毕以后才干继续运行任务。我们能够从《仙剑奇侠传》、《古剑奇谭》等游戏中找到这种样例。比方《仙剑奇侠传四》中的五毒兽勇气读条效果:
再比方《古剑奇谭》中融入了游戏特色介绍的读条效果:
从上面的这些样例我们能够看出,在游戏设计中通过合理的游戏场景载入方式,降低玩家等待的时间。对于一个游戏来说是设计的时候须要好好考虑的一个问题。在Web设计中一个好的Web设计通常要考虑要将用户等待页面响应的时间降到最低,这样才不会导致用户流量的缺失。在一个注重用户体验的时代。不管设计什么样的产品,带给用户最为极致的体验才是最重要的。我们注意到这两款游戏都採用了异步载入的方式。由于在载入游戏的过程中,系统在运行界面GUI元素的绘制工作。那么,好了,在了解了异步载入的概念和异步载入的原理以后,我们就来实现今天的内容,如图是我们今天要来实现的效果图,我们希望实如今游戏载入的过程中,能够在进度条下方交替显示不同的提示内容:
那么详细怎么实现呢?在Unity3D中提供了一个LoadLevelAsync()方法。该方法能够用于异步载入场景资源。
该方法返回一个 AsyncOperation类型的返回值,该返回值有两个重要的属性:1、 isDone:能够查询是否载入完毕;2、progress:一个介于0-1之间的值,能够表示载入的进度
好了。通过这两个属性和这种方法。我们就能够实现今天的内容啦,一起開始吧!
首先创建一个场景,我们这里使用系统默认的GUI系统,如图:
这个界面就是我们今天的载入界面,在界面载入的过程中,我们能够在背景下方显示游戏中的Tips和游戏进度。通常情况下。我们须要三个界面来完毕一个完整的界面载入效果。
第一个界面称为触发界面,主要负责响应用户的触发操作。第二个就是我们今天这里在讲的载入界面,主要负责读条及UI刷新。第三个界面就是我们终于要呈现给玩家的界面。
这比方在《仙剑奇侠传》游戏中。当玩家靠近房屋时会在门口显示一个箭头的效果。这样能够提示玩家即将进入场景载入的触发区域。当玩家进入这个范围时,就会触发载入界面的显示。这样我们就能看到五毒兽勇气的读条效果了。当读条结束后,会进入第三个界面,这是我们终于要显示给玩家的界面。。好了,这就是异步载入游戏场景的一个完整的过程了。
我们以下来编写脚本实现今天的功能,对于载入过程这个界面,我们主要用到我们的LoadLevelAsync()方法:
using UnityEngine;
using System.Collections; public class Loading : MonoBehaviour { //异步对象
private AsyncOperation mAsyn;
//绑定Tip的GUIText
private GUIText mTip;
//Tip集合,实际开发中须要从外部文件里读取
private string[] mTips=new string[]
{
"异步载入过程中你能够浏览游戏攻略",
"异步载入过程中你能够查看当前进度",
"异步载入过程中你能够推断是否载入完毕",
"博主不理解轩6的读条为什么那样慢",
"难道是由于DOOM不懂Unity3D",
}; //更新Tip的时间
private const float UpdateTime=2.0F;
//上一次更细的时间
private float lastTime=0.0F; void Start ()
{
mTip=GameObject.Find("GUITips").GetComponent<GUIText>();
StartCoroutine("Load");
} IEnumerator Load()
{
mAsyn=Application.LoadLevelAsync("Main");
yield return mAsyn;
} void Update ()
{
//假设场景没有载入完则显示Tip
if(mAsyn!= null && !mAsyn.isDone)
{
//假设达到了更新的时间
if(Time.time-lastTime>=UpdateTime)
{
lastTime=Time.time;
mTip.guiText.text=mTips[Random.Range(0,5)]+"("+(float)mAsyn.progress * 100+"%"+")";
}
}else
{
Application.LoadLevel("Main");
}
}
}
这里的代码非常好理解。假设载入完毕了就载入终于界面,否则就随机显示Tips和载入的进度。
这里博主为了节省时间,使用的是前一篇文章中的场景。如图:
而对于触发载入事件的界面,我们仅仅须要使用普通的载入方式就能够了:
using UnityEngine;
using System.Collections; public class TriggerToLoad : MonoBehaviour { void OnGUI()
{
if(GUILayout.Button("载入Loading场景",GUILayout.Height(30)))
{
Application.LoadLevel("Scene2");
}
}
}
显然,这是一个当用户点击button时载入场景的方法,所以不再做太多的解释。由于这种方法仅仅支持Pro版本号的Unity3D,因此博主的免费版Unity3D注定是无法运行了,所以这里无法给大家做程序演示,希望大家谅解啊。在Unity3D异步载入场景的问题中,有两个常见的问题:
1、使用 AsyncOperation的progress获取载入进度,载入进度不变
2、无法捕捉到场景载入完毕的事件:使用DontDestroyOnLoad能够解决
好了,今天的内容就是这样了,博主的能力有限,能够理解到的仅仅有这么多啦。不管怎么样。还是希望能和大家一起成长啊,呵呵。
參考文章:
每日箴言:勇者不是没有眼泪的人。而是含着眼泪微笑奔跑的人。
喜欢我的博客请记住我的名字:秦元培,我博客地址是blog.csdn.net/qinyuanpei。
转载请注明出处。本文作者:秦元培,本文出处:http://blog.csdn.net/qinyuanpei/article/details/30836567
2015年11月30日更新:
有朋友问起这篇文章中的效果无法实现,这是由于这篇文章写作时间较长。并且当时博主所使用的Unity3D版本号是免费版本号,所以异步载入这块的内容并没有经过深度地測试。
既然大家问起这个问题,就在这里补充着说一下吧,这个问题在网上已经有了比較好的解决方式。
首先。我们要明白的一点是异步载入和同步载入不同。异步载入是非堵塞的,所以在这个过程中我们能够使用一个进度条或者载入界面来过渡场景的载入过程。其次,Unity3D的异步载入有一个Bug是当进度载入到0.9的时候就停止了载入。并且会在这里卡顿一下。因此我们不能以1.0作为载入完毕的一个推断标志。Unity3D中的AsyncOperation是我们在处理场景异步载入时的一个类。这个类中封装了场景载入相关的信息。如场景是否载入完毕的isDone和当前场景载入的进度progress。这里。我们想重点说说allowSceneActivation这个属性。这个属性能够决定当场景载入到0.9的时候是否由Unity3D自己主动完毕剩余部分内容的载入,默认这个属性为true,所以当我们载入到0.9的时候,场景会略微地卡顿一下。那么,好了,我们怎么解决问题呢?这里分成两部分来处理,即前面的0.9由Unity3D来进行载入。而剩下的0.1则由我们完毕手动载入。我们一起来看以下的代码:
IEnumerator LoadSceneAsync(string levelName)
{
///当前进度
int currentProgress = 0;
///目标进度
int targetProgress = 0;
AsyncOperation op = Application.LoadLevelAsync(levelName); //载入前面的0.9
op.allowSceneActivation = false;
while(op.progress < 0.9f)
{
//计算目标进度
targetProgress = (int)op.progress * 100;
//在当前进度和目标进度间进行平滑
while(currentProgress < targetProgress)
{
++currentProgress;
//在这里处理载入相关的逻辑
yield return new WaitForEndOfFrame();
}
} //载入剩下的0.1
targetProgress = 100;
//在当前进度和目标进度间进行平滑
while(currentProgress < targetProgress)
{
++currentProgress;
//在这里处理载入相关的逻辑
yield return new WaitForEndOfFrame();
}
op.allowSceneActivation = true;
}
在这段代码中,我们实际上是利用allowSceneActivation属性控制了场景载入的过程,前面的0.9是正常的载入过程,而剩下0.1却是为了掩人耳目而补充的载入过程。
这里我们还使用了一个平滑的过程,这是由于假设直接以目标进度来设置载入相关的逻辑如进度条。那么表现出来的情况就是载入的进度不均匀,由于载入的进度是跳跃性的。
但是实际上载入的进度能百分之百的控制为均匀吗?答案是不行的。由于载入过程中的资源本身就不是均匀的啊。所以本质上进度条就是游戏资源载入过程中的遮羞布,它本身就带有非常强的欺骗感,比方进度都已经记载了99%了,结果最后的1%却载入了好长好长的时间,这科学吗?这显然不科学啊。这是博主又一次找到原始项目后又一次编写代码实现的场景载入进度条效果:
好了,这块儿基本没什么好说的了,再次提醒不读代码直接抄代码的各位,我本身不是什么大神。我写的代码一直非常渣,我从来没打算给大家一个开箱即用的编程体验,由于编程的过程本身就是迷惑的,须要你一步步地去探索。假设你仅仅是把我写的代码拷贝到编辑器中,却不知道该怎么用这个脚本,脚本中哪里有什么错误。我认为我真的没有必要在这样无私地奉献下去了,我自己从开源社区中收益良多。所以我自己愿意将自己了解到的、探索过的东西分享给大家,但是这并非各位懒惰的理由对吗?很多新手认为我对新手的要求特别严苛。大概新手们都认为假设我会了我还会来问你?可问题是新手不懂得该怎么提问甚至不懂得如何思考,我做本科毕业论文的时候尽管面对的是自己不喜欢的专业。可我比班里每个同学都认真,即使到了论文答辩的时候大家的结果不会由太大的区别,但是我挺喜欢这个过程的,由于它让我知道了怎么样去思考,毫无疑问,向别人求教是要做功课的,假设不懂得怎么提问,能够常常上上知乎,假设你希望的是我直接把代码写好给你、甚至帮你调试代码寻找Bug,抱歉我没有这种义务。有个读者从上周開始就向我频繁地索要一篇文章中没有给出源码的一个IO类的代码。我自认我已经将这个类的作用解释的特别清楚了,它没有做特别复杂的事情。仅仅是对System.IO这个命名空间的简单封装。我认为不论什么一个有编程基础的人都应该熟悉IO部分。但是将近一周过去了,这个读者还是没有自己实现这部分功能,试问就算我把代码给你了,你难道就会认认真真地看完然后从中有所收获?呵呵。我真不想为某些人的懒惰而愤慨了。我愿意分享技术是我的*。我不愿提供完整的代码相同是我的*。强迫是没有不论什么意义的。除了让你变得更加无知。好了,这次更新就是这样子!