《游戲編程模式》(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
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...