Visual C#編寫3D游戲框架示例

發表于:2007-05-25來源:作者:點擊數: 標簽:框架你可visual示例編寫
你可能對實際地編寫 游戲 代碼期待已久了。由于DirectX SDK 2004年夏季更新包含了一個牢固的示例框架組件,并且它被設計成能在你自己的代碼中直接使用,同時還為你處理了很多事務,所以你只要簡單的使用它,就可以節省大量的時間和精力。 本文中的例子使用的
你可能對實際地編寫游戲代碼期待已久了。由于DirectX SDK 2004年夏季更新包含了一個牢固的示例框架組件,并且它被設計成能在你自己的代碼中直接使用,同時還為你處理了很多事務,所以你只要簡單的使用它,就可以節省大量的時間和精力。

  本文中的例子使用的就是這個示例框架組件,在本文中,你將學習到的內容有:

  · 如何建立自己的項目

  · 如何使用示例框架組件來列舉設備

  建立項目

  在本文中,我假定你的所有開發工作都將使用Visual Studio .NET 2003來完成。如果你不希望使用這個環境,可以使用命令行編譯代碼,它允許你使用任意的文本編輯器或集成開發環境(IDE)。

  啟動Visual Studio .NET 2003并點擊起始頁面中的"新建項目"按鈕。如果你沒有使用起始頁面,可以點擊"文件"菜單下的"新建"子菜單中的"項目"菜單項,或者使用Ctrl+Shift+N。選擇"Visual C#項目"區域中的"Windows項目"數據項。把這個項目命名為Blockers,這是游戲的名稱。

  在你查看自動生成的代碼之前,首先把示例框架組件添加到你的項目中。一般情況下,我會在"解決方案瀏覽器"中建立一個新文件夾,并把這些文件放入一個這個獨立的文件夾中(把這個文件夾的名字取為Framework)。右鍵點擊這個新建的文件夾,從"添加"菜單中選擇"添加已有的項"。導航到DirectX SDK文件夾,你會發現該示例框架文件位于Samples\Managed\Common文件夾中,選擇每個文件并添加到你的項目中。

  在示例框架組件被添加到項目中以后,你就可以去掉自動生成的代碼了。這些代碼中的大部分都是用于建立別致的Windows窗體應用程序的,因此,它與我們編寫游戲的代碼是無關的。用列表1中的代碼替換已有的代碼和類(Form1)。

  列表1:空的框架組件

using System;
using System.Configuration;
using Microsoft.DirectX;
using Microsoft.DirectX.Direct3D;
using Microsoft.Samples.DirectX.UtilityToolkit;

public class GameEngine : IDeviceCreation
{
 ///程序入口。初始化所有部分并進入一個消息處理循環。用空閑時間顯示場景
 static int Main()
 {
  using(Framework sampleFramework = new Framework())
  {
   return sampleFramework.ExitCode;
  }
 }
}

  這段新代碼中有三個地方比較突出。首先,你可能注意到了除了靜態的main方法之外,刪除了所有東西,而且main方法也被修改過。剩余的代碼是Windows窗體設計器的支撐代碼。由于這個應用程序不需要使用該設計器,因此這些代碼就沒有用了,可以被刪除。其次,這段代碼不能編譯,因為游戲引擎希望實現的兩個接口還未實現。再次,這段代碼實際上沒有做任何事務。

  在你開始解決后面兩個問題之前,你必須添加一些引用。由于你準備在這個項目中顯示奇特的3D圖像,你就必須給項目添加能執行這樣的顯示操作的組件的引用。本文采用受控DirectX來執行這種操作,因此你需要在"項目"菜單中選擇"添加引用"。圖1顯示了彈出的對話框。


圖1:添加引用對話框

  如果你安裝了DirectX 9 SDK 2004年夏季更新,你會發現有多個版本的受控DirectX組件可供使用。請選擇最新的版本(1.0.2902.0版本)。對于這個項目來說,你需要添加三個不同的組件引用:

  · Microsoft.DirectX

  · Microsoft.DirectX.Direct3D

  · Microsoft.DirectX.Direct3DX

  DirectX根(root)組件包含了輔助顯示計算的數學結構。其它兩個組件相應地包含了Direct3D和D3DX的功能。添加這些引用之后,你可以簡單地查看一下列表1中添加的using語句,以確保名字空間被正確地引用了。這個步驟可以確保你不需要完整地限定類型。例如,如果不添加using語句,那么聲明一個Direct3D設備變量,就必須使用下面的語句:

