熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> .NET編程 >> 正文

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

2013-11-13 10:19:27  來源: .NET編程 
你可能對實際地編寫游戲代碼期待已久了由於DirectX SDK 年夏季更新包含了一個牢固的示例框架組件並且它被設計成能在你自己的代碼中直接使用同時還為你處理了很多事務所以你只要簡單的使用它就可以節省大量的時間和精力

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

  · 如何建立自己的項目

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

 建立項目

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

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

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

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

  列表空的框架組件

using System;
using SystemConfiguration;
using MicrosoftDirectX;
using MicrosoftDirectXDirectD;
using MicrosoftSamplesDirectXUtilityToolkit;

public class GameEngine : IDeviceCreation
{
 ///程序入口初始化所有部分並進入一個消息處理循環用空閒時間顯示場景
 static int Main()
 {
  using(Framework sampleFramework = new Framework())
  {
   return sampleFrameworkExitCode;
  }
 }
}
  這段新代碼中有三個地方比較突出首先你可能注意到了除了靜態的main方法之外刪除了所有東西而且main方法也被修改過剩余的代碼是Windows窗體設計器的支撐代碼由於這個應用程序不需要使用該設計器因此這些代碼就沒有用了可以被刪除其次這段代碼不能編譯因為游戲引擎希望實現的兩個接口還未實現再次這段代碼實際上沒有做任何事務

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



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

  · MicrosoftDirectX

  · MicrosoftDirectXDirectD

  · MicrosoftDirectXDirectDX

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

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

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

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

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

  列表實現接口

/// 設備初始化的時候調用這段代碼檢查設備最小的性能
/// 如果沒有通過檢查就返回false
public bool IsDeviceAcceptable(Caps caps Format adapterFormat
Format backBufferFormat bool windowed)
{
 if (!ManagerCheckDeviceFormat(capsAdapterOrdinal capsDeviceType
   adapterFormat UsageQueryPostPixelShaderBlending
   ResourceTypeTextures backBufferFormat))
  return false;

 if (capsMaxActiveLights == )
  return false;

 return true;
}

/// 在建立某個設備之前這個回調函數會被立即調用以允許應用程序修改設備的
/// 設置信息它提供的設置參數包含了框架組件為新設備挑選的設置 並且應用程序
/// 可以直接對這個結構進行任何需要的修改請注意示例框架沒有糾正無效的
/// 設備設置信息因此必須小心地返回有效的設備設置否則建立設備就會失敗
public void ModifyDeviceSettings(DeviceSettings settings Caps caps)
{
 // 這個應用程序沒有使用任何get方法它被設計成在一個純設備上工作
 // 因此如果受到支持並且使用HWVP就建立一個純設備
 if ( (capsDeviceCapsSupportsPureDevice) && ((settingsBehaviorFlags & CreateFlagsHardwareVertexProcessing) != ) )
  settingsBehaviorFlags |= CreateFlagsPureDevice;
}
  請你查看一下聲明的IsDeviceAcceptable方法在示例框架忙著枚舉系統中的設備的時候它會在每個找到的組合上調用該方法注意到該方法返回一個布爾型值嗎?這使得你有權利告訴示例框架你認為某個設備是否符合需求但是在你仔細查看第一個方法中的代碼的時候請先看一下聲明的第二個方法(ModifyDeviceSettings)某個設備被建立之前示例框架會立即調用這個方法允許你加入任何希望的選項但是你要小心地使用參數選項因為它可能導致設備建立失敗

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

  請注意

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

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

  請注意

  窗體模式是我們運行的大多數應用程序的顯示方式其中大多數應用程序帶有邊框和控制菜單右上角帶有最小化最大化和關閉按鈕在全屏模式下應用程序覆蓋了整個屏幕並且在大多數情況下沒有邊框如果全屏模式使用了另外的屏幕大小(你當前使用的桌面)你可以改變桌面的分辨率
  你可能注意到了默認行為是接受該設備但是在接受之前進行了兩項檢查第一項檢查確保了傳遞進來的設備可以進行alpha混合(游戲的用戶界面需要這個)如果它不能夠實現就返回false表明這個設備不能被接受接著它檢查是否支持活動光源(active light)的能力沒有光源的屏幕看起來是平面的是假的因此一般至少需要一個光源

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

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



