24h購物| | PChome| 登入
2006-01-04 21:20:05| 人氣37| 回應0 | 上一篇

游戲設計理論9:十分鐘學會COM技術

推薦 0 收藏 0 轉貼0 訂閱站台

十分鐘學會DirectX中的COM技術

  DirectX是按照微軟的COM(ComponentObjectModel)搭起來的。設計COM是希望它能提供一個更安全,易升級,可移植的軟件模塊。COM用的面向對象的模式比一般的C++更嚴格。例如,COM只能永遠通過成員函數(memberfunction)進行訪問,并且不能擁有公用數據成員(publicdatamembers)
  
  COM異常嚴格地對待對象(object)和接口(interface).而且就不讓你直接訪問對象,COM竟然都不給對象起名字,讓你老老實實通過接口來訪問對象。在DirectDraw編程中,我們講:“訪問對象”,其實都是在訪問接口。叫慣了也就不改了。

  所有的COM接口都從IUnknown接口中派生出來。“I”打頭是COM接口的標識(IforInterface).所有的DirectDraw接口都以“I”打頭,但莫明其妙的是在很多編程手冊中都把“I”略去。所以看書時要做到心中有愛。
  
  IUnknown接口提供三個成員函數,其余所有的COM接口,都繼承這三個函數。
  AddRef()
  Release()
  QueryInterface()

  AddRef()和Release()支持COM的一個特色功能,名字叫“活著封裝”(lifetimeencapsulation).“活著封裝”是一個協議,用來讓對象在崩潰時(destruction)自己負責自己的后事。

  “活著的時候”(lifetime)每個對象內部有一個值用來跟蹤記載自己用過的指針(pointer),或引用(reference)。當這個對象建立時,該值為1。然后隨著對它的調用/被調用,該值遞增。反之遞減。當本身崩潰時(destroyitself),該值為0。

  AddRef()就是用來加計數器的。你可能不用親自調用它。你在用DirectDrawAPI時,AddRef()就被自動調用了。

  Release()則是對著干。減值。你常常要親自用到它,因為程序可能會異常退出AddRef的作用域(scope)。如出錯控制中。

  QueryInterface()用來問COM一個接口是否可用。如可用,則返回一個相應接口的指針。

  問對象是否支持一個接口用QueryInterface,那么怎么問?當然要知道接口的ID。我們用GUID來表示,GUID=GloballyUniqueIDentifier.(全局單一證認)。GUID是一個128bits的數。所有DirectX中接口的GUID值都可在DirectX的頭文件中找到。

  2、windows游戲設計時的單任務與多任務處理
  
  單任務處理
  
  Windows最杰出的功能之一是能夠同時運行多個程序,但有時也會讓人感到頭疼,特別是對于那些習慣于完全控制計算機甚至時鐘頻率、非常自信的游戲程序員(當然,我們的確在乎那些沒禮貌的、在退出時不恢復正確的系統時間的游戲。但是幸好,現在我們可以忘掉這些了)。
  ----在多任務環境下,游戲程序員需要注意三個大的負效應:

  當游戲失去焦點而進入后臺后,其執行不得不被掛起(可以在MobyDickWindows中使用“中止的”變量觀察它是如何工作的)。如果是一個實時游戲,程序員當然希望它被懸掛。但在回合制游戲中,當玩家去做其它事情時,程序員可能不希望計算機一方作任何動作,但希望后臺的人工智能(AI)運算依舊執行。

  其它的任務占用CPU時間,結果造成我們不能一直控制游戲中事情發生時的速度。我們將在后面討論這個痛苦的問題。

  每當游戲回到前臺,程序員不得不重畫游戲窗口。Windows并不負責記憶它所覆蓋或隱藏的窗口的內容;它所能做的最多是通知一個窗口需要重畫其客戶區域。這在有關Windows的文章中都有論述(參見WM_PAINT的內容),我們在這里就不討論了。事實上,MobyDickWindows并不恢復其自己的窗口;我們將在講到DirectDraw下的雙緩沖時看它是如何實現的。


  程序中的多任務

  ----盡管MobyDickDOS在使用中斷處理程序時展示了內部多任務(或者說多線程)的一種原始形式,但是該程序仍然沒有突破DOS的單主題特性,即在一個時間只做一件事情。有些DOS程序的確作到了真正的多線程,但是那需要非常巨大的編程工作。Windows95SDK使這項工作簡單了許多,把線程放進每一個游戲開發者的“錦囊”之中(如果讀者還不熟悉這個概念,那么簡單說明一下,一個線程就是程序的一部分,它執行時獨立于其它的部分,并且不需要與其它部分同步。線程不是由中斷來驅動的;它們只是在每一次Windows給它們CPU時間時繼續其執行。)

  ----在下列情況下,可能要考慮實現獨立的線程:

  允許后臺AI。就算是用戶正忙于來回移動(movingpiecesaround)、打開對話框等事情,計算機也能夠考慮其下一步。處理這類線程非常方便,因為它不需要與其它的事情同步。

  預先加載數據。例如,當玩家正努力向下一級奮斗時,使一個線程負責讀取文件并準備好游戲“世界”。

  給予時間緊迫(time-critical)的任務優先權。我們將在后面會到這個主題。

  游戲循環

  ----游戲循環的概念在各編程環境下都比較相似。第一步,需要獲取輸入:可以是輪詢它,等待它,或者在它“運行”時通過中斷或一個消息隊列攔截它。第二步,處理該輸入,并且把它變成一個在游戲中有實際意義的動作,如:使飛機傾斜飛行或小卒向前走一步。然后,把結果顯示出來。當然,在這個主題中也要求精雕細刻和“變奏曲”,包括計算AI的移動、把控制權從一個玩家移交給另一個玩家、檢查勝負等等。

  ----然而,在Windows和DOS下實現循環的機制迥然不同。每個Windows程序都建立于一個消息循環之上。盡管一個游戲循環可以建立在消息循環之上,但是這兩者仍有本質的差別。

  MobyDickDOS的循環

  ----MobyDickDOS演示了一種簡單的游戲循環,在這里我們所做的工作是:檢查是否有什么東西要移動,移動它,顯示結果。

  while(!gamedone)

  //調用時間程序-如果時間未到,則沒有任何響應。

  AhabMoved=Move_Ahab();

  //僅當Ahab沒有移動時移動MobyDick。

  //否則它們可能擦肩而過卻不能攔截。

  if(!AhabMoved)Move_Moby();

  //如果有任何一個移動,更新屏幕,

  //并檢查是否有勝利和失敗。

  if((MobyX!=OldMobyX)||(MobyY!=OldMobyY)

  ||(AhabMoved))
  {
  UpdateScreen();

  if((MobyX==AhabX)&&(MobyY==AhabY)


  &&(painted[MobyX][MobyY]))
  {
  gamedone=1;

  cprintf("a");

  cprintf("Youwin!");

  }


  if(TimesUp<=0){cprintf("a");cprintf("Time’sup!");gamedone="1;"}if(raw_key="="MAKE_ESC){gamedone="1;"progdone="1;"}}//結束更新}//結束游戲內部循環(while!gamedone)MobyDickWindows的循環從表面看來,好像沒有多大的差別:do{if(PeekMessage(&msg,NULL,0,0,PM_REMOVE)){if(msg.message="="WM_QUIT)break;//唯一的退出循環的出口。TranslateMessage(&msg);DispatchMessage(&msg);}else{if((MobyX!="OldMobyX)"||(MobyY!="OldMobyY)"||(AhabMoved)){UpdateScreen();if((AhabX="="MobyX)&&(AhabY="="MobyY)&&(painted[AhabY][AhabX])){Control="MessageBoxEx(hwnd,""YoucaughtMoby!Playagain?","CallMeIshmael",MB_ICONQUESTION|MB_YESNO,0);if(Control="="IDYES)InitializeGame();elsebreak;}}//如果有人移動了}//如果屏幕已更新}//結束循環while(TRUE);

  ----前面已經提到過,這里沒有檢查是否運行超時,請忽略它,筆者在Windows版中未實現它是為了避免令人煩惱的中斷。中斷并退出無限循環的機制有一點兒而且并不重要。我們把精力集中在消息循環本身,所以把其它的無關代碼都刪掉,只留下最基本的部分:

  do

  {

  if(PeekMessage(&msg,NULL,0,0,PM_REMOVE))

  {

  if(msg.message==WM_QUIT)break;

  TranslateMessage(&msg);

  DispatchMessage(&msg);

  }

  elseDoSomething();

  }

  while(TRUE)

  ----這是一個非常典型的消息循環。唯一有點特殊的地方就是它使用的是PeekMessage而不是GetMessage。

  GetMessage與PeekMessage的比較

  ----為什么要用PeekMessage呢?原因很簡單,GetMessage等待一個消息(就像_getch),而PeekMessage不是這樣(就像_kbhit)。請考慮下面的循環:

  while(GetMessage(&msg,NULL,0,0))

  {
  //我們并不進入括號內部,直到有一個消息

  TranslateMessage(&msg);

  DispatchMessage(&msg);

  DoSomething()

  }

  //當GetMessage返回NULL時,退出該程序

  returnmsg.wParam;

  ----在這里,DoSomething不會完成,除非一個消息--或許多消息--被放入隊列中并被處理。如果DoSomething恰好產生一個消息,例如,如果它更新了屏幕并且因此而產生了一個WM_PAINT消息,那么好了,水泵注水后將開始啟動了。要使DoSomething可靠地完成其工作,這并不是一個好方法,它使代碼有點混淆,但它工作的還不錯。


  ----相比之下,PeekMessage則無論是否有消息在等待,只要檢查一下消息隊列就完成其操作(yieldsthefloor)。在我們的例子中,我們實際上是使用PeekMessage來處理消息的(通過分發它所找到的每一個消息并使用PM_REMOVE參數從隊列中清除它)。同下面同樣有效的代碼相比,它要更加直接:

  if(PeekMessage(&msg,NULL,0,0,PM_NOREMOVE))

  {

  if(!GetMessage(&msg,NULL,0,0))break;

  TranslateMessage(&msg);

  DispatchMessage(&msg);

  }

  elseDoSomething();

  ----在這里有非常重要的一點要說明,我們的偽代碼DoSomething是獨立于消息的;無論隊列中送出的什么消息,甚至無論有沒有消息在那兒,它都將執行。在MobyDick中,我們將屏幕更新和勝利條件的檢查放在這里,因為在這里檢查一個或多個消息被響應后是否需要更新屏幕或是否達到勝利條件很方便。

  ----那么,消息循環就是游戲循環嗎?從抽象的角度,是的,因為它是大的齒輪,帶動那些小的齒輪。但是,盡管把一些函數調用放在此處可能比較方便,Windows編程規則卻要求任何響應一個消息的動作都應該放在消息響應程序中(就是說,放在窗口過程中)。在一個實時游戲中,絕大多數的動作發生在一個或多個WM_TIMER消息響應程序中。回合制游戲則常常在輸入消息的響應函數中做大量的工作。

  ----事實上,程序員經常會發現在主消息循環內,除了標準的翻譯和分發消息任務之外什么事情也沒做。如果這樣,就可以回過頭來使用GetMessage,因為除了響應消息之外,什么事也不需要發生。

  ----總結一下,實現循環有兩個關鍵點:

  Windows消息循環與游戲循環不相同。游戲循環依然存在(至少在概念上如此),但是同DOS下的情況相比,它的部件在代碼中更加分散。

  如果想要在消息循環內執行任何獨立于時鐘消息和輸入消息的代碼,請使用PeekMessage而不是GetMessage。

台長: Kato
人氣(37) | 回應(0)| 推薦 (0)| 收藏 (0)| 轉寄
全站分類: 電玩動漫(電玩、動畫、漫畫、同人)

是 (若未登入"個人新聞台帳號"則看不到回覆唷!)
* 請輸入識別碼:
請輸入圖片中算式的結果(可能為0) 
(有*為必填)
TOP
詳全文