Microsoft.DirectX.Direct3D.Device device = null;

  Using語句可以減少很多輸入內容(沒有人希望在聲明一個變量時輸入全部的內容)。由于你已經增加了using語句,就可以使用如下所示的聲明語句了:

private Device device = null;

  你可以看到,用這種方式聲明變量簡單多了,節省了大量的輸入。在了解這些信息之后,你可以開始修補應用程序編譯過程中的錯誤了,并準備好編寫第一個3D游戲了。你現在必須實現的唯一一個接口是IDeviceCreation,它控制著設備的列舉和建立。

  你可能會想"列舉設備做什么?我只有一個監視器"!盡管一般情況下是這樣的,但是現在的顯卡實際上是支持多個監視器的,即使你只有一個設備,你仍然擁有多個可選擇的模式。顯示器的格式可能不同(你可以在Windows桌面設置中看到這些種類,例如16位色和32位色)。全屏幕模式下的高度和寬度也可能有不同的值,你甚至于還可以控制屏幕的刷新率??偠灾?,還是有些事情需要解決。

  列表2中的代碼修補的應用程序中的編譯錯誤。

  列表2:實現接口

/// 設備初始化的時候調用。這段代碼檢查設備最小的性能,
/// 如果沒有通過檢查,就返回false
public bool IsDeviceAclearcase/" target="_blank" >cceptable(Caps caps, Format adapterFormat,
Format backBufferFormat, bool windowed)
{
 if (!Manager.CheckDeviceFormat(caps.AdapterOrdinal, caps.DeviceType,
   adapterFormat, Usage.QueryPostPixelShaderBlending,
   ResourceType.Textures, backBufferFormat))
  return false;

 if (caps.MaxActiveLights == 0)
  return false;

 return true;
}

