天天看点

在Unity3D的网络游戏中实现资源动态加载

用unity3d制作基于web的网络游戏,不可避免的会用到一个技术-资源动态加载。比如想加载一个大场景的资源,不应该在游戏的开始让用户长时间等待全部资源的加载完毕。应该优先加载用户附近的场景资源,在游戏的过程中,不影响操作的情况下,后台加载剩余的资源,直到所有加载完毕。 

本文包含一些代码片段讲述实现这个技术的一种方法。本方法不一定是最好的,希望能抛砖引玉。代码是c#写的,用到了json,还有c#的事件机制。 

在讲述代码之前,先想象这样一个网络游戏的开发流程。首先美工制作场景资源的3d建模,游戏设计人员把3d建模导进unity3d,托托拽拽编辑场景,完成后把每个gameobject导出成xxx.unity3d格式的资源文件(参看buildpipeline),并且把整个场景的信息生成一个配置文件,xml或者json格式(本文使用json)。最后还要把资源文件和场景配置文件上传到服务器,最好使用cms管理。客户端运行游戏时,先读取服务器的场景配置文件,再根据玩家的位置从服务器下载相应的资源文件并加载,然后开始游戏,注意这里并不是下载所有的场景资源。在游戏的过程中,后台继续加载资源直到所有加载完毕。 

一个简单的场景配置文件的例子: 

mydemosence.txt 

json代码 

"assetlist" : [{ 

"name" : "chair 1", 

"source" : "prefabs/chair001.unity3d", 

"position" : [2,0,-5], 

"rotation" : [0.0,60.0,0.0] 

}, 

"name" : "chair 2", 

"position" : [1,0,-5], 

"rotation" : [0.0,0.0,0.0] 

"name" : "vanity", 

"source" : "prefabs/vanity001.unity3d", 

"position" : [0,0,-4], 

"name" : "writing table", 

"source" : "prefabs/writingtable001.unity3d", 

"position" : [0,0,-7], 

"rotation" : [0.0,0.0,0.0], 

"name" : "lamp", 

"source" : "prefabs/lamp001.unity3d", 

"position" : [-0.5,0.7,-7], 

}] 

assetlist:场景中资源的列表,每一个资源都对应一个unity3d的gameobject 

name:gameobject的名字,一个场景中不应该重名 

source:资源的物理路径及文件名 

position:gameobject的坐标 

rotation:gameobject的旋转角度 

你会注意到writing table里面包含了lamp,这两个对象是父子的关系。配置文件应该是由程序生成的,手工也可以修改。另外在游戏上线后,客户端接收到的配置文件应该是加密并压缩过的。 

主程序: 

c#代码 

。。。 

