遊戲消息處理

事件機制在很多高級程序設計語言中都有支持。譬如VB、C#(delegate)、C++Builder(並不屬於C++的範疇。C++Builder中的事件處理器必須用關鍵字closure<閉包>修飾)等等,甚至在HTML中也可以見到它的身影。事件機制的引入使軟件系統變得更加易於理解——它使一種語言(平臺)更加接近於這個世界的真相。事情的發展變得像現實世界中那樣順理成章。某一事件的產生引發了一系列其他事件的產生,這些事件要麼是結果要麼又會引發一系列事件的產生……如此這般,信息才得以在事件的新陳代謝中延續,世界才得以向前發展。在某些遊戲設計過程中的一項重要任務就是模擬現實世界的某些特徵,以期實現機器與用戶的更加親密的溝通。事件機制就是很好的一例。我們需要事件來使我們的系統更加人性化。

  我想,在我繼續進行下面對討論之前,先簡單介紹一下“事件”這個東東。


1. 遊戲中的事件機制

  聯繫是普遍存在的。事事有聯繫、時時有聯繫,整個世界是一個相互聯繫的統一整體。一個人的行爲、物的狀態的改變或事的進展過程的某一階段可以引發一個事件。一個事件的發生或許會引發另外的事件——通過人的感知、大腦的反映,然後作出決策,付諸行動——也或許,就這麼蒸發掉,無人知曉。但無論如何,在這一過程中,我們總能抽象出一些實質性的東西來。就像下面的圖示:
 
在遊戲中:

  事件源——表示任何可以引發事件的對象。譬如,一個“人”、“坦克”、“建築物”、“地面”。

  事件——表示任何可以處理的事件。譬如,“感冒”、“射擊”、“倒塌”、“有對象經過”。

  響應者——表示任何對某事件感興趣的對象。

  響應器——表示對某事件感興趣的對象對某一確定事件作出的反應。

特別的,對於過程:

  通知——發生在事件與響應者之間。我們把它分爲兩種方式:有限聽衆式廣播式。對事件感興趣的對象(響應者)只有確定的有限個(只有一個的情況下,可以叫做點對點式)的情況就是有限聽衆式。而對於廣播式,事件並不知道會有哪些(個)對象對自己感興趣。它向所有可以接收事件通知的對象廣播事件。

  觸發——響應者發現自己對特定事件需要做出相應的行動時就會觸發事件處理器,並同時傳遞需要的事件信息給它。對於響應者,它也可以選擇沉默——自己瞭解事件但並不作出行動。因此這個過程的決定權在響應者手上。


2. 萬事之鼻祖 Event

  我們需要一個類來表示所有事件的普遍性質。
public class Event {
     // 屬性
     public string Name { get;set; }// 獲取或設置事件的名稱
     public string Message { get;set; }// 獲取或設置事件的簡單描述
     EventTypes EventType { get;set; }// 獲取或設置事件類型(枚舉EventTypes)
     ListenerCollection Listeners { get; } // 獲取響應者的集合
     public bool PoolEvent { get;set; }// 獲取或設置事件的簡單描述
 
     // 方法
     void RaiseEvent(); // 通知響應者事件的發生
     void AbandonListener( int index ); // 拋棄一個事件響應者,並把它從 Listeners 中移除。
     void AbandonListener(); // 拋棄所有的事件響應者
}

3. 枚舉類型 EventTypes

  這個枚舉類型指示事件通知過程的類型:有限聽衆式、廣播式。
public enum EventTypes {
     LimitedListener ,
     Broadcast
}

4. 響應者接口 IListener

  該接口只有唯一的方法 EventArrived() 。事件發生時會調用這個方法並傳遞相關參數。這個參數必須是 EventArgs 或由它派生而來。
public interface IListener {
     // 通知一個響應者事件的到達。
     void EventArrived( EventArgs args );
}

5. EventPool

  一個事件池。當且僅當需要事件廣播時我們才需要它。需要注意的是 AddEvent 方法。它把一個事件添加到池中,第二個參數指定是否將該事件已經指定的響應者亦添加到廣播的響應者中。事件添加後,其 Event::EventType 屬性會被設置爲 EventTypes.Broadcast。
public class EventPool {
     // 屬性
     public ArrayList Events { get; }// 獲取池中所有的事件的集合
     public ListnerCollection Listners { get; }// 獲取池中所有的響應者的集合
 
     // 方法
     void AddEvent( Event obj ,bool copyListners ); // 添加一個事件並把它作爲廣播式事件
     void RemoveEventAt( int index ); // 將一個事件從列表中移除
     void RemoveEvent( Event listener ); // 將一個事件從列表中移除
     void Broadcast( Event event ); // 向列表中的所有響應者廣播指定事件(可以是非池中的事件)
     void BroadcastItemAt( int index ); // 向列表中的所有響應者廣播池中的指定事件
}

6. EventArgs
public class EventArgs {
     public Event Event { get; } // 獲取傳遞這個參數的事件
     public object Sender { get; } // 獲取事件源
}

7. UML Diagram

8. 響應者行爲

  響應者實現 IListener 接口後就可以響應事件了。在 EventArrived() 方法中,你可以直接處理事件,抑或是調用其它的事件處理器(響應器)。C#中有很好的解決方案——委託——替代函數指針的最有效的方法。在C++中也可以用虛擬函數表來模擬委託機制。總之,在響應器上的解決方案是很靈活的。在實際開發中,可以根據不同的環境做出不同的選擇。


9. 擴展機制

  在一個遊戲中,除了已經定義好的事件外,其劇情或功能可能會要求玩家自行定義一些事件。這就需要一種可擴展的方案。我們引入了 CustomEvent 類——繼承自 Event,以及 Condition 類。
public class CustomEvent : Event {
     public CustomEvent( Condition condition ) {
          _Condition = condition;
     }
 
     public Condition TestCondition { get{ return _Condition; } }
 
     Condition _Condition = null;
}
 
public abstract class Condition {
     public Condition() {}
     bool abstract Test();
}
  初始化一個 CustomEvent 類時必須同時傳入一個 Condition 類。Condition 類必須被繼承。Test()方法在適當的時候被調用以檢測是否可以引發這個事件。


10. 後記

  以上談到的只是一個簡單的模型,是否實用還要等待實踐的檢驗。歡迎讀者的批評與建議。
   
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章