《游戲編程模式》(6)

来源:http://www.cnblogs.com/pandawuwyj/archive/2017/01/23/6344362.html
-Advertisement-
Play Games

Chapter 14 組件模式 允許一個單一的實體跨越多個不同域而不會導致耦合。 為實現兩個類之間的代碼共用,應該讓他們擁有同一個類的實例,而不是繼承同一個類。 使用情境: 分割不同的域: 1 class InputComponent 2 { 3 4 public: 5 void update(Bj ...


Chapter 14 組件模式

允許一個單一的實體跨越多個不同域而不會導致耦合。

為實現兩個類之間的代碼共用,應該讓他們擁有同一個類的實例,而不是繼承同一個類。

使用情境:

  1. 有一個涉及多個域的類。但希望這些域保持解耦;
  2. 這個類很龐大;
  3. 希望定義許多共用不同能力的對象。

分割不同的域:

 1 class InputComponent
 2 {
 3 
 4 public:
 5   void update(Bjorn& bjorn)
 6   {
 7     switch (Controller::getJoystickDirection())
 8     {
 9       case DIR_LEFT:
10         bjorn.velocity -= WALK_ACCELERATION;
11         break;
12  
13       case DIR_RIGHT:
14         bjorn.velocity += WALK_ACCELERATION;
15         break;
16     }
17   } 
18 
19 private:
20   static const int WALK_ACCELERATION = 1;
21 
22 };
23 
24 class PhysicsComponent
25 {
26 
27 public:
28   void update(Bjorn& bjorn, World& world)
29   {
30     bjorn.x += bjorn.velocity;
31     world.resolveCollision(volume_,
32         bjorn.x, bjorn.y, bjorn.velocity);
33   }
34  
35 private:
36   Volume volume_;
37 
38 }; 
39 
40 class GraphicsComponent
41 {
42 
43 public:
44   void update(Bjorn& bjorn, Graphics& graphics)
45   {
46     Sprite* sprite = &spriteStand_;
47     if (bjorn.velocity < 0)
48     {
49       sprite = &spriteWalkLeft_;
50     }
51     else if (bjorn.velocity > 0)
52     {
53       sprite = &spriteWalkRight_;
54     } 
55 
56     graphics.draw(*sprite, bjorn.x, bjorn.y);
57   }
58 
59 private:
60   Sprite spriteStand_;
61   Sprite spriteWalkLeft_;
62   Sprite spriteWalkRight_;
63 
64 };

最後的GameObject類:

 1 class GameObject
 2 {
 3 
 4 public:
 5   int velocity;
 6   int x, y; 
 7 
 8   void update(World& world, Graphics& graphics)
 9   {
10     input_.update(*this);
11     physics_.update(*this, world);
12     graphics_.update(*this, graphics);
13   } 
14 
15 private:
16   InputComponent input_;
17   PhysicsComponent physics_;
18   GraphicsComponent graphics_;
19 
20 };

 

更進一步,抽象出InputComponent為基類,使得GameObject可以連接不同的Input組件:

 1 class InputComponent
 2 {
 3 public:
 4   virtual ~InputComponent() {}
 5   virtual void update(Bjorn& bjorn) = 0;
 6 };
 7 
 8 class PlayerInputComponent : public InputComponent
 9 {
10 
11 public:
12   virtual void update(Bjorn& bjorn)
13   {
14     switch (Controller::getJoystickDirection())
15     {
16       case DIR_LEFT:
17         bjorn.velocity -= WALK_ACCELERATION;
18         break;
19 
20       case DIR_RIGHT:
21         bjorn.velocity += WALK_ACCELERATION;
22         break;
23     }
24   } 
25 
26 private:
27   static const int WALK_ACCELERATION = 1;
28 
29 };
30 
31 class DemoInputComponent : public InputComponent
32 {
33 
34 public:
35   virtual void update(Bjorn& bjorn)
36   {
37     // AI to automatically control Bjorn...
38   }
39 };

其他組件也是如此:

 1 class PhysicsComponent
 2 {
 3 public:
 4   virtual ~PhysicsComponent() {}
 5   virtual void update(GameObject& obj, World& world) = 0;
 6 };
 7 
 8 class GraphicsComponent
 9 {
10 public:
11   virtual ~GraphicsComponent() {}
12   virtual void update(GameObject& obj, Graphics& graphics) = 0;
13 };
14 
15 class BjornPhysicsComponent : public PhysicsComponent
16 {
17 public:
18   virtual void update(GameObject& obj, World& world)
19   {
20     // Physics code...
21   }
22 };
23 
24 class BjornGraphicsComponent : public GraphicsComponent
25 {
26 public:
27   virtual void update(GameObject& obj, Graphics& graphics)
28   {
29     // Graphics code...
30   }
31 };

然後是更靈活的GameObject類:

 1 class GameObject
 2 {
 3 
 4 public:
 5   int velocity;
 6   int x, y;
 7  
 8   GameObject(InputComponent* input,
 9              PhysicsComponent* physics,
10              GraphicsComponent* graphics)
11   : input_(input),
12     physics_(physics),
13     graphics_(graphics)
14   {}
15 
16   void update(World& world, Graphics& graphics)
17   {
18     input_->update(*this);
19     physics_->update(*this, world);
20     graphics_->update(*this, graphics);
21   }
22 
23 private:
24   InputComponent* input_;
25   PhysicsComponent* physics_;
26   GraphicsComponent* graphics_;
27 
28 };
29 
30 GameObject* createBjorn()
31 {
32   return new GameObject(new PlayerInputComponent(),
33                         new BjornPhysicsComponent(),
34                         new BjornGraphicsComponent());
35 }

 

 

Chapter 15 事件隊列

對消息或事件的發送與受理進行時間上的解耦。

事件隊列給接受端的控制權:延遲處理、聚合請求或完全拋棄。發送端所能做的就是往隊列里投遞消息,無法有實時反饋的預期。

避免A到B到A的事件迴圈:不要在處理事件端的代碼里再發送事件。

 

Audio類:

 1 class Audio
 2 {
 3 
 4 public:
 5   static void init()
 6   {
 7     head_ = 0;
 8     tail_ = 0;
 9   }
10 
11   // Methods...
12 
13 private:
14   static int head_;
15   static int tail_; 
16 
17   static const int MAX_PENDING = 16;
18 
19   static PlayMessage pending_[MAX_PENDING];
20 };
21 
22 void Audio::playSound(SoundId id, int volume)
23 {
24   // Walk the pending requests.
25   for (int i = head_; i != tail_;
26        i = (i + 1) % MAX_PENDING)
27   {
28     if (pending_[i].id == id)
29     {
30       // Use the larger of the two volumes.
31       pending_[i].volume = max(volume, pending_[i].volume);
32  
33       // Don't need to enqueue.
34       return;
35     }
36   }
37 
38   assert((tail_ + 1) % MAX_PENDING != head_);
39 
40   // Add to the end of the list.
41   pending_[tail_].id = id;
42   pending_[tail_].volume = volume;
43   tail_ = (tail_ + 1) % MAX_PENDING;
44 } 
45 
46 void Audio::update()
47 {
48   // If there are no pending requests, do nothing.
49   if (head_ == tail_) return;
50 
51   ResourceId resource = loadSound(pending_[head_].id);
52   int channel = findOpenChannel();
53   if (channel == -1) return;
54   startSound(resource, channel, pending_[head_].volume); 
55 
56   head_ = (head_ + 1) % MAX_PENDING;
57 }
  1. 環狀緩衝區;
  2. PlaySound只做元素入列,首先要判斷是否有重覆的,如果有就標記音量高的那個,tail增長時取餘;
  3. Update做元素取出,首先判斷是否有待取元素,head增長時取餘。

 

Chapter 16 服務定位器

為某服務提供一個全局訪問入口來避免使用者與該服務具體實現類之間產生耦合。

 

Audio服務:

 1 class Audio
 2 {
 3 
 4 public:
 5   virtual ~Audio() {}
 6 
 7   virtual void playSound(int soundID) = 0;
 8   virtual void stopSound(int soundID) = 0;
 9   virtual void stopAllSounds() = 0;
10 
11 };

Audio服務提供器:

 1 class ConsoleAudio : public Audio
 2 {
 3 
 4 public:
 5   virtual void playSound(int soundID)
 6   {
 7     // Play sound using console audio api...
 8   }
 9  
10   virtual void stopSound(int soundID)
11   {
12     // Stop sound using console audio api...
13   } 
14 
15   virtual void stopAllSounds()
16   {
17     // Stop all sounds using console audio api...
18   }
19 };

包含空服務的定位器:

 1 class Locator
 2 {
 3 
 4 public:
 5   static void initialize() { service_ = &nullService_; }
 6  
 7   static Audio& getAudio() { return *service_; }
 8  
 9   static void provide(Audio* service)
10   {
11     if (service == NULL)
12     {
13       // Revert to null service.
14       service_ = &nullService_;
15     }
16     else
17     {
18       service_ = service;
19     }
20   } 
21 
22 private:
23   static Audio* service_;
24   static NullAudio nullService_;
25 
26 };

註冊一個服務提供器:

1 ConsoleAudio *audio = new ConsoleAudio();
2 Locator::provide(audio);

使用:

1 Audio *audio = Locator::getAudio();
2 audio->playSound(VERY_LOUD_BANG);

調用playSound()的代碼對ConsoleAudio一無所知,它只知道Audio的抽象介面。Locator與ConsoleAudio也沒有耦合,代碼里唯一知道具體實現類的地方,是提供這個服務的代碼。

 

空服務:

1 class NullAudio: public Audio
2 {
3 public:
4   virtual void playSound(int soundID) { /* Do nothing. */ }
5   virtual void stopSound(int soundID) { /* Do nothing. */ }
6   virtual void stopAllSounds()        { /* Do nothing. */ }
7 };

getAudio返回一個引用而不是指針,因為在C++里理論上一個引用不會為NULL,返回一個引用提示了使用者任何時候都能期望得到一個有效的對象。

空服務可以保證在Locator初始化之前使用它的安全性,也可以用來實現暫時禁用服務:

1 Locator::provide(NULL);

 

日誌裝飾器:

 1 class LoggedAudio : public Audio
 2 {
 3 
 4 public:
 5   LoggedAudio(Audio &wrapped)
 6   : wrapped_(wrapped)
 7   {} 
 8 
 9   virtual void playSound(int soundID)
10   {
11     log("play sound");
12     wrapped_.playSound(soundID);
13   }
14 
15   virtual void stopSound(int soundID)
16   {
17     log("stop sound");
18     wrapped_.stopSound(soundID);
19   } 
20 
21   virtual void stopAllSounds()
22   {
23     log("stop all sounds");
24     wrapped_.stopAllSounds();
25   }
26 
27 private:
28   void log(const char* message)
29   {
30     // Code to log message...
31   } 
32 
33   Audio &wrapped_;
34 
35 };

 

1. 如果服務被限制在游戲的一個單獨域中,那就把服務的作用域限制到類中

    eg.獲取網路訪問的服務就可能應該被限制在聯網的類中

2.廣泛使用的服務,作用域為全局域

    eg.日誌服務應該是全局的


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 項目發起於2015年9月,最初的需求微信公眾號預約上門洗車可線上支付和當面現金支付。之前他們都是用電話預約上門洗車。 微信預約:使用技術後臺spring+struts2+jdbc,前臺主要是jquerymobile+bootstrap。圖標使用的是阿裡的iconfont。 15年10月正式上線,後來 ...
  • 數組保存的是一組有順序的、具有相同類型的數據。 1、創建: 數組的聲明格式: int arrary[]; int [] array1, array2; //同時聲明多個數組。 上面的語句只是對數組進行了聲明,還沒有對其分配記憶體,不可存放、訪問。Java中數組可以看做是一種特殊的對象,可用new對數組 ...
  • 高級語言程式設計報告 列印版報告截止上交日期:2014年11 月 15 日 電子版報告發至[email protected], 郵件標題寫明報告次數序號姓名 序號 34 姓名 許愷 照片 成績 E-MAIL及電話 18810556775 實習題目 第一次作業: 函數 任務六 一、 代碼及註釋 //編 ...
  • Spring boot是Spring推出的一個輕量化web框架,主要解決了Spring對於小型項目飽受詬病的配置和開發速度問題。 Spring Boot 包含的特性如下: 創建可以獨立運行的 Spring 應用。 直接嵌入 Tomcat 或 Jetty 伺服器,不需要部署 WAR 文件。 提供推薦的 ...
  • 轉發註明出處:http://www.cnblogs.com/0zcl/p/6259128.html,這次博客寫了很久~~ 一、需求 1. 用戶加密認證 (完成)2. 允許同時多用戶登錄 (完成)3. 每個用戶有自己的家目錄 ,且只能訪問自己的家目錄(完成)4. 對用戶進行磁碟配額,每個用戶的可用空間 ...
  • 今天呢,給大家來講一下抽象工廠模式,說到這裡,大家會想到好多種關於工廠的模式,前面已經講了兩種了 簡單工廠模式和工廠方法模式。好,下麵我們來看一下抽象工廠模式。 同樣,我們來舉一個案例 一、案例 我們在做項目的時候,肯定會與資料庫打交道,那麼我們用簡單的控制台應用程式來模擬一個向SqlServer數 ...
  • Chapter 17 數據局部性 通過合理組織數據利用CPU緩存機制來加快記憶體訪問速度。 數據局部性:多級緩存加快了最近訪問過的數據的鄰近記憶體的訪問速度,保持數據位於連續的記憶體中可以提高性能。 找到出現性能問題的地方,不要把時間浪費在非頻繁執行的代碼上。 為了做到緩存友好,可能會犧牲繼承、介面等這些 ...
  • 在C++的世界里構建一個序列化框架;並非一件困難的事情,但也並非簡單。因此,需要分成兩部分來完成這項任務: 1、序列化容器。 2、序列化方式。 前者,很容易理解;但也決定著我們將要存儲數據的方式:二進位抑或其他。二進位方式,很容易想到和使用的方式;但也最容易以極不安全的方式去使用;因為,為了各種原因 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...