/// 在建立某個設備之前,這個回調函數會被立即調用,以允許應用程序修改設備的
/// 設置信息。它提供的設置參數包含了框架組件為新設備挑選的設置, 并且應用程序
/// 可以直接對這個結構進行任何需要的修改。請注意,示例框架沒有糾正無效的
/// 設備設置信息,因此必須小心地返回有效的設備設置,否則建立設備就會失敗。
public void ModifyDeviceSettings(DeviceSettings settings, Caps caps)
{
 // 這個應用程序沒有使用任何get方法,它被設計成在一個純設備上工作。
 // 因此如果受到支持并且使用HWVP,就建立一個純設備。
 if ( (caps.DeviceCaps.SupportsPureDevice) && ((settings.BehaviorFlags & CreateFlags.HardwareVertexProcessing) != 0 ) )
  settings.BehaviorFlags |= CreateFlags.PureDevice;
}

  請你查看一下聲明的IsDeviceAcceptable方法。在示例框架忙著枚舉系統中的設備的時候,它會在每個找到的組合上調用該方法。注意到該方法返回一個布爾型值嗎?這使得你有權利告訴示例框架你認為某個設備是否符合需求。但是,在你仔細查看第一個方法中的代碼的時候,請先看一下聲明的第二個方法(ModifyDeviceSettings)。某個設備被建立之前,示例框架會立即調用這個方法,允許你加入任何希望的選項。但是你要小心地使用參數選項,因為它可能導致設備建立失敗。

  現在我們回到第一個方法:我們先看一下它的參數。首先,它帶有一個Caps類型的參數,它是設備性能的簡單結構體。該結構體包含了具體設備的巨量信息,可以幫助你決定某個類型是不是你正在使用的設備類型。其后的兩個參數都是特定設備的格式參數:一個是后臺的緩沖區格式,另一個是設備的格式。

  請注意

  后臺緩沖區是實際要顯示的數據(象素)在發送給顯卡處理并輸出到屏幕之前所存儲的地方。后臺緩沖區的格式決定了可以顯示多少種色彩。大多數格式遵循特定的命名習慣--每個字符跟著一個數字,例如A8R8G8B8。字符所指定的構成部分擁有與其后面的數字相同數量的位(bit)。在A8R8G8B8中,該格式可以包含32位色彩信息,alpha、red、green和blue各用8位。最常見的構成是:

  A Alpha
  R Red
  G Green
  B Blue
  X Unused

  你可以查看DirectX SDK文檔得到更多關于格式的信息。由于我們還需要知道該設備時候可以顯示在窗體中,所以這個方法還有一個參數(最后一個)。盡管大多數游戲都運行在全屏模式下,但是編寫和調試在全屏模式下運行的游戲卻很困難。在調試過程中,這個應用程序在窗體模式而不是全屏模式下顯示。

  請注意

  窗體模式是我們運行的大多數應用程序的顯示方式。其中大多數應用程序帶有邊框和控制菜單,右上角帶有最小化、最大化和關閉按鈕。在全屏模式下,應用程序覆蓋了整個屏幕,并且在大多數情況下沒有邊框。如果全屏模式使用了另外的屏幕大?。惝斍笆褂玫淖烂妫?,你可以改變桌面的分辨率。

  你可能注意到了默認行為是接受該設備,但是在接受之前進行了兩項檢查。第一項檢查確保了傳遞進來的設備可以進行alpha混合(游戲的用戶界面需要這個),如果它不能夠實現,就返回false表明這個設備不能被接受。接著,它檢查是否支持活動光源(active light)的能力。沒有光源的屏幕看起來是平面的、是假的,因此一般至少需要一個光源。

  還有一些代碼在設備建立之前的確修改了該設備,你可能想知道這段代碼的作用。能夠執行處理過程的設備需要用多種方法來顯示頂點,要么在硬件中計算或軟件中計算,或者兩者都使用。如果處理過程完全在硬件中進行,這就是另外一種模式,就叫做"純硬件設備",它潛在地提供了更高的性能。這段代碼檢查你當前是否要建立一個硬件處理設備,如果你正準備這樣做,并且該純設備是可以使用的,那么它就切換到這種模式中。你不能建立純設備(如果該設備是可用的)的唯一情形是你計劃調用該設備上的某個get方法或屬性。由于在這個例子中你不需要這樣操作,所以你可以自由地使用能力更加強大的設備。

  示例框架中有一些不安全(unsafe)的代碼,因此你需要更新自己的項目并處理這些問題。見圖2。


圖2:允許不安全的代碼



  列舉所有設備選項

  現在你可以讓框架組件開始列舉系統中的設備了。首先,為游戲引擎類聲明一個構造函數,并把main中建立的示例框架實例作為參數傳遞進去。如列表3所示。

  列表3:添加構造函數

private Framework sampleFramework = null; // 示例的框架組件
/// 建立該類的一個新的實例
public GameEngine(Framework f)
{
 // 存儲框架組件
 sampleFramework = f;
}

  該構造函數除了存儲示例框架實例之外沒有做其它的任何操作,這是因為這個實例是游戲中其它的一切東西幾乎都需要使用的。在你調用示例框架之后,它所做的第一件事情是試圖列舉系統中的所有設備。在你的項目文件中,你在Framework文件夾中可以看到dxmutenum.cs文件。這個文件包含了列舉系統中所有設備所需要的全部代碼。由于理解如何列舉和為什么列舉設備是非常重要的,所以請你打開這個文件。
你首先應該注意到Enumeration類自身是不能被創建的,并且每個可用的成員和方法都是用static(靜態的)關鍵字聲明的。由于在正常情況下(最少是現在)應用程序在運行的時候,你的圖形硬件是不會改變的,因此這些列舉代碼只需要在應用程序開頭運行一次。

  列舉工作是從Enumerate方法開始的,該方法在設備建立之前被示例框架調用。請注意,這個方法的唯一參數是你自己在游戲引擎類中所實現的接口。這個接口被保存下來,因為隨后,隨著設備組合的列舉,會調用IsDeviceAcceptable方法來決定某個設備是否應該添加到有效設備列表中。

  那么設備到底是怎樣列舉出來的呢?這些功能都位于受控DirectX的Manager類中。如果你非常熟悉非受控的DirectX應用程序編程接口(API),那么我告訴你,這個類映射了IDirect3D9組件對象模型(COM)接口。請留意列表4中的Enumerate方法中的第一個循環。

  列表4:列舉設備

