RacingGame學習筆記1——輔助類

來源:互聯網
上載者:User

第一節:輔助類

我想,在開始分析飛車的代碼前,有必要先解釋下輔助類這個概念。

顧名思義,輔助類的作用就是提供服務。在物件導向設計的表述中他們也常被稱為服務者。與服務者相對應的是掌握控制權的控制者。他們兩者之間的關係可以理解為士兵和軍官之間的關係:控制者控制著程式中的全部,或部分邏輯;而服務者往往只封裝了某一些方面的功能,自身並沒有控制的能力,僅僅只是聽候控制者的調遣。這種劃分系統結構的方式在實際應用中經常被使用。其最大好處是結構清晰,同時也把在軟體生存期間可能需要反覆修改的控制流程程集中到不處理低層問題的控制類中。相當於避免讓一個戰略家被繁雜事物困擾,使之能夠專心思考戰略全域上的問題。

現在可以看看我們可愛的飛車遊戲代碼中的一些輔助類的組織圖了。值得注意的一點是飛車中類的組織圖中,雖然能明顯看到一個Helpers檔案夾,讓人知道這裡面是輔助類,但並不是所有的提供服務的類都在這兒。如Graphics中的很多提供基本繪製的類,按上文所說的服務和控制的分法來說,也應該屬於輔助類。在飛車的組織圖中,同樣可以看到一個名為GameLogic的檔案夾,從名字上很明顯可以判斷這屬於控制者。但同樣,並非代碼中所有的控制者都在GameLogic中,在GameScreens中的那些控制著遊戲介面樣式的類同樣屬於控制類。所以說輔助類和控制類是更多意義上是一種抽象的理解。在Benjamin Nitschke設計每一個類的時候並沒有刻意去注意哪些類屬於服務者,哪些類又是控制者。但我們在閱讀他的代碼中,思路中若能把握這樣一條線索,對整個遊戲的結構便能看的更加明晰。

這一節的主要內容是分析飛車結構中的Helpers目錄中的幾個檔案。如前所述,嚴格意義上這並不是代碼中全部的輔助類。但為了全編的條理性,我將其他涉及具體功能的輔助類放到相應節中敘述。

 

檔案一:ColorHelper.cs

從檔案名稱中就能夠直觀地看出該檔案是一個顏色方面的輔助類。像這樣以檔案的主要內容為開頭,以Helper、Manager、Controler、Keeper這樣表示身份的詞作為結尾的命名方式同樣也是物件導向設計中常使用的方法。比如負責碰撞檢測的類稱為CollisionManager,專門用來儲存情境資訊的類稱為SceneDataKeeper等,在飛車的原始碼中也能發現不少像這樣的名稱。

讓我們開始分析這個源檔案。原始碼中對ColorHelper類的注釋是這樣說的:“顏色輔助類,只是轉換顏色到不同的格式,同時提供在Color類(指XNAFramework中的Color類)中缺少的輔助方法。”

然後我們可以注意下對ColorHelper類的聲明。ColorHelper類被聲明為public static class。對於public,我本並不想在此過多的說明,任何一本類C語言的入門書都會對這個關鍵字做全面的解釋。但仍然容易見到很多寫代碼的人對public、internal、private這三個關鍵字的理解有限。很多人不明白internal的作用,該用internal的地方都用public代替了。實際上internal的功能是強大的。在我當前的一個遊戲項目SmartTank中,利用了

[assembly: InternalsVisibleTo( "assemblyName" )]

這個程式集屬性將一個基礎程式集的部分函數的調用許可權制在若干高層程式集中。而在另一些未用該屬性聲明為內部可見的程式集中,這些函數是不可見的。個人認為這是管理組件之間的可見域的一種很簡便的方法。免去了另一種在程式集之間使用介面和動態注入的辦法的繁瑣。

而static這個關鍵字卻是輔助類的一個很典型的標誌。通過static關鍵字來將一個類聲明為靜態類。這樣,對ColorHelpr的所有引用都只會是ColorHelper.StaitcMethod()或者ColorHelper.StaticVariable這兩種方式。符合輔助類不管理狀態變數的特點。在飛車的代碼中我們將會看到很多提供低層服務的內都被聲明為靜態類。

然後我們來看看ColorHelper類中的具體內容。首先是聲明了兩個Color類型的常量,Empty與HalfAlpha。聲明中之所以用readonly關鍵字而不用const是因為const不能修飾包括了結構類型的type類型。(更詳細的解釋請參考MSDN。)Benjamin認為這兩個常量會經常被使用,所以就在這裡聲明為靜態唯讀對象了。XNAFramework中的很多類中也包含這樣的常量,例如Vetctor2.Zero。