public class mainmonobehavior : monobehaviour { 

public delegate void maineventhandler(gameobject dispatcher); 

public event maineventhandler startevent; 

public event maineventhandler updateevent; 

public void start() { 

resourcemanager.getinstance().loadsence("scenes/mydemosence.txt"); 

if(startevent != null){ 

startevent(this.gameobject); 

public void update() { 

if (updateevent != null) { 

updateevent(this.gameobject); 

这里面用到了c#的事件机制,大家可以看看我以前翻译过的国外一个牛人的文章。c# 事件和unity3d 

在start方法里调用resourcemanager,先加载配置文件。每一次调用update方法,mainmonobehavior会把update事件分发给resourcemanager,因为resourcemanager注册了mainmonobehavior的update事件。 

resourcemanager.cs 

private mainmonobehavior mainmonobehavior; 

private string mresourcepath; 

private scene mscene; 

private asset msceneasset; 

private resourcemanager() { 

mainmonobehavior = gameobject.find("main camera").getcomponent<mainmonobehavior>(); 

mresourcepath = pathutil.getresourcepath(); 

public void loadsence(string filename) { 

msceneasset = new asset(); 

msceneasset.type = asset.type_json; 

msceneasset.source = filename; 

mainmonobehavior.updateevent += onupdate; 

在loadsence方法里先创建一个asset的对象,这个对象是对应于配置文件的,设置type是json,source是传进来的“scenes/mydemosence.txt”。然后注册mainmonobehavior的update事件。 

public void onupdate(gameobject dispatcher) { 

if (msceneasset != null) { 

loadasset(msceneasset); 

if (!msceneasset.isloadfinished) { 

return; 

//clear mscene and msceneasset for next loadsence call 

mscene = null; 

msceneasset = null; 

mainmonobehavior.updateevent -= onupdate; 

onupdate方法里调用loadasset加载配置文件对象及所有资源对象。每一帧都要判断是否加载结束,如果结束清空mscene和msceneasset对象为下一次加载做准备,并且取消update事件的注册。 

最核心的loadasset方法: 

private asset loadasset(asset asset) { 

string fullfilename = mresourcepath + "/" + asset.source; 

//if www resource is new, set into www cache 

if (!wwwcachemap.containskey(fullfilename)) { 

if (asset.www == null) { 

asset.www = new www(fullfilename); 

return null; 

if (!asset.www.isdone) { 

wwwcachemap.add(fullfilename, asset.www); 

继续loadasset方法: 

if (asset.type == asset.type_json) { //json 

if (mscene == null) { 

string jsontxt = msceneasset.www.text; 

mscene = jsonmapper.toobject<scene>(jsontxt); 

//load scene 

foreach (asset sceneasset in mscene.assetlist) { 

if (sceneasset.isloadfinished) { 

continue; 

} else { 

loadasset(sceneasset); 

if (!sceneasset.isloadfinished) { 

代码能够运行到这里,说明资源都已经下载完毕了。现在开始加载处理资源了。第一次肯定是先加载配置文件,因为是json格式,用jsonmapper类把它转换成c#对象,我用的是litjson开源类库。然后循环递归处理场景中的每一个资源。如果没有完成,返回null,等待下一帧处理。 

else if (asset.type == asset.type_gameobject) { //gameobject 

if (asset.gameobject == null) { 

wwwcachemap[fullfilename].assetbundle.loadall(); 

gameobject go = (gameobject)gameobject.instantiate(wwwcachemap[fullfilename].assetbundle.mainasset); 

updategameobject(go, asset); 

asset.gameobject = go; 

if (asset.assetlist != null) { 

foreach (asset assetchild in asset.assetlist) { 

if (assetchild.isloadfinished) { 

asset assetret = loadasset(assetchild); 

if (assetret != null) { 

assetret.gameobject.transform.parent = asset.gameobject.transform; 

asset.isloadfinished = true; 

return asset; 

终于开始处理真正的资源了,从缓存中找到www对象,调用instantiate方法实例化成unity3d的gameobject。updategameobject方法设置gameobject各个属性,如位置和旋转角度。然后又是一个循环递归为了加载子对象,处理gameobject的父子关系。注意如果loadasset返回null,说明www没有下载完毕,等到下一帧处理。最后设置加载完成标志返回asset对象。 

updategameobject方法: 

private void updategameobject(gameobject go, asset asset) { 

//name 

go.name = asset.name; 

//position 

vector3 vector3 = new vector3((float)asset.position[0], (float)asset.position[1], (float)asset.position[2]); 

go.transform.position = vector3; 

//rotation 

vector3 = new vector3((float)asset.rotation[0], (float)asset.rotation[1], (float)asset.rotation[2]); 

go.transform.eulerangles = vector3; 

这里只设置了gameobject的3个属性,眼力好的同学一定会发现这些对象都是“死的”,因为少了脚本属性,它们不会和玩家交互。设置脚本属性要复杂的多,编译好的脚本随着主程序下载到本地,它们也应该通过配置文件加载,再通过c#的反射创建脚本对象,赋给相应的gameobject。 

最后是scene和asset代码: 

public class scene { 

public list<asset> assetlist { 

get; 

set; 

public class asset { 

public const byte type_json = 1; 

public const byte type_gameobject = 2; 

public asset() { 

//default type is gameobject for json load 

type = type_gameobject; 

public byte type { 

public string name { 

public string source { 

public double[] bounds { 

public double[] position { 

public double[] rotation { 

public bool isloadfinished { 

public www www { 

public gameobject gameobject { 

代码就讲完了,在我实际测试中,会看到gameobject一个个加载并显示在屏幕中,并不会影响到游戏操作。代码还需要进一步完善适合更多的资源类型,如动画资源,文本,字体,图片和声音资源。 

动态加载资源除了网络游戏必需,对于大公司的游戏开发也是必须的。它可以让游戏策划(负责场景设计),美工和程序3个角色独立出来,极大提高开发效率。试想如果策划改变了什么npc的位置,美工改变了某个动画,或者改变了某个程序,大家都要重新倒入一遍资源是多么低效和麻烦的一件事。

<a href="http://www.web3d.com.cn/new/teach/unity3d/2012/3/27/37137809.html">http://www.web3d.com.cn/new/teach/unity3d/2012/3/27/37137809.html</a>

简介:09年入行,喜欢游戏和编程,对3d游戏和引擎尤其感兴趣。 

版权声明:本文版权归作者和博客园共有,欢迎转载。转载必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

转载:http://www.cnblogs.com/geniusalex/archive/2013/02/22/2922975.html