// 查找系統中的每個適配器
for each(AdapterInformation ai in Manager.Adapters)
{
 EnumAdapterInformation adapterInfo = new EnumAdapterInformation();
 // 存儲一些信息
 adapterInfo.AdapterOrdinal = (uint)ai.Adapter; // 序號
 adapterInfo.AdapterInformation = ai.Information; // 信息

 // 獲取這個適配器上的所有顯示模式
 // 建立一個所有顯示適配器格式的臨時列表
 adapterFormatList.Clear();

 // 現在檢測支持哪種格式
 for(int i = 0; i < allowedFormats.Length; i++)
 {
  // 檢查這種格式的每一種可支持的顯示模式
  for each(DisplayMode dm in ai.SupportedDisplayModes[allowedFormats[i]])
  {
   if ( (dm.Width < minimumWidth) ||
    (dm.Height < minimumHeight) ||
    (dm.Width > maximumWidth) ||
    (dm.Height > maximumHeight) ||
    (dm.RefreshRate < minimumRefresh) ||
    (dm.RefreshRate > maximumRefresh) )
   {
    continue; // 這種格式是無效的
   }

   // 添加到列表中
   adapterInfo.displayModeList.Add(dm);

   // 如果先前并不存在就把它添加到格式列表中
   if (!adapterFormatList.Contains(dm.Format))
   {
    adapterFormatList.Add(dm.Format);
   }
  }
 }

 // 獲取適配器顯示模式
 DisplayMode currentAdapterMode = ai.CurrentDisplayMode;
 // 檢查這種格式是否在列表中
 if (!adapterFormatList.Contains(currentAdapterMode.Format))
 {
  adapterFormatList.Add(currentAdapterMode.Format);
 }

 // 對顯示模式列表進行排序
 adapterInfo.displayModeList.Sort(sorter);

 // 獲取這個適配器上每個設備的信息
 EnumerateDevices(adapterInfo, adapterFormatList);

 // 如果適配器上至少有一個設備,并且它是兼容的,就把它添加到列表中
 if (adapterInfo.deviceInfoList.Count > 0)
 {
  adapterInformationList.Add(adapterInfo);
 }
}

  Manager類的Adapters(適配器)屬性是一個包含了系統中所有"適配器"信息的集合。"適配器"這個術語可能有點不恰當,但是它的基本定義是指任何監視器可以連接到的東西。例如,假設你有一塊ATI Radeon 9800 XT顯卡。雖然只有一塊顯卡,但是可能把兩個不同的監視器連接到它上面(通過視頻圖形適配器[VGA]端口和后面的數字視覺接口[DVI]端口)。當用這兩種監視器的時候,這塊顯卡就有兩個適配器,因此是兩種設備。
 
  請注意

  這是一種通過把設備創建為適配器組的方式在"不同的"設備之間共享資源的方法。這種方法受到了少許限制。你可以查閱DirectX文檔了解更多的信息。

  這個循環至少會迭代一次,這依賴于你的系統。在把當前活動的適配器的基本信息存儲起來以后,代碼必須找到在全屏模式下這個適配器可以支持的所有顯示模式。你可能發現了受到支持的模式都可以直接從當前正在列舉的適配器信息中直接列舉出來,代碼也是這樣做的。