接下來的公有函數中,提供了兩種混合顏色的方法,和兩種改變色彩深度/透明度的方法。

MultiplyColors(Color col1, Color col2):將兩個顏色各通道的值相乘後除以255,作為新顏色的相應通道值。

InterpolateColor(Color col1, Color col2, float percent):將兩個顏色各通道的值線性混合後作為新顏色的相應通道值。

經過對兩個方法的比較測試,我們可以發現,前一個方法會凸現在兩個顏色中值都較高的通道,而削弱兩個顏色中值都不高的通道;而後一種方法由於是線性混合,新的顏色是輸入顏色的某個中間色,而參數percent(0~1f)表示第二個輸入顏色在輸出顏色中所佔的百分比。

ApplyAlphaToColor(Color col, float newAlpha ):將傳入顏色的Alpha值(透明度)修改為newAlpha*225,作為輸出顏色。

MixAlphaToColor ( Color col, float newAlpha ):將傳入顏色的Alpha值修改為newAlpha*225並將其他通道的值乘以newAlpha作為輸出顏色相應通道的值。

這兩個函數在製作2d貼圖的淡入淡出效果時非常有用。有了這個函數,我們只需要在遊戲每一次Update中改變(遞增或遞減)一個儲存著Alpha值的變數,然後將此變數傳遞給這兩個函數其中之一來產生實際的繪製顏色,就能製作出淡入或淡出效果。如果使用前者,則單純改變顏色的透明度,如果使用後者,將會發現在改變透明度的同時還改變了顏色的深淺。

雖然這個類看起來很短小,但作用倒是不小。所以我在SmartTank的項目就直接將該檔案引用了。輔助類的另一個特點就是跟具體程式松耦合,能夠很容易地轉移到其他項目中。

 

檔案二:Directories.cs

個人很疑惑為什麼Benjamin沒有把這個類聲明為靜態類,而是寫了一個空的私人建構函式來防止在類的外部建立這個類的執行個體。實際上聲明為靜態類完全不成問題,編譯得很順利。(當然還得去掉那個私人建構函式。)難道Benjamin這樣做的目的是想向我們展示他達到同一目的的不同途徑?恐怕這個問題只能去問他本人了。順帶著說明一下為什麼要將建構函式聲明為私人的:一個類,即使你不寫建構函式,編譯器預設都會為你產生一個不帶參數的空的公用建構函式。這樣只要在類的可見域中,任何部分的代碼都可以建立一個類的執行個體。但如果我們寫了一個私人的不帶參數的建構函式來覆蓋這個預設的建構函式,那麼這個類便沒有公用建構函式了。自然,在這個類的外部就無法建立這個類的執行個體了。這種方法常使用在單一實例類這種設計模式中——這種類只擁有一個唯一的執行個體,往往在這個類中作為靜態成員被建立。

言歸正傳,讓我們來分析下這個Directories類的功能和他為整個程式帶來的怎樣的好處。

