你可能對實際地編寫游戲代碼期待已久了
由於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 System
Configuration;
using Microsoft
DirectX;
using Microsoft
DirectX
Direct
D;
using Microsoft
Samples
DirectX
UtilityToolkit;
public class GameEngine : IDeviceCreation
{
///程序入口
初始化所有部分並進入一個消息處理循環
用空閒時間顯示場景
static int Main()
{
using(Framework sampleFramework = new Framework())
{
return sampleFramework
ExitCode;
}
}
}
這段新代碼中有三個地方比較突出
首先
你可能注意到了除了靜態的main方法之外
刪除了所有東西
而且main方法也被修改過
剩余的代碼是Windows窗體設計器的支撐代碼
由於這個應用程序不需要使用該設計器
因此這些代碼就沒有用了
可以被刪除
其次
這段代碼不能編譯
因為游戲引擎希望實現的兩個接口還未實現
再次
這段代碼實際上沒有做任何事務
在你開始解決後面兩個問題之前
你必須添加一些引用
由於你准備在這個項目中顯示奇特的
D圖像
你就必須給項目添加能執行這樣的顯示操作的組件的引用
本文采用受控DirectX來執行這種操作
因此你需要在
項目
菜單中選擇
添加引用
圖
顯示了彈出的對話框
圖
添加引用對話框
如果你安裝了DirectX
SDK
年夏季更新
你會發現有多個版本的受控DirectX組件可供使用
請選擇最新的版本(
版本)
對於這個項目來說
你需要添加三個不同的組件引用
· Microsoft
DirectX
· Microsoft
DirectX
Direct
D
· Microsoft
DirectX
Direct
DX
DirectX根(root)組件包含了輔助顯示計算的數學結構
其它兩個組件相應地包含了Direct
D和D
DX的功能
添加這些引用之後
你可以簡單地查看一下列表
中添加的using語句
以確保名字空間被正確地引用了
這個步驟可以確保你不需要完整地限定類型
例如
如果不添加using語句
那麼聲明一個Direct
D設備變量
就必須使用下面的語句
Microsoft
DirectX
Direct
D
Device device = null;
Using語句可以減少很多輸入內容(沒有人希望在聲明一個變量時輸入全部的內容)
由於你已經增加了using語句
就可以使用如下所示的聲明語句了
private Device device = null;
你可以看到
用這種方式聲明變量簡單多了
節省了大量的輸入
在了解這些信息之後
你可以開始修補應用程序編譯過程中的錯誤了
並准備好編寫第一個
D游戲了
你現在必須實現的唯一一個接口是IDeviceCreation
它控制著設備的列舉和建立
你可能會想
列舉設備做什麼?我只有一個監視器
!盡管一般情況下是這樣的
但是現在的顯卡實際上是支持多個監視器的
即使你只有一個設備
你仍然擁有多個可選擇的模式
顯示器的格式可能不同(你可以在Windows桌面設置中看到這些種類
例如
位色和
位色)
全屏幕模式下的高度和寬度也可能有不同的值
你甚至於還可以控制屏幕的刷新率
總而言之
還是有些事情需要解決
列表
中的代碼修補的應用程序中的編譯錯誤
列表
實現接口
/// 設備初始化的時候調用
這段代碼檢查設備最小的性能
/// 如果沒有通過檢查
就返回false
public bool IsDeviceAcceptable(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 ==
)
return false;
return true;
}
/// 在建立某個設備之前
這個回調函數會被立即調用
以允許應用程序修改設備的
/// 設置信息
它提供的設置參數包含了框架組件為新設備挑選的設置
並且應用程序
/// 可以直接對這個結構進行任何需要的修改
請注意
示例框架沒有糾正無效的
/// 設備設置信息
因此必須小心地返回有效的設備設置
否則建立設備就會失敗
public void ModifyDeviceSettings(DeviceSettings settings
Caps caps)
{
// 這個應用程序沒有使用任何get方法
它被設計成在一個純設備上工作
// 因此如果受到支持並且使用HWVP
就建立一個純設備
if ( (caps
DeviceCaps
SupportsPureDevice) && ((settings
BehaviorFlags & CreateFlags
HardwareVertexProcessing) !=
) )
settings
BehaviorFlags |= CreateFlags
PureDevice;
}
請你查看一下聲明的IsDeviceAcceptable方法
在示例框架忙著枚舉系統中的設備的時候
它會在每個找到的組合上調用該方法
注意到該方法返回一個布爾型值嗎?這使得你有權利告訴示例框架你認為某個設備是否符合需求
但是
在你仔細查看第一個方法中的代碼的時候
請先看一下聲明的第二個方法(ModifyDeviceSettings)
某個設備被建立之前
示例框架會立即調用這個方法
允許你加入任何希望的選項
但是你要小心地使用參數選項
因為它可能導致設備建立失敗
現在我們回到第一個方法
我們先看一下它的參數
首先
它帶有一個Caps類型的參數
它是設備性能的簡單結構體
該結構體包含了具體設備的巨量信息
可以幫助你決定某個類型是不是你正在使用的設備類型
其後的兩個參數都是特定設備的格式參數
一個是後台的緩沖區格式
另一個是設備的格式
請注意
後台緩沖區是實際要顯示的數據(象素)在發送給顯卡處理並輸出到屏幕之前所存儲的地方
後台緩沖區的格式決定了可以顯示多少種色彩
大多數格式遵循特定的命名習慣
每個字符跟著一個數字
例如A
R
G
B
字符所指定的構成部分擁有與其後面的數字相同數量的位(bit)
在A
R
G
B
中
該格式可以包含
位色彩信息
alpha
red
green和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文件夾中可以看到dxmutenum
cs文件
這個文件包含了列舉系統中所有設備所需要的全部代碼
由於理解如何列舉和為什麼列舉設備是非常重要的
所以請你打開這個文件
你首先應該注意到Enumeration類自身是不能被創建的
並且每個可用的成員和方法都是用static(靜態的)關鍵字聲明的
由於在正常情況下(最少是現在)應用程序在運行的時候
你的圖形硬件是不會改變的
因此這些列舉代碼只需要在應用程序開頭運行一次
列舉工作是從Enumerate方法開始的
該方法在設備建立之前被示例框架調用
請注意
這個方法的唯一參數是你自己在游戲引擎類中所實現的接口
這個接口被保存下來
因為隨後
隨著設備組合的列舉
會調用IsDeviceAcceptable方法來決定某個設備是否應該添加到有效設備列表中
那麼設備到底是怎樣列舉出來的呢?這些功能都位於受控DirectX的Manager類中
如果你非常熟悉非受控的DirectX應用程序編程接口(API)
那麼我告訴你
這個類映射了IDirect
D
組件對象模型(COM)接口
請留意列表
中的Enumerate方法中的第一個循環
列表
列舉設備
// 查找系統中的每個適配器
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 =
; 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 >
)
{
adapterInformationList
Add(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 (d
Width > d
Width)
return +
;
if (d
Width < d
Width)
return
;
if (d
Height > d
Height)
return +
;
if (d
Height < d
Height)
return
;
if (d
Format > d
Format)
return +
;
if (d
Format < d
Format)
return
;
if (d
RefreshRate > d
RefreshRate)
return +
;
if (d
RefreshRate < d
RefreshRate)
return
;
// 它們一定相同
所以返回
return
;
}
}
IComparer接口允許我們在數組或集合上執行簡單的
快速排序算法
這個接口提供的唯一的方法是Compare
它必須返回整型值
也就是如果左邊的數據項大於右邊的就返回+
如果左邊的數據項小於右邊的就返回
如果相等就返回
你可以看到
在上面的實現中
顯示模式的寬度有最高的優先級
接著是高度
格式和刷新率
這個次序規定了在比較兩種模式(例如
x
和
x
)的時候正確的操作方法
這些模式被排序之後
就調用EnumerateDevices方法
列表
顯示了這個方法
列表
列舉設備類型
private static void EnumerateDevices(EnumAdapterInformation adapterInfo
ArrayList adapterFormatList)
{
// 在查找設備類型的時候忽略任何異常
DirectXException
IgnoreExceptions();
// 列舉每個Direct
D設備類型
for(uint i =
; 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 >
)
{
// 有
把它添加到列表中
adapterInfo
deviceInfoList
Add(deviceInfo);
}
}
// 打開異常處理開關
DirectXException
EnableExceptions();
}
查看這段代碼的時候
你必須注意兩個非常重要的信息
你能猜到是哪兩個嗎?如果你猜的是對DirectXException類的調用那就對了
第一個調用關閉了受控DirectX部件內部任何異常的產生
你可能會懷疑這樣做的優點
實際上這樣做是出於性能的考慮
捕捉和拋出異常是很昂貴的操作
而這段代碼可能產生大量的異常
你可能希望盡快地執行列舉過程
因此過程中產生的任何異常都被簡單地忽略了
在這個 函數執行完之後
就恢復正常的異常處理過程
這段代碼看起來相當簡潔
你可能會問
這段代碼為什麼傾向於產生異常呢
?
我有一個很好的答案
大多數情形是某種設備不支持DirectX
也許你沒有升級顯卡驅動程序或當前的顯卡驅動程序所需要的必要代碼路徑不正確
也可能是由於顯卡本身太老了
沒有能力支持DirectX
有時候一些人通過包含不支持DirectX
的PCI顯卡激活了系統中的多監視器模式
這個方法中的代碼試圖得到這個適配器的性能信息並列舉出不同的組合方式
並且它試圖獲取每個可用的設備的這些信息
可能的設備類型包括
· 硬件(Hardware)
建立的最常見的設備類型
呈現過程由硬件(顯卡)來完成
· 引用(Reference)
這種設備不管硬件是否能夠執行處理過程
可以呈現Direct
D運行時支持的任何設置
所有的處理過程在軟件中進行
這意味著在游戲中這種設備類型很慢
· 軟件(Software)
除非你編寫了光柵化程序(rasterizer)
否則永遠不會使用這個選項
假設在列舉過程中找到了某些設備組合
就把它存儲到列表中
列舉類存儲了少量的列表
示例框架在以後可以使用它們
列表
是EnumerateDeviceCombos方法
列表
列舉設備組合
private static void EnumerateDeviceCombos(EnumAdapterInformation adapterInfo
EnumDeviceInformation deviceInfo
ArrayList adapterFormatList)
{
// 查找這種設備支持哪種適配器格式
for each(Format adapterFormat in adapterFormatList)
{
for(int i =
; i < backbufferFormatsArray
Length; i++)
{
bool windowed = false;
do
{
if ((!windowed) && (adapterInfo
displayModeList
Count ==
))
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 ==
)
{
continue;
}
BuildConflictList(deviceCombo);
BuildPresentIntervalList(deviceInfo
deviceCombo);
deviceCombo
adapterInformation = adapterInfo;
deviceCombo
deviceInformation = deviceInfo;
// 把組合添加到設備列表中
deviceInfo
deviceSettingsList
Add(deviceCombo);
windowed = !windowed;
}
while (windowed);
}
}
}
總結 在本文中
你開始建立了第一個游戲項目
並且看到了示例框架的一些內容
你看到了大量的列舉系統中可能支持的設備組合的代碼
這個示例框架是你在未來編寫游戲的一個重要的出發點
From:http://tw.wingwit.com/Article/program/net/201311/13244.html