允許不安全的代碼  列舉所有設備選項

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

  列表添加構造函數

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

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

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

  列表列舉設備

// 查找系統中的每個適配器
for each(AdapterInformation ai in ManagerAdapters)
{
 EnumAdapterInformation adapterInfo = new EnumAdapterInformation();
 // 存儲一些信息
 adapterInfoAdapterOrdinal = (uint)aiAdapter; // 序號
 adapterInfoAdapterInformation = aiInformation; // 信息

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

 // 現在檢測支持哪種格式
 for(int i = ; i < allowedFormatsLength; i++)
 {
  // 檢查這種格式的每一種可支持的顯示模式
  for each(DisplayMode dm in aiSupportedDisplayModes[allowedFormats[i]])
  {
   if ( (dmWidth < minimumWidth) ||
    (dmHeight < minimumHeight) ||
    (dmWidth > maximumWidth) ||
    (dmHeight > maximumHeight) ||
    (dmRefreshRate < minimumRefresh) ||
    (dmRefreshRate > maximumRefresh) )
   {
    continue; // 這種格式是無效的
   }

   // 添加到列表中
   adapterInfodisplayModeListAdd(dm);

   // 如果先前並不存在就把它添加到格式列表中
   if (!adapterFormatListContains(dmFormat))
   {
    adapterFormatListAdd(dmFormat);
   }
  }
 }

 // 獲取適配器顯示模式
 DisplayMode currentAdapterMode = aiCurrentDisplayMode;
 // 檢查這種格式是否在列表中
 if (!adapterFormatListContains(currentAdapterModeFormat))
 {
  adapterFormatListAdd(currentAdapterModeFormat);
 }

 // 對顯示模式列表進行排序
 adapterInfodisplayModeListSort(sorter);

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

 // 如果適配器上至少有一個設備並且它是兼容的就把它添加到列表中
 if (adapterInfodeviceInfoListCount > )
 {
  adapterInformationListAdd(adapterInfo);
 }
}
  Manager類的Adapters(適配器)屬性是一個包含了系統中所有適配器信息的集合適配器這個術語可能有點不恰當但是它的基本定義是指任何監視器可以連接到的東西例如假設你有一塊ATI Radeon XT顯卡雖然只有一塊顯卡但是可能把兩個不同的監視器連接到它上面(通過視頻圖形適配器[VGA]端口和後面的數字視覺接口[DVI]端口)當用這兩種監視器的時候這塊顯卡就有兩個適配器因此是兩種設備
    請注意

  這是一種通過把設備創建為適配器組的方式在不同的設備之間共享資源的方法這種方法受到了少許限制你可以查閱DirectX文檔了解更多的信息
  這個循環至少會迭代一次這依賴於你的系統在把當前活動的適配器的基本信息存儲起來以後代碼必須找到在全屏模式下這個適配器可以支持的所有顯示模式你可能發現了受到支持的模式都可以直接從當前正在列舉的適配器信息中直接列舉出來代碼也是這樣做的
