用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