Directory意為目錄。Directories便是管理程式中所有檔案目錄常量的類了。Directories類中的一個名為Directories的區間(region)。此區間中包含了若干程式其他部分常用到的目錄的路徑。任何需要此路徑的地方,都必須來詢問Directories類。就好比Directories類是一個管理倉庫的老爺爺。其他人(類)要到倉庫裡去找東西,每一次來都要問老爺爺螺絲放哪裡了啊?扳手又放哪裡了啊?老爺爺就告訴他們具體的位置。如果出於某種原因螺絲要換地方存放了,(遊戲的編寫過程中很有可能會變化目錄結構)只需要通知老爺爺就可以了,以後別人來要螺絲,老爺爺又會告訴他螺絲的新位置。這種方式比讓每個人評自己的印象去找螺絲要好的多,如果螺絲換位子了而這個資訊又沒有通知到每一個需要螺絲的人。那麼沒有獲知的人去找螺絲就會出異常了。其實早在C語言時期,就有一條代碼約定:不要在代碼中直接包含常量,先將常量用標識符表示出來。(通過#define或者常量聲明)然後在代碼中每個需要這個常量的地方,都代以該標識符。作用也是一樣的。

Directory類中使用的比較多的一個函數是Path.Combine( string, string )。這個函數在拼接目錄或檔案路徑時非常有用。不需要你關心傳入的字串的開頭或結尾是否包括那兩個“/”。Path類也就是System.IO名稱空間中的一個輔助類了,還包含有很多與路徑相關的輔助靜態函數。

最後要說一下類中對GameBaseDirectory的初始化語句:

GameBaseDirectory = StorageContainer.TitleLocation;

StorageContainer類是XNAFramework中定義的一個類。而TitleLocation是用來獲得遊戲執行目錄的絕對路徑的。如果你查詢TitleLocation的定義的話,就可以發現這麼一行注釋:The title's install location based on the current platform.意思就是說傳回值視平台的不同而不同。也就是說,這個類是為了方便統一處理在windows平台和xbox平台上的儲存檔案的。無論這個程式在windows上運行,還是xbox平台上運行,StorageContainer.TitleLocation都可以返回相應的運行目錄。免除了XNA程式員的一個麻煩。

 

檔案三:FileHelper.cs

讓我們接下來將視線轉移到程式中處理檔案IO的一個輔助類,FileHelper。

這個類的輔助函數包括了對檔案讀寫的基本操作;包括從檔案中提取包含檔案中每一行的字串的數組,(GetLines函數);也包括了對一些特定資料結構的讀寫的輔助函數。

對檔案的讀寫操作涉及平台特性。XNA中的檔案讀寫操作在本書之前的章節已經提及,本處就不在重複說明了。

讓我們把視線轉入Getlines函數中。其中有這樣一個迴圈:

List<string> lines = new List<string>();

do

{

    lines.Add( reader.ReadLine() );

} while (reader.Peek() > -1);

MSDN中對擷取檔案中行的範例程式碼並沒有用do while句型。而是:while(reader.Peek() >= 0)

{

       lines.Add( reader.ReadLine() );

}

讀者可能會擔心Benjamin這樣的寫法遇到所讀檔案為空白的時候會不會拋出異常。經過一個簡單的測試,發現在此使用do while這種句型確實有值得商榷的地方:當所讀檔案為空白時,reader.ReadLine()的傳回值為null。而這個沒有被初始化的string對象就這樣被添加到lines中,又返回到調用GetLines函數的地方。帶來一些潛在的危險。個人認為還是採用MSDN中的寫法比較妥當。

另外我們來看看FileHelper當中的幾個序列化和還原序列化的輔助函數。分別將三元向量,四員向量,4*4矩陣進行讀寫。對序列化這個名詞可以略微解釋一下:因為檔案的讀寫操作都是以流的形式進行的。(涉及硬體的工作形式。)所以如果要將記憶體中的資料儲存到磁碟上或者通過網路傳播出去,抑或是要從磁碟或網路上將資訊裝換成記憶體中那樣的結構形式,就必須經過序列化或還原序列化的過程。BinaryWriter與BinaryReader實際上已經綁定到某個流上,執行writer.Write()或者reader.Read()時實際上是在一個流上進行讀寫操作。看看這幾個序列化的函數就可以發現序列化無非只是將某個資料結構按一定的順序寫到流中,而還原序列化只是按同樣的順序從流中讀取資料,並用來初始化這個資料結構而已了。

一旦自己的遊戲中需要儲存和讀取自訂檔案,(比如從地圖編輯器中儲存一個遊戲情境,然後在遊戲中讀入這個情境檔案。)就必然會面臨檔案的序列化和還原序列化的問題。整個檔案的序列化,實際上也是由FileHelper中這樣的基本資料結構的序列化組成的。這種情況下,除了自己設計序列化的方式之外,還可以遵循.Net Framework提供的一個序列化的管理方法。關於這個,可以從MSDN中關於ISerializable、SerializableAttribute、BinaryFormatter的頁面中獲知。

 

檔案四:Log.cs

相信大家對Log這個詞一定不會陌生。這個記錄日誌的東西已經是中大型應用程式中不可缺少的組成部分。他的目的就是用來Debug的。或許一些人會忽視這玩意的作用,認為自己的程式出點什麼問題,設定幾個斷點,跟蹤一下就都能夠解決。(我最初也抱有這樣的頑固念頭。)但設想一下,在像我們遊戲更新的Update函數那樣的反覆迴圈中,出現了一個足夠偶然,(大概幾千次迴圈會無規律地出現那麼一兩次。)但又足以讓程式無法正常啟動並執行Bug。又加上這個bug涉及的因素可能不止一個,或者你根本不知道這個bug從何而來。在這種情況下,你想重現這個bug都幾乎不可能。更別說修正它了。但在這樣情況下,如果我們有一個像這樣的Log類,能對關鍵組件的運行狀態進行記錄。那麼一旦發現程式運行不正常了,只需看看Log寫下的記錄,對bug的定位就會容易很多了。然後可以在出問題的組件中進行調試,或者按測試的那套方法進行修改。目的性明確了不少。

來看Log類的結構。只有兩個函數,一是initialize(),用來建立記錄檔的,可以看到他調用了FileHelper的CreateGameContentFile方法(貌似沒有該方法),傳入createNew參數的是false,意思就是如果檔案存在,開啟這個檔案並返回就可以了。可見Log類每次運行都是接著同一個記錄檔的末尾來添加新日誌。也可以在代碼中看到每次程式運行都會在記錄檔中添加一個記錄已耗用時間的說明頭,以便能夠區分不同時間的作業記錄。

第二個函數就是Log的輔助函數了:Write( string message )。程式中任何的位置可以調用這個函數來記錄下出錯的資訊。而這個函數的作用就是往記錄檔中寫下這個訊息了。

值得注意的是這麼一個預先處理指令:

#if DEBUG

    // In debug mode write that message to the console as well!

     System.Console.WriteLine(s);

#endif

這幾行的意思是,如果當前啟動並執行是Debug版本的組建檔案,那麼也在控制台上輸出這條資訊。為避免一些讀者在此處產生疑惑,我就簡單的介紹下System.Console.WriteLine(s)在這裡的作用。如果你在C#的IDE(整合編輯環境,在這裡也就指C# Express或者Visual Studio)中開啟了RacingGame的解決方案。右鍵點擊“方案總管”中RacingGame項目名,可以在右鍵菜單中的最下方找到“屬性”。單擊後出現屬性編輯介面。在屬性介面中定位到“應用程式”選項卡,可以看到“程式集名稱”,“預設命名空間”,接下來是一個名為“輸出類型”的選框,當前它的值是Windows應用程式。開啟下拉式清單,會發現還有兩種其他的類型,分別是“控制台應用程式”和“類庫”。這裡,如果選擇“控制台應用程式”的話,編譯再啟動並執行時候會首先出現一個控制台,然後再出現遊戲的視窗。這種情況下Console.WriteLine函數的輸出就會顯示在控制台中。但如果項目輸出類型是Windows應用程式的話,控制台的視窗將不在顯示,Console.WriteLine函數的輸出會記錄在輸出視圖中。而輸出視圖可以通過IDE菜單中“視圖”下的“輸出”切換是否顯示。這樣,當遊戲還在Debug階段是,直接通過輸出視圖便可以查看本次啟動並執行日誌。方便了程式的調試。

 

檔案五:RandomHelper.cs

讓遊戲中出現一些隨機的因素能夠大大加強遊戲的可玩性。在暴雪公司的著名作品《DiabloII》中,每建立一個玩家帳號,產生的遊戲地圖都會不一樣。因為遊戲中地圖的具體形狀是通過一個地圖種子按一定的演算法產生的。地圖種子的值不同,得到的地圖形狀就會不同。.NetFramework中提供一個產生隨機數的類——Random。其內部也是使用一個種子,通過一種隨機演算法產生隨機數序列。也就是說,如果種子相同,產生的隨機序列就會一摸一樣。這樣的序列一般稱之為偽隨機數序列。但如果把種子與程式的已耗用時間相關聯,由於程式每次已耗用時間不可能一樣,產生的隨機數序列就不可能重複出現了。

RandomHelper類就是對Random類進行了封裝。在GenerateNewRandomGenerator函數中可以明顯看出是怎樣將已耗用時間設定為Random用於產生序列的種子的:

globalRandomGenerator = new Random( (int)DateTime.Now.Ticks );

接下來,在region“Get random float and byte methods”中定義了一些獲得某一種類型的隨機值的輔助函數。其中NormalVector3的含義是標準三元向量,即長度為1的三元向量。

這同樣是一個很實用的輔助類,所以我的遊戲中也直接包含了這個檔案。

 

檔案六:Vector3Helper.cs

這是Helpers目錄中的最後一個檔案了。檔案的內容也是最簡單的——補充一些三元向量的實用函數,分別有:計算兩向量之間的夾角;計算點到直線的距離;計算點到面的距離。好在中國人的數學普遍比美國人學得好,這本書的之前的章節對遊戲中的數學問題也做過介紹了,所以這裡對數學的計算公式也就不再多說。我們在自己的項目中,如果有必要的話,完全可以寫一個更好的數學組件。包括這裡的一些向量運算。

 

小節:

本節通過對Helpers檔案夾中六個輔助類的分析,瞭解了輔助類的作用。瞭解了物件導向設計中控制者和服務者之間的分工合作。同時也介紹了檔案IO、日誌記錄、隨機數產生等相關知識。

輔助類的一大優點是跟具體項目基本不耦合,可以隨著項目經驗不斷積累,不斷改良。同時也容易相互交流,分享。所以祝大家都能積累出自己的一些小小的財富吧!

本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/enginetanmary/archive/2009/09/25/4593692.aspx

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.