列舉某個適配器模式的時候第一步是檢查最小和最大的范圍集合大多數設備支持很多模式但是其中很多我們現在不會使用了很多年前你可能見過在x全屏窗口中運行游戲但是現在不會發生這種情況(除非你正好在玩手持式游戲例如Gameboy Advance)示例框架選擇的最小的大小為x窗體沒有設置最大的尺寸

  請注意

  示例框架選擇的最小尺寸為x並不意味著在全屏模式下它就會選擇最小的尺寸在全屏模式下示例框架選擇最好的可用尺寸它一般是當前桌面的大小(通常不是x的)

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

  列表對顯示模式進行排序

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

  if (dWidth > dWidth)
   return +;
  if (dWidth < dWidth)
   return ;
  if (dHeight > dHeight)
   return +;
  if (dHeight < dHeight)
   return ;
  if (dFormat > dFormat)
   return +;
  if (dFormat < dFormat)
   return ;
  if (dRefreshRate > dRefreshRate)
   return +;
  if (dRefreshRate < dRefreshRate)
   return ;

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

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

  列表列舉設備類型

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

  // 存儲該類型
  deviceInfoDeviceType = deviceTypeArray[i];

  // 試圖獲取其性能
  deviceInfoCaps = ManagerGetDeviceCaps((int)adapterInfoAdapterOrdinal deviceInfoDeviceType);

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

  // 我們有設備組合嗎?
  if (deviceInfodeviceSettingsListCount > )
  {
   // 有把它添加到列表中
   adapterInfodeviceInfoListAdd(deviceInfo);
  }
 }
 // 打開異常處理開關
 DirectXExceptionEnableExceptions();
}
  查看這段代碼的時候你必須注意兩個非常重要的信息你能猜到是哪兩個嗎?如果你猜的是對DirectXException類的調用那就對了第一個調用關閉了受控DirectX部件內部任何異常的產生你可能會懷疑這樣做的優點實際上這樣做是出於性能的考慮捕捉和拋出異常是很昂貴的操作而這段代碼可能產生大量的異常你可能希望盡快地執行列舉過程因此過程中產生的任何異常都被簡單地忽略了在這個 函數執行完之後就恢復正常的異常處理過程這段代碼看起來相當簡潔你可能會問這段代碼為什麼傾向於產生異常呢

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

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

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

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

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

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

  列表列舉設備組合

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

    if (!ManagerCheckDeviceType((int)adapterInfoAdapterOrdinal
        deviceInfoDeviceType adapterFormat
        backbufferFormatsArray[i] windowed))
     continue; // 不支持的

    // 我們需要加速象素陰影混合嗎?
    if (isPostPixelShaderBlendingRequired)
    {
      if (!ManagerCheckDeviceFormat(
         (int)adapterInfoAdapterOrdinal
         deviceInfoDeviceType adapterFormat
         UsageQueryPostPixelShaderBlending
         ResourceTypeTextures backbufferFormatsArray[i]))
       continue; // 不支持的
    }

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

    EnumDeviceSettingsCombo deviceCombo = new EnumDeviceSettingsCombo();

    // 存儲信息
    deviceComboAdapterOrdinal = adapterInfoAdapterOrdinal;
    deviceComboDeviceType = deviceInfoDeviceType;
    deviceComboAdapterFormat = adapterFormat;
    deviceComboBackBufferFormat = backbufferFormatsArray[i];
    deviceComboIsWindowed = windowed;

    BuildDepthStencilFormatList(deviceCombo);
    BuildMultiSampleTypeList(deviceCombo);
    if (deviceCombomultiSampleTypeListCount == )
    {
     continue;
    }
    BuildConflictList(deviceCombo);
    BuildPresentIntervalList(deviceInfo deviceCombo);

    deviceComboadapterInformation = adapterInfo;
    deviceCombodeviceInformation = deviceInfo;

    // 把組合添加到設備列表中
    deviceInfodeviceSettingsListAdd(deviceCombo);

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

  在本文中你開始建立了第一個游戲項目並且看到了示例框架的一些內容你看到了大量的列舉系統中可能支持的設備組合的代碼這個示例框架是你在未來編寫游戲的一個重要的出發點
From:http://tw.wingwit.com/Article/program/net/201311/13244.html
    推薦文章
    Copyright © 2005-2013 電腦知識網 Computer Knowledge   All rights reserved.