




版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)
文檔簡介
1、Good is good, but better carries it.精益求精,善益求善。一個基于組件的動態(tài)對象系統(tǒng)-一、靜態(tài)的痛苦作為一個項目經(jīng)驗豐富的程序員,你經(jīng)常會遇到游戲開發(fā)過程中的“反復(fù)”(iterations):今天美術(shù)將一個靜態(tài)的模型改為骨骼模型并添加了動畫;明天企劃會議上決定把所有未拾取武器由原先的閃光效果改為原地旋轉(zhuǎn);后天你的老板告訴你:配合投資方的要求,需要提升AI的質(zhì)量,這使得AI需要響應(yīng)特定的碰撞檢測、可破壞的路徑變化,甚至彼此的交互。哦,修改設(shè)計,按照教科書上的做法我們必須對現(xiàn)有代碼進行重構(gòu),你回答道。但你的老板顯然不這么認為。盡管全體程序員一致地、強烈地反對,項目
2、經(jīng)理還是決定要在一周內(nèi)把這些改動全部付諸實施。這是一場噩夢不是嗎?于是工程上的禁忌、代碼層面的犯罪各種各樣丑陋不堪的東西寫進了游戲程序,除此之外,你還搭上了周末和女朋友約會的時間。更糟糕的是,當(dāng)你周一凌晨提交代碼后,發(fā)現(xiàn)原本“健壯”的游戲程序,經(jīng)常莫名奇妙地崩潰,這讓你的老板在投資方那里出盡了洋相后果可想而知。當(dāng)然這不能完全責(zé)怪你的項目經(jīng)理和老板,畢竟游戲不是一道純軟件大餐。而我相信,你的游戲只要還被當(dāng)作一件藝術(shù)品來制做,就永遠無法避免反復(fù)。既然它至榛完美的必經(jīng)之路就是設(shè)計上的反復(fù),那么我們總有辦法將它的沖擊降至最小。這里我要討論的是一個基于組件的對象系統(tǒng):在游戲?qū)又?,它可以使對象行為的改變?/p>
3、得異常簡單,甚至可以在無需程序員介入的情況下,由企劃或設(shè)計師來動態(tài)組合成新類型的對象,而作為應(yīng)用該系統(tǒng)的一個副產(chǎn)品,它還能為你的游戲?qū)哟a降低耦合度。下面讓我們來看看,傳統(tǒng)情況下我們是如何設(shè)計游戲?qū)拥模核械奈矬w都是一個Object。它作為游戲中所有類型的基類,由許多子類來繼承,諸如Renderable、Movable、Collideable等等。顧名思義是為可渲染對象、可移動對象、和計算碰撞的對象準備的基類。繼承自Renderable又有一個名為Animatable的類,顯然有經(jīng)驗的你也能猜到它具有賦予類型以動畫的功能。在Collider之下有一個Inventory類,它定義了可拾取物件的一
4、些規(guī)則。在此之下就是一些具體的類,例如會進行動畫的、可移動的Character人物類,以及只能渲染靜態(tài)物件的、可拾取的Weapon類、Item類、Armor類。這樣一個簡單的類繼承體系可以由圖1來表示。圖1一個傳統(tǒng)的、典型的、看上去不錯的繼承體系嗯,這個繼承體系看上去合理且干凈,絕對可以做教科書中的范例,而且對于這個簡單系統(tǒng)來說能工作得很好,直到有一天企劃的設(shè)計發(fā)生了修改。就像之前提到的,企劃們從測試員或內(nèi)測玩家中獲得了反饋:武器或者道具掉落在地上,如果沒有一點顯眼的表示,玩家很難注意到,甚至?xí)屨麄€游戲顯得死氣沉沉。于是他們告訴你武器掉落在地上需要原地旋轉(zhuǎn),就像Quake那樣,而道具掉落在地
5、上,每隔2秒要閃爍一下。你對照著類繼承圖比劃了一下,覺得可以把Inventory類的繼承關(guān)系從Collideable下轉(zhuǎn)移為多重繼承Collideable和Animatable。于是你開始修改類繼承結(jié)構(gòu),盡管Armor不需要播放動畫,一個空函數(shù)就可以打發(fā)它了。那么這個問題目前算是被解決了??墒呛镁安婚L,關(guān)卡企劃覺得目前剛體物理的效果還不錯,決定廣泛應(yīng)用這一特性,而他失望地發(fā)現(xiàn)很多物件都沒有剛體物理的效果,只有RigidBody才擁有這項功能,而它的實現(xiàn)只有一些簡單的盒子一類的物體,用于做關(guān)卡設(shè)計。于是他告訴你需要把屏幕上能看到的物體,盡量都賦予剛體特性。你同他爭執(zhí)了一段時間,最后你妥協(xié)了,把R
6、enderable整個拉到RigidBody繼承體系下。這樣盡管Tree和Character并不能按照一個簡單剛體來運動,但至少Weapon、Item、Armor可以了。在折騰完關(guān)于剛體物理對象的改動之后,你再度審視這個繼承體系時,發(fā)現(xiàn)它已經(jīng)不像原先那般優(yōu)雅了:大量定義接口的基類被放在繼承樹的上方,而下方都是零散的各個具體類。這很讓人倒胃口,你這么想著,打算著手真正重構(gòu)目前的代碼。但時間不等人,第二天企劃又告訴你,他需要用腳本來控制這些剛體對象的位置,這下連Movable都無法幸免,你必須把它移動到RigidBody之上,讓所有的具體類都能繼承它。這樣一個頭重腳輕(top-heavy)的繼承樹
7、簡直是一個教科書式的反面教材(如圖2所示)!堅持原則的你實在看不下去了,向項目經(jīng)理提出了質(zhì)疑,要求砍掉這個功能,或者開辟額外的時間讓你重構(gòu)代碼。但是很不幸,很多情況下,項目經(jīng)理是不會理睬這種要求的。圖2在許多“合理”的設(shè)計改動后,繼承樹往往變成了這種頭重腳輕的樣子如此這般的設(shè)計,為什么無法滿足游戲的快速反復(fù)的開發(fā)需要呢?我想主要原因有二:一是C+和其他強類型語言在繼承上的強制性;二是我們恰恰讓繼承做了它所不擅長的事情。繼承在很多強類型語言中,是一個靜態(tài)的語言行為,是在編譯期決定的,而且對一個較大的繼承體系的修改,不但面臨重重困難,而且將會對之后的系統(tǒng)產(chǎn)生深遠影響。繼承的這種特性決定了它不適合類
8、型行為經(jīng)常變更的場合,或者說在類型行為經(jīng)常變更的場合中,僅僅使用繼承很難解決矛盾。那除了繼承,語言的其他特性是否能滿足我們對對象類型這種近乎變態(tài)的反復(fù)要求呢?答案之一就是組合,或者聚合,直觀一點就叫“has-a”的關(guān)系。倍受推崇的設(shè)計模式一書中,也建議盡量使用對象組合而非類繼承。該書開宗明義寫道:“1、對接口編程,而不是對實現(xiàn)編程;2、優(yōu)先考慮使用對象組合,而不是使用類繼承”GoF94。至于原因,在書中也有很精辟的論述:“我們的經(jīng)驗顯示,架構(gòu)師經(jīng)常過分強調(diào)將繼承作為重用技術(shù),而事實上,如果著重以對象組合作為重用技術(shù),則可以獲得更多的可重用性以及簡單的設(shè)計”注1。二、動態(tài)的優(yōu)雅1、組合既然大師們
9、是這樣說的,我們不妨回頭看看游戲?qū)拥南到y(tǒng)。假設(shè)我們要設(shè)計這樣一個“武器”的類,類似上面的那個例子,它需要能渲染、能播放動畫、能移動位置,甚至在掉落在場景中時,它還具備剛體物理的特性。于是可以整理出如圖3這樣的類:圖3一個典型的由組件組合而成的對象??梢钥吹?,一個Weapon對象就是簡單地由IRenderable、IAnimatable、IMovable以及IRigidBody這些具體的組件組合而成的。在下文里,我就把組合成對象的這些功能性的類,稱為組件。哦,功能倒是都組合在一起了,但我怎么使用這些組件呢?它沒有任何可供調(diào)用的方式!經(jīng)常使用基類接口的你開始注意到這個問題。在傳統(tǒng)設(shè)計中,我們通常需
10、要一個統(tǒng)一的基類接口來操作多個對象,而這些接口被聲明為虛擬的,以便我們在類層次中實現(xiàn)多態(tài)(若接口不是虛擬的,則其調(diào)用的實現(xiàn)函數(shù)就是虛擬的),而在客戶端,我們一旦能獲得這個接口就能以統(tǒng)一的方式來處理所有從這個類繼承的類型。這種做法對于有經(jīng)驗的你早就像吃飯睡覺一般熟悉了,如果不能通過統(tǒng)一接口來處理多種對象,恐怕很多人要難過到死。我們的組件,也是由一個通用的組件基類接口定義的,權(quán)且稱他為IComponent,實現(xiàn)各自功能的組件,需要各自擴展這套接口。例如渲染組件IRenderable,可能需要擴展一個Render()方法;而動畫組件IAnimatable,可能要做的是擴展一個Update(float
11、)方法用以更新動畫;IMovable組件就需要SetPosition()/GetPosition()之類的接口等等。有了這些組件接口之后,我們的組件類就直接實現(xiàn)這些接口。例如Renderable就實現(xiàn)IRenderable:Render()。定義接口的優(yōu)點在于,你可以通過一個對象的句柄,查詢這個對象是否實現(xiàn)了某一組件的接口。如果回答是肯定的,則可以返回一個指向該組件的指針,而指針的類型是接口類,這樣客戶端代碼就可以調(diào)用這些組件的實際功能了。如果回答是否定的,則返回空指針,意味著該對象并未實現(xiàn)指定的接口,查詢失敗。圖4比較好的說明了這個問題。圖4需要使用組件接口時,要對ObjectManager
12、查詢組件接口。2、無中生有既然對象都是由組件組成的,那么對象本身就可以非常精簡,甚至連一個組件的指針都不用儲存,而將組件管理的工作可以交給對象管理器去做,我們暫且叫它ObjectManager。這樣,世界上就不存在名叫Car的對象,也不存在叫Dog的對象,它們不過是一些組件的組合而已,只是在ObjectManager一側(cè)的記錄中,有著Dog所擁有的組件,以及Car所擁有的組件。當(dāng)對象需要行為的時候,客戶端代碼就向ObjectManager索取對應(yīng)的組件接口,比如:代碼1IRenderable*renderable=static_cast(objectManager-QueryInterface
13、(object,TYPE_IRenderable);或者寫一道宏指令以減少筆誤:代碼2IRenderable*renderable=QUERY_INTERFACE(objectManager,object,IRenderable);之后你就像平常一樣操作這個組件:代碼3renderable-PreRender();.renderable-Render();.renderable-PostRender();所有不同類型的ObjectHandle看起來都是差不多的,他們的區(qū)別只在于記錄在ObjectManager里的組件不同而已。所以,忘掉類型的概念吧!在這個世界中,只有組件的組合,沒有死板的類型
14、。3、即插即用那么如果企劃再對我的Weapon提出什么非份的要求,怎么辦?擔(dān)驚受怕的你繼續(xù)問道。很簡單,如果Weapon類還需要其他的功能,只要這個功能已經(jīng)以組件形式實現(xiàn)了,那么你完全可以讓他自己搞定!因為基于組件的對象系統(tǒng)中,一個具體的對象已經(jīng)沒有靜態(tài)的“類型”概念了,只要我愿意,我可以對某個對象添加任意的組件功能,即使它看上去多么荒謬:代碼4ObjectHandle*weapon=objectManager-CreateObject();/創(chuàng)建了一個赤身裸體的對象,它還沒有任何功能。objectManager-AddComponent(weapon,TYPE_IRenderable);/對
15、象擁有了渲染的組件,及其功能。objectManager-AddComponent(weapon,TYPE_IProceduralAnimatable);/武器也需要過程動畫嗎?無論怎樣的組件都能添加。對象的能力不再“靜態(tài)”地由繼承關(guān)系決定,而是由一組扁平組織起來的組件“動態(tài)”地、自由地組合而成。只要組件實現(xiàn)得足夠健壯,我們可以放心地生成任意“類型”(或稱組合)的對象,而不用擔(dān)心設(shè)計上的修改和對象臃腫的問題。嗯,這樣的對象足夠靈活,但還不夠!我們可以解析描述對象的XML文件,從其中讀取的信息里,決定我們要生成什么樣的對象,以及添加怎樣的組件。代碼5voidCreateObjectFromXml
16、(XmlNode*pNode)ObjectHandle*object=NULL;if(pNode-GetName()=TEMPLATE_WEAPON)object=objectManager-CreateObject();objectManager-AddComponent(object,TYPE_IRenderable);objectManager-AddComponent(object,TYPE_IProceduralAnimatable);IProceduralAnimtable*procAnim=static_cast(objectManager-QueryInterface(obje
17、ct,TYPE_IProceduralAnimtable);ASSERt(procAnim);procAnim-SetSeed(Rand();procAnim-SetIteration(pNode-GetAttribute(Num_Iteration).ToNumber();else.哈哈!這樣我們可以把這些添加功能的工作,扔給企劃寫XML去了。而且我們可以為某些特定“類型”的對象定義模板,不用每次創(chuàng)建對象時都一個個手動添加類型。更上一層樓的做法是,把這套系統(tǒng)暴露給腳本系統(tǒng),讓腳本也可以創(chuàng)建自己的組件,同時也能使用C+已定義的、且暴露給腳本的組件。這樣連企劃也能使用腳本來創(chuàng)建新類型的組件,然后
18、隨他們高興去創(chuàng)建、組合對象,反正隨便他們怎么折騰都行。我們不僅能讓數(shù)據(jù)來驅(qū)動對象的“內(nèi)容”,還能驅(qū)動對象的“類型”,真真正正地做到了數(shù)據(jù)驅(qū)動,不是嗎?當(dāng)然,這種方法也是有負面效果的,任何方法都不可能完美。負面效果就是組合對象變得太過容易了,一不小心企劃就創(chuàng)建了成千上萬種不同對象,對于游戲平衡調(diào)整的復(fù)雜度也會隨之提高,不過,這就是企劃的份內(nèi)事務(wù)了。4、深化交流與合作理想情況下,組件之間應(yīng)該不進行通信。但如果組件之間完全不進行通信,那么這個游戲估計也不怎么吸引人了。組件之間進行通信的方式一般有兩種:其一是通過查詢接口,直接獲得其他組件的接口,通過調(diào)用函數(shù)的方式進行通信。例如:代碼6voidRend
19、erable:Render()/需要獲得對象在世界空間的位置IMovablemovable=static_cast(objectManager-QueryInterface(object,TYPE_IMovable);ASSERt(movable);constPoint3&pos=movable-GetPosition();./通過獲得的位置信息進行繪制。這種通信方式會增加代碼之間的耦合性,但適合于需要知道特殊接口,或需要保證調(diào)用順序的場合。第二種方式就是事件(或消息)。通過事件和消息,也迫使代碼之間的耦合度下降。例如:代碼7objectManager-SubscribeEvent(TYPE_
20、IAnimatable,EVENT_Tick,newMemberFunctor(Animatable:OnTickEvent);.voidAnimatable:OnTickEvent(floattick)_fElapsedTime+=tick;而在發(fā)生事件的組件中,只需要調(diào)用以下方法即可以讓所有注冊的回調(diào)函數(shù)響應(yīng):代碼8objectManager-FireEvent(EVENT_Tick,tick);基于事件或消息的通信方式,由于它的調(diào)用取決于注冊順序,適合于無需保證調(diào)用順序的場合。最后在我們的場景中,對象之間的組織如圖4所示:圖5我們的場景像是一個由組件組成的二維表格,表格的列是同一組件類型
21、的實例,而每個對象就是表格的一行,它可以自由選擇是否需要某列提供的組件功能。三、著手實現(xiàn)該是著手寫一些代碼的時候了注2。基于上述應(yīng)用的代碼,我們肯定需要一個IComponent的接口,作為所有組件的基類:代碼9publicinterfaceIComponentvoidInit(ObjectIdoid,ObjectManagerobjMan);ObjectIdObjectIdget;這個接口只定義了一個組件的最小功能集,它所做的就是保留ObjectManager的句柄和所屬對象的句柄。根據(jù)IComponent接口,我們可以衍生出更多的接口:代碼10interfaceIComponentMovab
22、le:IComponentVector3Positionget;set;.interfaceIComponentRenderable:IComponentvoidTick(floatdt);boolDraw();這兩個接口分別定義了可移動對象以及可供渲染的對象的基本接口。在客戶端代碼中,基本上用戶只需要面對的就是這些接口,而不用關(guān)心其實現(xiàn)?,F(xiàn)在對于這些接口分別實現(xiàn)它的具體類:代碼11classComponentMovable:IComponentMovableObjectId_oid;Vector3_pos;publicComponentMovable()_pos=newVector3();p
23、ublicvoidInit(ObjectIdoid,ObjectManagerobjMan)this._oid=oid;publicObjectIdObjectIdgetreturn_oid;/有關(guān)世界位置的屬性publicVector3Positiongetreturn_pos;set_pos=value;.classComponentRenderable:IComponentRenderableObjectId_oid;ObjectManager_objMan;publicComponentRenderable()publicvoidInit(ObjectIdoid,ObjectManag
24、erobjMan)this._oid=oid;this._objMan=objMan;publicObjectIdObjectIdgetreturn_oid;publicvoidTick(floatdt)/有關(guān)幀更新的東西publicboolDraw()/有關(guān)渲染的東西returntrue;.以上這些具體類將會提供我們組件的基本能力。而這些組件的具體實現(xiàn),一旦注冊到對象管理器后,客戶端程序員就無需再關(guān)心它了。作為庫的提供者,我們甚至可以把這些實現(xiàn)類完全隱藏起來,讓客戶端的程序員以數(shù)據(jù)驅(qū)動的方式注冊這些類型,就像上節(jié)中解析XML的函數(shù)所作的一般。由于對象的功能都是由組件提供的,對象本身的表示將會
25、非常簡單,它只需要一個標(biāo)識自己的標(biāo)記就可以了。很多程序語言支持將地址或句柄作為對象的唯一標(biāo)識,所以有時候連這個標(biāo)識都可以去掉。不過為了除錯的目的,我們還是為它加上了一個描述自身的字串:代碼12publicclassObjectIdprivatestring_desc;publicObjectId(stringdesc)_desc=desc;publicstringDescriptiongetreturn_desc;我們的設(shè)計是想讓客戶端所需的先驗知識盡可能的少,只有對象句柄ObjectId、所需的接口類型IComponentXXX,以及ObjectManager的方法??梢哉fObjectMan
26、ager是這個系統(tǒng)核心部件。下面就讓我們來看一下ObjectManager是如何上演這出把戲的。首先我們需要讓ObjectManager創(chuàng)建對象,并把對象句柄返回給調(diào)用端,這可以有如下的簡單實現(xiàn):代碼13publicclassObjectManagerprivateDictionaryObjectId,Listobject2ComponentList;.publicObjectIdCreateObject(stringdesc)ObjectIdoid=newObjectId(desc);/為新的對象創(chuàng)建其所擁有的組件列表object2ComponentListoid=newList();ret
27、urnoid;接下來客戶端需要做的就是為對象添加組件。而這個添加組件的工作也相對簡單:代碼14publicclassObjectManager.publicboolAddComponentToObject(ObjectIdoid,IComponentcomponentInstance)ListcomponentList;if(object2ComponentList.TryGetValue(oid,outcomponentList)/TODO:需要保證同一對象中注冊的組件類型唯一componentInstance.Init(oid,this);componentList.Add(compone
28、ntInstance);returntrue;thrownewObjectNotFoundException(oid);一旦為某個對象添加了組件,其他代碼就可以通過QueryInterface的方法來獲得某一類型的組件的指針:代碼15publicclassObjectManager.publicIComponentQueryInterface(ObjectIdoid,TypeinterfaceType)ListcomponentList;if(object2ComponentList.TryGetValue(oid,outcomponentList)/在注冊列表中,查詢組件類型。如果不熟悉C#
29、匿名方法和委托:這里其實就是一個簡單的查找,/只不過查找條件是由Type.IsIntanceOf()的結(jié)果來決定的。IComponentfindResult=componentList.Find(delegate(IComponentcomponent)if(interfaceType.IsInstanceOfType(component)returntrue;elsereturnfalse;);returnfindResult;thrownewObjectNotFoundException(oid);如果查詢結(jié)果成功,則安全返回組件接口引用。查詢不到則返回空句柄。如果對象句柄本身也沒能查詢到
30、,則拋出一個異常以示抗議。到目前為止,ObjectManager已經(jīng)可以做到生成對象、為對象注冊組件、并提供外界查詢組件接口的功能。這樣,客戶端代碼已經(jīng)可以組合復(fù)雜對象,并通過查詢接口的方式在組件之間進行通信(見前一節(jié))??蛻舳舜a已經(jīng)可以這樣寫:代碼16ObjectIdoid=objectManager.CreateObject();objectManager.AddComponent(oid,newRenderable();objectManager.AddComponent(oid,newMovable();.IRenderable*renderable=objectManager.Qu
31、eryInterface(oid,typeof(IRenderable);renderable-Render();voidRenderble:Foobar()IMovablemovable=objectManager.QueryInterface(_oid,typeof(IMovable);DoSomethingWithPosition(movable.Position);不過要實現(xiàn)通過消息或事件的通信方式,需要再加把勁。C#由于有方便的委托機制,示例代碼中就使用了這種語言特性。而如果使用C+或者實現(xiàn)一個泛型函數(shù)綁定嫌麻煩的話,完全可以使用基于消息的機制,也足夠方便:代碼17publiccla
32、ssObjectManager/記錄逐事件的組件類型列表,一對多privateDictionarystring,Listevent2Types;/記錄逐組件類型的處理函數(shù),一對一privateDictionaryeventTable;/為每個組件類型準備的實例列表,字典中的記錄將會隨著每個組件的生成、銷毀而變化privateDictionaryType,Listtype2ComponentList;.publicvoidSubscribeEventHandler(stringeventName,TypereceivingComponentType,ComponentEventDispatche
33、rhandler)ListtypeList;if(!event2Types.TryGetValue(eventName,outtypeList)typeList=newList();event2TypeseventName=typeList;/TODO:檢測類型列表中,和目標(biāo)類型相同的記錄,避免為一個類型添加多個事件處理函數(shù)。typeList.Add(receivingComponentType);eventTablereceivingComponentType=handler;publicboolFireEvent(stringeventName,IComponentsender,Compo
34、nentEventArgse)boolprocessed=false;ListtypeList=event2TypeseventName;if(null!=typeList)/通知所有注冊了該事件的組件類型foreach(TypecomponentTypeintypeList)ComponentEventDispatcherdispatcher=(ComponentEventDispatcher)eventTablecomponentType;Listcomponents=type2ComponentListcomponentType;if(null!=components)/通知該組件類型的
35、所有實例foreach(IComponentcmpincomponents)/調(diào)用事件處理函數(shù)processed|=dispatcher(cmp,sender,e);returnprocessed;事件-類型在ObjectManager中的管理類似于圖5:圖6一套典型的事件-類型映射。組件類型中的顏色即對應(yīng)其注冊的事件顏色,例如IScriptable注冊了EventDraw和EventTick兩種事件。這樣客戶端可以利用ObjectManager提供的事件消息機制,寫出下面的代碼:代碼18publicclassMovable:IMovablepublicvoidMove(constVector
36、3offset)_pos+=offset;objectManager.FireEvent(EVENT_MOVE,this,newMoveEventArgs(offset,_pos);publicclassInventory:IInventoryprivateboolOnMoveEvent(IComponentsender,MoveEventArgse)/處理事件.returntrue;publicstaticboolMoveEventDispatcher(IComponentreceiver,IComponentsender,ComponentEventArgse)returnreceiver
37、-OnMoveEvent(sender,e);objectManager-SubscribeEvent(EVENT_MOVE,IIventory,Inventory.MoveEventDispatcher);.IMovablemovable=(IMovable)objectManager(oid,typeof(IMovable);movable.Move(newVector3(10,100,1000);/這將觸發(fā)EVENT_MOVE事件,從而調(diào)用Inventory組件注冊的處理函數(shù)。到目前為止,我們已經(jīng)基本覆蓋了一個基于組件的對象系統(tǒng)的實現(xiàn)和實際用例。它已經(jīng)完全勝任對象生成、組件注冊、組件接口查詢、以及注冊事件響應(yīng)、生成事件等工作。此外我們還能在這之上添加數(shù)據(jù)驅(qū)動的方法,可以讓系統(tǒng)直接從外部文件、外部輸入中獲得類型組合。拜動態(tài)類型查詢的機制所賜,它還能很方便地在游戲編輯器中實現(xiàn)一個對象debugger。這些額外的實現(xiàn)工作就留由有興趣的讀者自己實現(xiàn)了。本文附帶的示例代碼可以作為實現(xiàn)的一份參考。四、實施中的阻力可以預(yù)見的是,如此翻云覆雨的架構(gòu)變更將會在程序團隊中引起怎樣的轟動??梢员WC的是,實施這種做法一定會遇到阻力,除
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 創(chuàng)業(yè)合伙人簽訂合同范本
- 業(yè)務(wù)轉(zhuǎn)包合同范例
- 農(nóng)家樂入股合同范本
- 產(chǎn)品會展合同范本
- 不退不換合同范本
- 助聽器合同范本
- 勞務(wù)派遣合同范本6
- 借名辦證合同范本
- 倉庫租憑合同范本
- 勞動合同范本廣州
- 《計算機安全基礎(chǔ)》課件
- 養(yǎng)老院行業(yè)現(xiàn)狀分析-2023年中國養(yǎng)老院行業(yè)市場發(fā)展前景研究報告-智研咨詢
- 住房公積金貸款申請書
- 多物理場耦合與協(xié)同仿真技術(shù)
- 監(jiān)理人員的節(jié)后復(fù)工安全培訓(xùn)考試試題
- 胸腔穿刺知情同意書
- 學(xué)校物業(yè)管理機構(gòu)設(shè)置與運作方案
- 農(nóng)村住房竣工驗收記錄表
- 2020-2021學(xué)年人教版道德與法治八年級下冊全冊教材答案
- 會計崗位實訓(xùn)第5版林冬梅課后參考答案
- 總承包單位對分包單位的管理制度格式版(3篇)
評論
0/150
提交評論