用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