列舉某個適配器模式的時候,第一步是檢查最小和最大的范圍集合。大多數設備支持很多模式,但是其中很多我們現在不會使用了。很多年前,你可能見過在320x200全屏窗口中運行游戲,但是現在不會發生這種情況(除非你正好在玩手持式游戲,例如Gameboy Advance)。示例框架選擇的最小的大小為640x480窗體,沒有設置最大的尺寸。

  請注意

  示例框架選擇的最小尺寸為640x480并不意味著在全屏模式下它就會選擇最小的尺寸。在全屏模式下,示例框架選擇最好的可用尺寸,它一般是當前桌面的大?。ㄍǔ2皇?40x480的)。


  在符合框架組件需求的受到支持的模式被添加到列表中后,當前的顯示模式就會被添加進來,因為這個模式肯定受到支持。最后,通過實現IComparer接口,這些模式會被排序。見列表5。

  列表5:對顯示模式進行排序

public class DisplayModeSorter : IComparer
{
 /// 比較兩種顯示模式
 public int Compare(object x, object y)
 {
  DisplayMode d1 = (DisplayMode)x;
  DisplayMode d2 = (DisplayMode)y;

  if (d1.Width > d2.Width)
   return +1;
  if (d1.Width < d2.Width)
   return -1;
  if (d1.Height > d2.Height)
   return +1;
  if (d1.Height < d2.Height)
   return -1;
  if (d1.Format > d2.Format)
   return +1;
  if (d1.Format < d2.Format)
   return -1;
  if (d1.RefreshRate > d2.RefreshRate)
   return +1;
  if (d1.RefreshRate < d2.RefreshRate)
   return -1;

  // 它們一定相同,所以返回0
  return 0;
 }
}

  IComparer接口允許我們在數組或集合上執行簡單的、快速排序算法。這個接口提供的唯一的方法是Compare,它必須返回整型值--也就是如果左邊的數據項大于右邊的就返回+1,如果左邊的數據項小于右邊的就返回-1,如果相等就返回0。你可以看到,在上面的實現中,顯示模式的寬度有最高的優先級,接著是高度、格式和刷新率。這個次序規定了在比較兩種模式(例如1280x1024和1280x768)的時候正確的操作方法。

  這些模式被排序之后,就調用EnumerateDevices方法。列表6顯示了這個方法。

  列表6:列舉設備類型

private static void EnumerateDevices(EnumAdapterInformation adapterInfo,
ArrayList adapterFormatList)
{
 // 在查找設備類型的時候忽略任何異常
 DirectXException.IgnoreExceptions();
 // 列舉每個Direct3D設備類型
 for(uint i = 0; i < deviceTypeArray.Length; i++)
 {
  // 建立一個新設備信息對象
  EnumDeviceInformation deviceInfo = new EnumDeviceInformation();

  // 存儲該類型
  deviceInfo.DeviceType = deviceTypeArray[i];

  // 試圖獲取其性能
  deviceInfo.Caps = Manager.GetDeviceCaps((int)adapterInfo.AdapterOrdinal, deviceInfo.DeviceType);

  // 獲取該設備上每個設備組合的信息
  EnumerateDeviceCombos( adapterInfo, deviceInfo, adapterFormatList);

  // 我們有設備組合嗎?
  if (deviceInfo.deviceSettingsList.Count > 0)
  {
   // 有,把它添加到列表中
   adapterInfo.deviceInfoList.Add(deviceInfo);
  }
 }
 // 打開異常處理開關
 DirectXException.EnableExceptions();
}

  查看這段代碼的時候,你必須注意兩個非常重要的信息。你能猜到是哪兩個嗎?如果你猜的是對DirectXException類的調用那就對了。第一個調用關閉了受控DirectX部件內部任何異常的產生。你可能會懷疑這樣做的優點,實際上這樣做是出于性能的考慮。捕捉和拋出異常是很昂貴的操作,而這段代碼可能產生大量的異常。你可能希望盡快地執行列舉過程,因此過程中產生的任何異常都被簡單地忽略了,在這個 函數執行完之后,就恢復正常的異常處理過程。這段代碼看起來相當簡潔,你可能會問"這段代碼為什么傾向于產生異常呢"?

  我有一個很好的答案:大多數情形是某種設備不支持DirectX 9。也許你沒有升級顯卡驅動程序或當前的顯卡驅動程序所需要的必要代碼路徑不正確;也可能是由于顯卡本身太老了,沒有能力支持DirectX 9;有時候一些人通過包含不支持DirectX 9的PCI顯卡激活了系統中的多監視器模式。

  這個方法中的代碼試圖得到這個適配器的性能信息并列舉出不同的組合方式,并且它試圖獲取每個可用的設備的這些信息??赡艿脑O備類型包括:

  · 硬件(Hardware)--建立的最常見的設備類型。呈現過程由硬件(顯卡)來完成。

  · 引用(Reference)--這種設備不管硬件是否能夠執行處理過程,可以呈現Direct3D運行時支持的任何設置。所有的處理過程在軟件中進行,這意味著在游戲中這種設備類型很慢。

  · 軟件(Software)--除非你編寫了光柵化程序(rasterizer),否則永遠不會使用這個選項。

  假設在列舉過程中找到了某些設備組合,就把它存儲到列表中。列舉類存儲了少量的列表,示例框架在以后可以使用它們。列表7是EnumerateDeviceCombos方法。

  列表7:列舉設備組合

