?? 9-9.txt
字號:
特別提示:
為什么需要使用時間呢?為了方便討論,假設我們每一幀都把賽道移動相同的距離。也許在你的電腦上它運行的很完美,但在其他的系統上呢?找一臺比你的系統配置低的系統運行看看吧,賽道看起來會運行的相當緩慢。同樣換到配置較高的系統上,賽道又會移動的快很多。原因在于你的計算是基于幀速率(frame rate)。假設在你的系統上,每秒可以跑60幀,那么所有的計算過程都是依賴于這個靜態的幀速率而來的。因此,在每秒可以跑40幀或80幀的系統中,自然會得到不同的計算結果。讓你的程序在每一個系統下運行都得到同樣的結果是我們的基本目標之一,因此無論如何都應該避免基于幀速率的計算。
解決這個問題一個比較好的方法就是根據時間來計算位移。比如,賽道的最大速度定義為每秒250個單位。首先,我們需要獲得自上一次“更新”過后過去的時間間隔。.net運行時內建的一個屬性(tick count)可以用來獲得系統的tick count。但它它并不完美:這個計時器的精度太低。它的值大約每15毫秒才更新一次,因此,在一個高幀速率的系統中(每秒60幀以上),賽道的移動將是不連續的,因為所用的時間不是平滑的。
如果你的系統支持的話,在DirectX SDK包含了一個高精度(通常精度為1毫秒)的計時器類DirectXTimer。但如何你的系統不支持,那么則只能使用tick count了。本書都將使用這個計時器來計算時間。(注:這里的DirectXTimer實際上是作者通過P/Invoke自己實現的一個計時器,代碼在Utility.cs文件中,書上沒有具體講解實現方法,但大家應該都能看明白吧^_^)
為場景添加一輛可移動的賽車吧
好了,現在已經有了渲染好的、并且可以沿著場景移動的賽道了,接下來應該添加實際與玩家交互的對象了:一輛賽車??梢院唵蔚脑偬砑淤惖赖哪莻€主要的類里加上一些關于賽車的變量和常量就可以了,但這樣的代碼將不是模塊化的。你應該把關于賽車的代碼分離出來,成為一個獨立的類。為工程添加一個名為“Car”的新類吧。
Car類應該完成些什么任務呢?因為當其他物體移動的使用它仍然是靜止不動的,不需要向前,也不需要向后。但為了讓賽車能躲避路上的障礙物,它應該能夠左右移動,同樣,它還需要能渲染自身。好了,有了這些信息,就可以為類添加成員了:
詳見源碼
這些變量已經足夠用于控制賽車了。Height和depth都為靜態的常量。賽車向兩旁的移動速度的增量也是常量。使用最后一個常量的原因是賽車模型的大小剛好比賽道大,所以需要把它縮小一點點。
其他的成員基本上一看名字就知道它的用途了。有賽車當前的位置數據,默認情況下賽車位于賽道的左邊。賽車的直徑(Diameter),稍后會使用它來進行碰撞檢測。有賽車的側滑速度。當然,還有用來檢測賽車在向哪個方向移動的兩個布爾變量。最后,是有關mesh的變量。
Car類的構造函數需要完成兩個任務:創建mesh對象(包括與它相關的結構)以及計算賽車的直徑。添加一下構造函數:
public Car(){詳見源碼}
創建car mesh的方法和創建road mesh的方法基本上一樣。接下來計算直徑的新的代碼則是比較有趣的。這里實際上是在計算賽車的邊界球體(bounding sphere,mesh的所有的頂點都包含在這個球體內)。Geometry類包含了這個方法,只要把需要計算邊界的頂點作為參數傳給這個方法就可以了。
這里所需的就是從mesh獲得頂點。你已經知道頂點保存在頂點緩沖內的,因此直接使用這塊頂點緩沖。為了讀取頂點緩沖中的數據,必須調用lock方法。在下一章中,會學到更多來自于VertexBuffer類的lock方法重載?,F在,只需要知道這個方法會使用一個流返回所有頂點數據。還可以使用ComputeBoundingSphere方法獲得這個mesh的“中心”以及邊界球體的半徑。因為我們并不需要關心mesh的中心,所以只需要把半徑乘2獲得直徑就可以了。但是,模型經過了縮放,所以直徑也需要縮放同樣的比例。最后(在必不可少的finally塊中),確定解鎖并且釋放了頂點緩沖。
接下來,添加繪制賽車的方法。Car類已經保存了賽車的位置,只需要獲得device對象就可以繪圖了。這個方法幾乎和DrawRoad方法一樣,區別在于變量不同以及在變換前需要縮放mesh,添加如下代碼:
public void DrawCar(Device device) {詳見源碼}
在使用Car類之前,還需要讓外部可以訪問類的私有成員,添加如下公共屬性:
{詳見源碼}
現在,應該在主要的游戲引擎類里添加成員來使用Car類了。在DogerGame類里添加如下代碼:
private Car car = null;
由于car類的構造函數需要device作為變量才能初始化,所以只有在創建了device之后才能調用它。在OnDeviceReset方法里創建car是個不錯的主意,在創建了road mesh之后添加如下代碼:
car = new Car(device);
創建了賽車之后,就可以更新渲染部分的代碼了。在OnPaint中兩個DrawRoad方法之后添加以下代碼:
car.Draw(device);
可以看到,已經在路上正確的渲染了賽車。可是,如何才能控制賽車左右移動呢?先忽略鼠標的存在,假設玩家擁有鍵盤,并且將使用鍵盤來控制游戲。使用鍵盤上的4個方法鍵來控制游戲是不錯的選擇。重載onKeyDown方法:
protected override void OnKyeDown(KeyEventArgs e) {詳見源碼}
這里沒有什么特別的內容。如果按下了ESC則游戲結束同時關閉窗口。按下左鍵或者右鍵,則把相應的moving變量設置為true,另一個則設為false?,F在運行程序,按下按鍵可以正確更新賽車的兩個moving變量。但賽車本身并不會移動,還需要為賽車添加一個函數更新它的位置:
public void Update(float elapsedTime)
這個方法接受逝去的時間值作為參數,所以無論在任何系統上,都會得到相同的結果。這個方法本身很簡單,哪一個moving變量的值為true,則向那個方向移動移動相應的距離(根據所經過的時間長短)接下利檢查是否已經移動到了邊界,如果是的話則完成移動。但是,這個方法是不會自己調用自己的,還需要更新OnFrameUpdate方法,加入以下代碼:
car.Update(elapsedTime);
(注:如果你是按著教程一步一步來,沒有偷看最后源碼的話,會發現此時賽車根本不會移動,郁悶吧,呵呵,原因是根本沒有啟動計時器。在初始化圖形設備的InitializeGraphics()方法中加上如下代碼吧 Utility.Timer(DirectXTimer.Start); )
添加障礙物
恭喜,這就是你創建的第一個3D互動程序了。已經完成了模擬賽車的移動。雖然實際上是賽道在移動,但顯示出的效果確實是賽車在移動。至此,游戲已經完成大半。接下來是添加障礙物的時候了。和添加Car類一樣,添加一個名為Obstacle的類。
我們將使用不同顏色形狀的mesh作為障礙物。通過mesh類創建stock對象可以改變mesh的類型,同時,使用材質來改變障礙物的顏色。添加如下的變量和常量:
{詳見源碼}
第一個常量表示將會有5種不同類型的mesh(球體、立方體、圓環、圓柱以及茶壺)。其中大多數的物體都有一個長度或半徑的參數。我們希望所有障礙物都有同樣的尺寸,所以應該把這些參數都設置為常量。很多種mesh類型都有一個而外的參數可以控制mesh中的三角形數量(stacks,slices,rings等等)。最后一個常量就是用來控制這些參數的。可以增大或減小這個參數來控制mesh的細節。
接下來的color數組用來控制mesh的顏色。我只是隨即的選擇了一些顏色而已,也可以把它們改為任何你喜歡的顏色。應該注意到這個類里既沒有任何的材質數組,也沒有紋理數組。你應該知道默認的mesh類型只包含了一個沒有材質和紋理的子集,因此,額外的信息是不需要的。
由于障礙物需要放置在路面之上,并且實際上是路在移動,所以必須保證它們是和路面同時移動的。需要position屬性來保證在路面移動時障礙物會同時更新。最后由于在創建茶壺時不能控制它的大小,需要檢查創建的是否為茶壺,平且對它進行相應的縮放。為Obstacle類添加如下構造函數:
注意到這里我們使用了來自utility的Rnd屬性。它的具體實現非常簡單位于utitity.cs文件中,只是用來返回一個隨即的時間而已。Obstacle默認的構造函數保存了障礙物的默認位置,而且默認的為一個“非茶壺的”mesh。接下來選擇創建某個類型的mesh。最后,選擇一個隨機的顏色作為材質顏色。
在把障礙物添加到游戲引擎之前,還有一些額外的工作需要完成。首先,添加一個方法來和賽道同步更新障礙物的位置。,添加如下代碼:
public void Update(float elapsedTime,float speed)
再一次使用elapsed time作為參數來保證程序在任何系統都能正常工作。同時,把當前賽道的速度也作為參數,這樣物體就好像是“放置”在賽道上一樣。接下來,還需要一個方法渲染障礙物:
public void Draw(Device device)
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -