unity动态加载.docx
《unity动态加载.docx》由会员分享,可在线阅读,更多相关《unity动态加载.docx(13页珍藏版)》请在冰豆网上搜索。
unity动态加载
用Unity3D制作基于web的网络游戏,不可避免的会用到一个技术-资源动态加载。
比如想加载一个大场景的资源,不应该在游戏的开始让用户长时间等待全部资源的加载完毕。
应该优先加载用户附近的场景资源,在游戏的过程中,不影响操作的情况下,后台加载剩余的资源,直到所有加载完毕。
本文包含一些代码片段讲述实现这个技术的一种方法。
本方法不一定是最好的,希望能抛砖引玉。
代码是C#写的,用到了Json,还有C#的事件机制。
在讲述代码之前,先想象这样一个网络游戏的开发流程。
首先美工制作场景资源的3D建模,游戏设计人员把3D建模导进Unity3D,托托拽拽编辑场景,完成后把每个gameobject导出成XXX.unity3d格式的资源文件(参看BuildPipeline),并且把整个场景的信息生成一个配置文件,xml或者Json格式(本文使用Json)。
最后还要把资源文件和场景配置文件上传到服务器,最好使用CMS管理。
客户端运行游戏时,先读取服务器的场景配置文件,再根据玩家的位置从服务器下载相应的资源文件并加载,然后开始游戏,注意这里并不是下载所有的场景资源。
在游戏的过程中,后台继续加载资源直到所有加载完毕。
一个简单的场景配置文件的例子:
MyDemoSence.txt
Json代码
{
"AssetList":
[{
"Name":
"Chair1",
"Source":
"Prefabs/Chair001.unity3d",
"Position":
[2,0,-5],
"Rotation":
[0.0,60.0,0.0]
},
{
"Name":
"Chair2",
"Source":
"Prefabs/Chair001.unity3d",
"Position":
[1,0,-5],
"Rotation":
[0.0,0.0,0.0]
},
{
"Name":
"Vanity",
"Source":
"Prefabs/vanity001.unity3d",
"Position":
[0,0,-4],
"Rotation":
[0.0,0.0,0.0]
},
{
"Name":
"WritingTable",
"Source":
"Prefabs/writingTable001.unity3d",
"Position":
[0,0,-7],
"Rotation":
[0.0,0.0,0.0],
"AssetList":
[{
"Name":
"Lamp",
"Source":
"Prefabs/lamp001.unity3d",
"Position":
[-0.5,0.7,-7],
"Rotation":
[0.0,0.0,0.0]
}]
}]
}
{
"AssetList":
[{
"Name":
"Chair1",
"Source":
"Prefabs/Chair001.unity3d",
"Position":
[2,0,-5],
"Rotation":
[0.0,60.0,0.0]
},
{
"Name":
"Chair2",
"Source":
"Prefabs/Chair001.unity3d",
"Position":
[1,0,-5],
"Rotation":
[0.0,0.0,0.0]
},
{
"Name":
"Vanity",
"Source":
"Prefabs/vanity001.unity3d",
"Position":
[0,0,-4],
"Rotation":
[0.0,0.0,0.0]
},
{
"Name":
"WritingTable",
"Source":
"Prefabs/writingTable001.unity3d",
"Position":
[0,0,-7],
"Rotation":
[0.0,0.0,0.0],
"AssetList":
[{
"Name":
"Lamp",
"Source":
"Prefabs/lamp001.unity3d",
"Position":
[-0.5,0.7,-7],
"Rotation":
[0.0,0.0,0.0]
}]
}]
}
AssetList:
场景中资源的列表,每一个资源都对应一个unity3D的gameobject
Name:
gameobject的名字,一个场景中不应该重名
Source:
资源的物理路径及文件名
Position:
gameobject的坐标
Rotation:
gameobject的旋转角度
你会注意到WritingTable里面包含了Lamp,这两个对象是父子的关系。
配置文件应该是由程序生成的,手工也可以修改。
另外在游戏上线后,客户端接收到的配置文件应该是加密并压缩过的。
主程序:
C#代码
。
。
。
publicclassMainMonoBehavior:
MonoBehaviour{
publicdelegatevoidMainEventHandler(GameObjectdispatcher);
publiceventMainEventHandlerStartEvent;
publiceventMainEventHandlerUpdateEvent;
publicvoidStart(){
ResourceManager.getInstance().LoadSence("Scenes/MyDemoSence.txt");
if(StartEvent!
=null){
StartEvent(this.gameObject);
}
}
publicvoidUpdate(){
if(UpdateEvent!
=null){
UpdateEvent(this.gameObject);
}
}
}
。
。
。
}
。
。
。
publicclassMainMonoBehavior:
MonoBehaviour{
publicdelegatevoidMainEventHandler(GameObjectdispatcher);
publiceventMainEventHandlerStartEvent;
publiceventMainEventHandlerUpdateEvent;
publicvoidStart(){
ResourceManager.getInstance().LoadSence("Scenes/MyDemoSence.txt");
if(StartEvent!
=null){
StartEvent(this.gameObject);
}
}
publicvoidUpdate(){
if(UpdateEvent!
=null){
UpdateEvent(this.gameObject);
}
}
}
。
。
。
}
这里面用到了C#的事件机制,大家可以看看我以前翻译过的国外一个牛人的文章。
C#事件和Unity3D
在start方法里调用ResourceManager,先加载配置文件。
每一次调用update方法,MainMonoBehavior会把update事件分发给ResourceManager,因为ResourceManager注册了MainMonoBehavior的update事件。
ResourceManager.cs
C#代码
。
。
。
privateMainMonoBehaviormainMonoBehavior;
privatestringmResourcePath;
privateScenemScene;
privateAssetmSceneAsset;
privateResourceManager(){
mainMonoBehavior=GameObject.Find("MainCamera").GetComponent();
mResourcePath=PathUtil.getResourcePath();
}
publicvoidLoadSence(stringfileName){
mSceneAsset=newAsset();
mSceneAsset.Type=Asset.TYPE_JSON;
mSceneAsset.Source=fileName;
mainMonoBehavior.UpdateEvent+=OnUpdate;
}
。
。
。
。
。
。
privateMainMonoBehaviormainMonoBehavior;
privatestringmResourcePath;
privateScenemScene;
privateAssetmSceneAsset;
privateResourceManager(){
mainMonoBehavior=GameObject.Find("MainCamera").GetComponent();
mResourcePath=PathUtil.getResourcePath();
}
publicvoidLoadSence(stringfileName){
mSceneAsset=newAsset();
mSceneAsset.Type=Asset.TYPE_JSON;
mSceneAsset.Source=fileName;
mainMonoBehavior.UpdateEvent+=OnUpdate;
}
。
。
。
在LoadSence方法里先创建一个Asset的对象,这个对象是对应于配置文件的,设置type是Json,source是传进来的“Scenes/MyDemoSence.txt”。
然后注册MainMonoBehavior的update事件。
C#代码
publicvoidOnUpdate(GameObjectdispatcher){
if(mSceneAsset!
=null){
LoadAsset(mSceneAsset);
if(!
mSceneAsset.isLoadFinished){
return;
}
//clearmSceneandmSceneAssetfornextLoadSencecall
mScene=null;
mSceneAsset=null;
}
mainMonoBehavior.UpdateEvent-=OnUpdate;
}
publicvoidOnUpdate(GameObjectdispatcher){
if(mSceneAsset!
=null){
LoadAsset(mSceneAsset);
if(!
mSceneAsset.isLoadFinished){
return;
}
//clearmSceneandmSceneAssetfornextLoadSencecall
mScene=null;
mSceneAsset=null;
}
mainMonoBehavior.UpdateEvent-=OnUpdate;
}
OnUpdate方法里调用LoadAsset加载配置文件对象及所有资源对象。
每一帧都要判断是否加载结束,如果结束清空mScene和mSceneAsset对象为下一次加载做准备,并且取消update事件的注册。
最核心的LoadAsset方法:
C#代码
privateAssetLoadAsset(Assetasset){
stringfullFileName=mResourcePath+"/"+asset.Source;
//ifwwwresourceisnew,setintowwwcache
if(!
wwwCacheMap.ContainsKey(fullFileName)){
if(asset.www==null){
asset.www=newWWW(fullFileName);
returnnull;
}
if(!
asset.www.isDone){
returnnull;
}
wwwCacheMap.Add(fullFileName,asset.www);
}
。
。
。
privateAssetLoadAsset(Assetasset){
stringfullFileName=mResourcePath+"/"+asset.Source;
//ifwwwresourceisnew,setintowwwcache
if(!
wwwCacheMap.ContainsKey(fullFileName)){
if(asset.www==null){
asset.www=newWWW(fullFileName);
returnnull;
}
if(!
asset.www.isDone){
returnnull;
}
wwwCacheMap.Add(fullFileName,asset.www);
}
。
。
。
传进来的是要加载的资源对象,先得到它的物理地址,mResourcePath是个全局变量保存资源服务器的网址,得到fullFileName类似1和Chair2用到了同一个资源Chair001.unity3d,加载Chair2的时候就不需要下载了。
如果当前帧没有加载完毕,返回null等到下一帧再做判断。
这就是WWW类的特点,刚开始用WWW下载资源的时候是不能马上使用的,要等待诺干帧下载完成以后才可以使用。
可以用yield返回www,这样代码简单,但是C#要求调用yield的方法返回IEnumerator类型,这样限制太多不灵活。
继续LoadAsset方法:
C#代码
。
。
。
if(asset.Type==Asset.TYPE_JSON){//Json
if(mScene==null){
stringjsonTxt=mSceneAsset.www.text;
mScene=JsonMapper.ToObject(jsonTxt);
}
//loadscene
foreach(AssetsceneAssetinmScene.AssetList){
if(sceneAsset.isLoadFinished){
continue;
}else{
LoadAsset(sceneAsset);
if(!
sceneAsset.isLoadFinished){
returnnull;
}
}
}
}
。
。
。
。
。
。
if(asset.Type==Asset.TYPE_JSON){//Json
if(mScene==null){
stringjsonTxt=mSceneAsset.www.text;
mScene=JsonMapper.ToObject(jsonTxt);
}
//loadscene
foreach(AssetsceneAssetinmScene.AssetList){
if(sceneAsset.isLoadFinished){
continue;
}else{
LoadAsset(sceneAsset);
if(!
sceneAsset.isLoadFinished){
returnnull;
}
}
}
}
。
。
。
代码能够运行到这里,说明资源都已经下载完毕了。
现在开始加载处理资源了。
第一次肯定是先加载配置文件,因为是Json格式,用JsonMapper类把它转换成C#对象,我用的是LitJson开源类库。
然后循环递归处理场景中的每一个资源。
如果没有完成,返回null,等待下一帧处理。
继续LoadAsset方法:
C#代码
。
。
。
elseif(asset.Type==Asset.TYPE_GAMEOBJECT){//Gameobject
if(asset.gameObject==null){
wwwCacheMap[fullFileName].assetBundle.LoadAll();
GameObjectgo=(GameObject)GameObject.Instantiate(wwwCacheMap[fullFileName].assetBundle.mainAsset);
UpdateGameObject(go,asset);
asset.gameObject=go;
}
if(asset.AssetList!
=null){
foreach(AssetassetChildinasset.AssetList){
if(assetChild.isLoadFinished){
continue;
}else{
AssetassetRet=LoadAsset(assetChild);
if(assetRet!
=null){
assetRet.gameObject.transform.parent=asset.gameObject.transform;
}else{
returnnull;
}
}
}