private static void EnumerateDeviceCombos(EnumAdapterInformation adapterInfo,
EnumDeviceInformation deviceInfo, ArrayList adapterFormatList)
{
 // 查找這種設備支持哪種適配器格式
 for each(Format adapterFormat in adapterFormatList)
 {
  for(int i = 0; i < backbufferFormatsArray.Length; i++)
  {
   bool windowed = false;
   do
   {
    if ((!windowed) && (adapterInfo.displayModeList.Count == 0))
     continue;

    if (!Manager.CheckDeviceType((int)adapterInfo.AdapterOrdinal,
        deviceInfo.DeviceType, adapterFormat,
        backbufferFormatsArray[i], windowed))
     continue; // 不支持的

    // 我們需要加速象素陰影混合嗎?
    if (isPostPixelShaderBlendingRequired)
    {
      if (!Manager.CheckDeviceFormat(
         (int)adapterInfo.AdapterOrdinal,
         deviceInfo.DeviceType, adapterFormat,
         Usage.QueryPostPixelShaderBlending,
         ResourceType.Textures, backbufferFormatsArray[i]))
       continue; // 不支持的
    }

    // 如果提供了某個應用程序回調函數,就要確保這個設備受到該應用程序的支持
    if (deviceCreationInterface != null)
    {
     if (!deviceCreationInterface.IsDeviceAcceptable(deviceInfo.Caps,
        adapterFormat, backbufferFormatsArray[i],windowed))
      continue; // 應用程序不喜歡這個設備
    }

    EnumDeviceSettingsCombo deviceCombo = new EnumDeviceSettingsCombo();

    // 存儲信息
    deviceCombo.AdapterOrdinal = adapterInfo.AdapterOrdinal;
    deviceCombo.DeviceType = deviceInfo.DeviceType;
    deviceCombo.AdapterFormat = adapterFormat;
    deviceCombo.BackBufferFormat = backbufferFormatsArray[i];
    deviceCombo.IsWindowed = windowed;

    BuildDepthStencilFormatList(deviceCombo);
    BuildMultiSampleTypeList(deviceCombo);
    if (deviceCombo.multiSampleTypeList.Count == 0)
    {
     continue;
    }
    BuildConflictList(deviceCombo);
    BuildPresentIntervalList(deviceInfo, deviceCombo);

    deviceCombo.adapterInformation = adapterInfo;
    deviceCombo.deviceInformation = deviceInfo;

    // 把組合添加到設備列表中
    deviceInfo.deviceSettingsList.Add(deviceCombo);

    windowed = !windowed;
   }
   while (windowed);
  }
 }
}

  總結

  在本文中,你開始建立了第一個游戲項目,并且看到了示例框架的一些內容。你看到了大量的列舉系統中可能支持的設備組合的代碼。這個示例框架是你在未來編寫游戲的一個重要的出發點。

原文轉自:http://www.anti-gravitydesign.com

評論列表(網友評論僅供網友表達個人看法,并不表明本站同意其觀點或證實其描述)
国产97人人超碰caoprom_尤物国产在线一区手机播放_精品国产一区二区三_色天使久久综合给合久久97