請選擇 進入手機版 | 繼續訪問電腦版
設為首頁收藏本站

Bluelovers.風

 找回密碼
 註冊

QQ登錄

只需一步,快速開始

查看: 743|回復: 5

[知識文庫] web策略類遊戲開發 [複製鏈接]

沈簡水發表於 2009-3-29 18:26:26 |顯示全部樓層
QRCode:
QR code

作者:Yahle
曾用網名:Dogvane
原載:http://www.cnblogs.com/yahle
版權所有。轉載時必須以鏈接形式註明作者和原始出處。

(一) WebGame架構篇

1 體系結構

1.1 傳統的網站的架構

傳統的網站一般都是以N層結構一般N為3,就是我們常說的三層架構。
3層架構分為數據層、業務邏輯層、頁面顯示層。

1.2 WebGame的架構

WebGame可以看作是網站和遊戲的結合體,因此它具備了這兩類系統的特性。我們不但可以把WebGame看作是一個網站,也可以把它看作是一個網絡遊戲。
的網站是B/S結構,網絡遊戲則是C/S結構,WebGame則是這兩者的結合我們暫且稱之為B/C/S結構。既在用戶眼裡,它是一個通過瀏覽器範圍的網站。在服務器系統裡,它又是一個傳統的C/S結構的網絡遊戲。

從上圖分析,用戶通過瀏覽器訪問服務器的時候,首先是訪問網頁服務器,如windows平台下的IIS,linux下的Apache。在通過網頁服務器,以某種特殊的方式(分佈式訪問,如.net下的remoting)去訪問遊戲服務器,通知遊戲邏輯服務器執行玩家操作,並從遊戲邏輯服務器裡獲得遊戲相關的信息,或者直接通過訪問數據庫而獲得遊戲數據。

1.2.1 為什麼要將服務器分為遊戲服務器和網頁服務器

網頁服務器的特點是觸發執行,及當有用戶訪問網頁的時候,才會執行該網頁的程序代碼。而我們常見的WebGame(Ogame,Travian)這些遊戲實際上是需要24小時不間斷執行的,因此網頁服務器的執行方式並不適合與遊戲。因此我們另外需要一個應用程序來執行這些24小時不間斷要做的事情。這也就是我們需要增加一個遊戲服務器的原因。

1.3 Web三國的架構

因為目前Web三國是非商業開發,因此如果照搬上面的WebGame架構來設計,會導致開發週期過長,開發效率低下的問題。實際上在開發初期,Web三國是按照上面的架構去開發,碰上了上述問題,才換了另外一種結構。
實際性現在Web三國的結構和WebGame架構差不多,只不過將遊戲服務器集成到網頁服務器裡,項目裡按照傳統的網站架構,將遊戲分為:數據層、遊戲邏輯層、頁面層。
至於如何實現24小時不間斷處理,者是通過在遊戲啟動時,創建一個線程去處理。這個是asp.net裡的一個功能,我不清楚php裡是否也有這樣的功能。
連到我的抓火狐推薦頁!
沈簡水發表於 2009-3-29 18:32:19 |顯示全部樓層

(二) WebGame事件

1 事件系統

事件系統是整個WebGame系統裡一個核心的組成部分,我們用它來控制的進程,讓遊戲世界裡能夠24小時運轉。

1.1 事件的概念

事件是指遊戲裡玩家的某個(系列)活動,它可以分為瞬時活動和非瞬時活動。
瞬時活動顧名思義就是在玩家發出指令的瞬間就能完成的活動。像RPG遊戲裡,玩家從NPC裡購買一瓶藥水,在玩家發出這個指令後,玩家的金錢減少,並獲得藥水,這一切都在玩家發出指令後瞬間完成(當然實際邏輯上處理還需要幾個ms處理時間)。
而非瞬時活動則是在玩家發出某個指令後一段事件才會被執行。例如RPG裡玩家鼠標點擊地圖上某個地方,遊戲角色則會自動行走到剛才點擊處。這個移動過程就是一個非瞬時過程,它有了一個移動的過程,這個過程需要消耗一定的時間(玩家能感知的事件)
非瞬時系列活動是指一位或者多位玩家通過一系列的瞬時\非瞬時活動完成一個動作(功能)。例如wow裡面的拍賣場,有1位玩家提供道具,同服務器裡的其他玩家對該道具進行競拍。
在WebGame裡,玩家的很多操作其實是非瞬時部分事件是村莊資源減少(前提投資),非瞬時事件是建築物建設,這個動作(同能)的結果是建築物等級上升。
又比如《Travian》裡的攻擊,瞬時事件是當前村莊的士兵減少(派出部隊),非瞬時事件是減少的士兵移動到需要攻擊的村莊(行軍過程),動作結束是,兩個村莊的部隊開打了(戰鬥)。

1.2 觸發器(事件隊列)

前面說了瞬時事件和非瞬時事件的概念,當WebGame在24小時運轉的時候,系統會產生大量的非瞬時事件,這些非瞬時事件不會在玩家點擊頁面時執行,而是需要等一段時間後才會執行,因此在遊戲裡把這些非瞬時事件拿出來,按事件的執行時間進行排序,組成一個事件隊列。再通過一個觸發器,在事件設定的執行時間到達的那時執行相對應的事件。
這裡面就涉及到兩個內容:
• 非瞬時的事件隊列
• 事件觸發器

1.2.1 事件隊列

數據庫除了用於存儲外,其查詢功能也非常強大,直接拿來做事件隊列很合適。事件隊列裡通常保存事件涉及的對象(村莊),結束時間、事件類型以及事件相關參數等。
下表為我們系統裡使用的事件表:



保存時將這些對象做xml序列化保存到EventObject字段裡。當然如果為了效率還可以存儲二進制序列化後的對象,這樣在序列化以及反序列化時能節省大部分時間。

1.2.2 觸發器

Asp.net
Asp.net的處理在觸發器上的處理就比較簡單了。在服務器程序啟動的時候,就執行一個線程,定時(1s)從數據庫裡取結束時間<當前事件的事件進行處理。
/**//// <summary>
/// 事件處理線程
/// 每間隔1s處理一次到時間了的事件
/// </summary>
static void EventThread()
{
    DateTime lastTime = DateTime.Now;
    while (Run)
    {
         try
         {
             Event.SystemEvent.DoEvent(lastTime);
         }
         catch (Exception ex)
         {
             Common.Logging.Error(ex.ToString());
         }

         long def = DateTime.Now.Ticks - lastTime.Ticks;
         lastTime = DateTime.Now;

         if (def < 10000000)
         {
             //  線程休息,並等待下一次時間間隔
             int ms = (int)(10000000 - def) / 10000;
             if (ms > 0)
                 System.Threading.Thread.Sleep(ms);
         }
     }
 }
/**//// <summary>
  /// 系統事件
  /// 主要作用是執行處理事件的過程
  /// </summary>
  public class SystemEvent
  {
      public static void DoEvent(DateTime time)
      {
          List<Event> evs = Event.GetEvents(time);
           foreach (Event e in evs)
           {
               try
               {
                   IEvent ie = e.Object;
                   if (ie != null)
                   {
                       ie.Parent = e;
                       ie.DoEvent();
                   }
               }
               catch (Exception ex)
               {
                   Common.Logging.Error(ex.ToString(), e.EventObject);
               }
               try
               {
                   e.Delete();    // 從數據庫裡刪除信息
               }
               catch (Exception ex)
               {
                   Common.Logging.Error(ex.ToString());
               }
           }
       }
   }
php
PHP沒用過,不過好像不能在PHP頁面裡無法創建線程,用純PHP服務端來實現觸發器估計有點難度。但是可以做成一個PHP的應用程序,由應用程序來實現觸發器。

1.3 遊戲資源的24小時自動增長

遊戲資源的24小時自動增長,這是一個有趣的話題,很多剛開始設計WebGame的朋友都會在這個問題上卡一下。每個人對於如何實現這個功能都有自己的獨到見解,我在這裡就不能給出一個唯一的答案,這裡給的解決方案只是自己正在做的WebGame用到的方案。
首先,我們否定了每個時間間隔(10分鍾)就執行更新村莊資源的設計,這即不準確,同時也很消耗服務器資源。所以,我們的系統就只有在用戶執行事件(瞬時的和非瞬時)的時候才將新的資源信息寫入數據庫。平時顯示資源的時候用
(當前時間 - 上一次更新事件)*資源每小時產量+上一次更新產量
公式計算出當前資源並顯示在頁面上。也就是說,每次頁面更新時重新計算資源,但只只要用戶沒有做任何修改資源的動作(事件),就不會把重新計算後的資源寫回數據庫。
《Travian》在前台界面上看到資源不斷的上漲其實是利用JavaScript實現的小效果。

使用道具 舉報

沈簡水發表於 2009-3-29 18:38:44 |顯示全部樓層

(三) 多線程下數據庫並發更新的處理

1 多線程下數據庫並發更新的處理

1.1 背景

不知道大家在玩《Travian》時有沒有做過這樣的事情:
同時打開多個集結點,並設定好要出發的士兵及數量,在快到壓秒的時候,快速切換頁面,不斷的點確定,以確保遊戲不會通訊問題導致壓秒失敗。

再看一個教科書裡經常提到的數據庫髒數據的案例:
A操作從表裡獲得數據D=10,在計算的時候,線程剛好進行切換,切換到B,B也需要操作D,並從數據庫裡取道值為10,在進行簡單操作(D=D-2)後將D=8的值寫回數據庫。B操作處理結束後,線程再切換回A操作,這時A在做自己的操作時,仍然採用先前取到D(10)的值,在進行一個簡單操作(D=D-1)後,仍然寫回數據庫。這時,數據庫裡的值變為9,而實際上D的值應該是7(D=10-2-1).。造成這個問題主要是因為CPU在執行A、B操作時沒有按照順序來執行,而是讓B搶先在A執行完之前執行,導致它們在計算D的時候,因為數據沒有同步而發生寫入髒數(A的數據覆蓋了B的數據)據的問題。
A操作偽代碼:
{
 D=GetDB()
 D=D-1
 SetDB(D)
}
B操作偽代碼:
{
 D=GetDB()
 D=D-2
 SetDB(D)
}
一個簡單的圖表表示它們的操作:
CPUA操作B操作數據庫當前值
D=GetDB()

10

線程切換D=GetDB()

10

D=D-2

10

etDB(D)

8

線程切換D=D-1

8

etDB(D)

9



仿照第一個例子,當一位玩家同時打開多個頁面點出兵進攻後,這些這些請求會同時到達服務器,服務器會根據這些Http請求創建相應的線程來處理進攻動作:
進攻的偽代碼:
{
 村莊士兵數量=GetDB()
 if (村莊士兵數量 > 這次進攻士兵數量)
 {
  村莊士兵數量=村莊士兵數量-這次進攻士兵數量
  SetDB(村莊士兵數量)
 }
}
按照第二個髒數據的例子,應該很容易想到,我們在這次進攻的時候,很有可能派出了2只部隊,但是只減少了1只部隊的士兵。
多線程未同步是造成遊戲bug的原因之一。

1.2 多線程並發與互斥

關於什麼是多線程,以及多線程下面的同步及互斥的方法我這裡就不過多的介紹了,相關內容可以到網上搜索,本文主要是討論同步的時機以及避免死鎖
cnblogs.com相關主題

1.3 WebGame裡多線程數據同步的方法

1.3.1 在asp.net下用lock進行加鎖操作

在我們的WebGame裡,採用asp.net的lock方法。具體就是在方法體裡,用lock裡鎖住一個對象,使其它方法在訪問這個對象的時候被阻塞。當第一個訪問對象的線程退出並釋放鎖以後,其它的線程才能取消阻塞狀態繼續操作該對象。
我們通過修改上面進攻的偽代碼,增加lock操作:
{
 lock(鎖定的對象)
 {
  村莊士兵數量=GetDB()
  if (村莊士兵數量 > 這次進攻士兵數量)
  {
   村莊士兵數量=村莊士兵數量-這次進攻士兵數量
   SetDB(村莊士兵數量)
  }
 }//unlock(鎖對象)
}
CPU執行流程表:
CPU線程A線程B
lock(鎖對象)
村莊士兵數量=GetDB()
if (村莊士兵數量 > 這次進攻士兵數量)
線程切換lock(鎖對象)對象被鎖,線程進入阻塞狀態
線程切換村莊士兵數量=村莊士兵數量-這次進攻士兵數量
etDB(村莊士兵數量)
//unlock(鎖對象)
線程切換村莊士兵數量=GetDB()A線程釋放鎖,B線程被喚醒
if (村莊士兵數量 > 這次進攻士兵數量)
村莊士兵數量=村莊士兵數量-這次進攻士兵數量
etDB(村莊士兵數量)

1.3.2 鎖的粒度

上面的偽代碼是對遊戲裡的邏輯代碼進行了加鎖處理,但只是簡單的描述該方法體裡需要進行加鎖以及加鎖的範圍。但在實際的代碼裡,我們需要明確lock裡鎖定的對象。如果這個對象選擇不合適,很有可能會造成性能損失或者死鎖。

1.3.2.1 數據庫鎖

數據庫鎖是最一種簡單的方法就,凡是在存在發生數據庫寫操作的代碼裡,都需要進行加鎖處理,並同意用一個數據庫鎖對象。
這種方法使用簡單,不容易造成死鎖。當存在的問題也是明顯,就是在發生寫數據庫操作時不能並發操作,這點特別是當用戶訪問量增大可能會造成一定的性能瓶頸。

1.3.2.2 村莊鎖

為了提升寫數據庫的效率,我們必須解決鎖粒度過大的問題,因此在我們的遊戲系統裡,對鎖的粒度進行的細化,細化到村莊級別的對象。
在遊戲一張村莊表對應是整個遊戲裡所有的村莊對象,而一個村莊對象在村莊表裡只是一條記錄。在使用數據庫鎖時,其實是告訴其它方法,現在我要寫數據庫,大家都等一下,等我寫好後再寫。當我們將鎖的對象細化到村莊(一條數據庫表記錄)的時候,實際是告訴數據庫,我現在要修改XXX村莊,大家都別動它,但你要修改YYY村莊我不管。

死鎖

在前面雖然降低了鎖的粒度,提高了數據庫並發性能,但隨之而來就很容易發生一個問題--死鎖。
例如當兩個村莊需要發生交易,這時我們需要同時修改A、B兩個村莊對象,需要對其進行順序鎖定(先鎖定A,再鎖定B),這時候,又發生另外一個操作,也需要同時對A、B兩個村莊進行鎖定,恰巧這個鎖定的順序是B、A。這樣就造成兩個現在互相等待形成死鎖。

中間變量解決死鎖

對於死鎖,在關於多線程介紹方面有很多解決方案,這裡就不過多闡述。在WebGame裡預防死鎖,可以採用結合遊戲的操作流程,對遊戲處理流程及數據進行拆分,來預防死鎖問題。簡單的來說,就是將涉及2個村莊修改的流程拆分為2個流程,並用中間變量予以表示,兩個處理流程涉及變化的值都在中間變量裡予以保存。
以前面提到的交易為例,在遊戲設計裡,兩個村莊在交易的時候,並不是瞬時交易,而是通過商人進行運輸並交易。這樣我們就以商人作為中間變量。
A<-->C<-->B
在上圖裡,交易開始,是由玩家觸發交易事件,這時以村莊A為鎖對象,進行鎖定。C作為要交易資源從村莊A裡被扣除。並將A修改後的數據回寫到數據庫。等過了一段事件後,商人C到達了目的地,這時由系統觸發後續的交易事件,這時以村莊B作為鎖對象進行鎖定。村莊B在獲得資源後寫回數據庫,整個交易事件就算完成了。
當然實際遊戲的交易比上面的例子稍微複雜一點點,因為交易雙方都有資源的減少與獲得。完整的流程應該是需要鎖定4次,並產生2個中間變量。
村莊A-->商人1-->村莊B
村莊A<--商人2<--村莊B

遊戲裡其它地方的鎖定

基本上,凡是某個事件涉及到兩個村莊修改的地方,都可以用上面的鎖定的方法對處理流程進行修改。例如村莊A攻打村莊A。當然其它事件在數據修改方面只涉及1個村莊,那麼就不需要怎麼麻煩,直接對村莊加鎖鎖定即可。
好在在策略類的WebGame裡涉及兩個村莊的情況不多,涉及到的基本可以用中間變量對操作流程進行拆分,因此這種鎖定方式在策略類WebGame還是比較合適的。當然實在不行也只能按照以往的預防死鎖的方法進行處理

1.4 非Asp.net裡同步的方案

1.4.1 Java

java在線程同步機制上與asp.net基本一致,因此上面所述的asp.net的方法也適合與java

1.4.2 php

瞭解不多,不過好像php沒有線程鎖與同步這個概念,如果直接通過語言環境進行同步可能比較困難。
不過在MySQL裡,存在一個表鎖定的方法,可以通過lock table的方法鎖定表,不允許其它MySQL用戶去進行操作。基本上和前面提到的數據庫鎖類一樣,只不多執行方法的時候是在MySQL端執行。

使用道具 舉報

沈簡水發表於 2009-3-29 18:43:13 |顯示全部樓層

(四)一個可以承載萬人在線的架構

Webgame現在已經開始需要進入大統一服務器時代,每個遊戲區域容納的玩家數量將從現在的幾萬人發展到幾十萬人,因此在新的背景下,webgame如何處理大量用戶的請求將成為問題。目前一台asp.net做的weggame服務器每秒能處理500~1000個頁面請求,按照每個玩家每隔3~5秒做一次頁面操作(頁面請求),一台服務器能承受2k~4k的玩家在線,對於一個只有幾萬人的策略遊戲來說,已經是足夠了。但對於一個未來將承載幾十萬人的遊戲來說遠遠不夠。

通過分析,玩家在遊戲過程中,有80%以上的訪問僅僅只是查看玩家在遊戲裡的狀態,實際上真正會對遊戲運行狀態及數據修改的的頁面請求不足20%。因此,我們可以將呈現頁面和處理遊戲邏輯的功能拆分為2組服務器:頁面服務器和邏輯服務器。兩者之間可以通過remoting的方式進行數據通訊。將服務器分離後,隨著頁面服務器的增加,頁面訪問能力能應該能提升4~6倍。在往上邏輯服務器就會出現訪問瓶頸。解決方法可以讓頁面服務器在讀取玩家數據時直接訪問數據庫或者增加一個對象緩存服務器。頁面服務器只有在必要的時候(需要進行邏輯運算時)才訪問邏輯服務器,而邏輯服務器在玩家數據發生改變後更新對象緩存服務器和數據庫。這樣就可以大大降低邏輯服務器的訪問次數,使頁面訪問能力進一步提升,輕鬆突破萬人在線。如果訪問量還需要繼續擴大,可以用httpd做前台負責相應圖片以及css等靜態文件。

已有 1 人評分聲望 幸運 理由
System Message 聲望+ 1 幸運+ 1 獎勵訊息:妖妾蜘蛛踩到狗大便, 狗狗主人只好送上賠禮. 於 2009-3-29 18:43:13

使用道具 舉報

沈簡水發表於 2009-3-29 18:45:13 |顯示全部樓層

(五)數據庫表設計

有人希望看數據庫表,在這裡發一下表設計,基本上沒有什麼特別的地方需要解釋的,數據庫的字段名都寫得很清楚了。當然,目前的字段只是遊戲的基本字段,如果遊戲功能多起來後,表設計會比現在複雜。

表名:Village

序號

列名

數據類型

長度

小數位

標識

主鍵

允許空

默認值

說明

1

ID

int

2

Name

varchar

50

3

code

int

0

4

PlayerID

int

0

5

villageType

int

1

6

CityCode

int

0

7

Building

varchar

255

8

BuildingLevel

varchar

255

9

X

int

0

10

Y

int

0

11

Population

int

0

12

Resource

varchar

50

13

MaxResource

varchar

50

14

OutPut

varchar

50

15

LastUpdateResource

datetime

Now()


表名:Troops




序號

列名

數據類型

長度

小數位

標識

主鍵

允許空

默認值

說明

1

ID

int

2

PlayerID

int

0

3

VillageCode

int

0

4

Num

varchar

50

5

TroopType

varchar

50

6

AttackVillageCode

int

0

7

EndTime

datetime

8

State

int

0

9

AttackBuildingID

int

0

10

Code

varchar

50

11

HeroID

int

0


表名:Trade




序號

列名

數據類型

長度

小數位

標識

主鍵

允許空

默認值

說明

1

ID

int

2

PlayerID

int

0

3

VillageCode

int

0

4

Type1

int

0

5

Num1

int

0

6

Type2

int

0

7

Num2

int

0

8

X

int

0

9

Y

int

0


表名:SystemMessage




序號

列名

數據類型

長度

小數位

標識

主鍵

允許空

默認值

說明

1

ID

int

2

PlayerID

int

0

3

Type

int

0

4

Title

varchar

50

5

Object

varchar

0

6

IsRead

int

0

7

CreateTime

datetime

Now()


表名:PlayerMessage




序號

列名

數據類型

長度

小數位

標識

主鍵

允許空

默認值

說明

1

ID

int

2

PlayerID

int

0

3

SendPlayerID

int

0

4

SendPlayerName

varchar

50

5

Title

varchar

50

6

Message

varchar

0

7

IsRead

int

0

8

CreateTime

datetime

Now()

9

IsDelete

int

0


表名:Player




序號

列名

數據類型

長度

小數位

標識

主鍵

允許空

默認值

說明

1

ID

int

2

Name

varchar

50

3

Password

varchar

50

4

Alliance

int

0

5

Nationality

int

0

6

Gender

varchar

50

7

Location

varchar

50

8

Info

varchar

0

9

Info2

varchar

0

10

Population

int

0


表名:Log




序號

列名

數據類型

長度

小數位

標識

主鍵

允許空

默認值

說明

1

ID

int

2

PlayerID

int

0

3

TypeID

int

0

4

Memo

varchar

0

5

LogTime

datetime

Now()


表名:Hero




序號

列名

數據類型

長度

小數位

標識

主鍵

允許空

默認值

說明

1

ID

int

2

Name

varchar

50

3

PlayerID

int

0

4

VillageCode

int

0

5

Level

int

0

6

exp

int

0

7

BaseProperty

varchar

50


表名:GoodFriend




序號

列名

數據類型

長度

小數位

標識

主鍵

允許空

默認值

說明

1

ID

int

2

PlayerID

int

0

3

GoodFriendID

int

0

4

Type

int

0


表名:Event




序號

列名

數據類型

長度

小數位

標識

主鍵

允許空

默認值

說明

1

ID

int

2

VillageCode

int

0

3

TargetVillageCode

int

0

4

Type

int

0

5

BeginTime

datetime

6

EndTime

datetime

7

ShowText

varchar

50

8

EventObject

varchar

0

使用道具 舉報

沈簡水發表於 2009-3-29 18:48:05 |顯示全部樓層

(六)緩存概述

既然是概述,就沒有太多詳細的東西,本文主要針對asp.net開發環境。

webgame需要緩存的內容包括

1.遊戲的配置信息
2.玩家的信息

對於遊戲配置信息,通常是指遊戲裡一些固定不變的信息,例如建築物每次升級時需要多少資源,需要多少時間等數據,這些數據當然可以寫死在代碼裡,但通常這些數據應該放在代碼外,要麼是以文件形式存放(xml或者txt),要麼就是放在數據庫裡。這部分數據的緩存很好做,只需要在應用開始時,統一做一次加載就可以了。一般來說,做過1年開發的同學都知道這種數據應該用單例模式來加載和使用,這點對java同樣適用。當然做成靜態屬性也是可以的,只要把握好加載的時機就可以了。這裡還順便說一下,如果遊戲的配置信息存在交叉訪問(索引),則要注意兩者之間的加載順序。或者對交叉訪問的部分不做索引,每次都動態的訪問(查詢)。

對於玩家的信息,則有一些說頭了。基本上,現在的.net項目都做成3成結構+ORM訪問。緩存的對象應該是遊戲的實體對象。同上實體對象和數據庫表之間都是一一對應的,這點沒什麼好多說的。以玩家或者村莊對象為了,它們的索引通常是ID,只需要創建對應的Dictionary<int, Player> 字典對象用來存取數據就可以了。
        Dictionary<int, Player> playerMap = new Dictionary<int, Player>();
        public Player Getplayer(int ID)
        {
            Player ret;
            if (playerMap.TryGetValue(ID, out ret))
                return ret;

            ret = 從數據庫裡獲取玩家對象(ID);

            playerMap.Add(ID, ret);

            return ret;
        }
上面是一個基本的用於從緩存裡訪問玩家對象的方法。這點對大家來說,都不算很難,有點經驗的同學都能寫得出來。

下一步如何更新這個緩存就是這個緩存系統才是webgame的麻煩地方。

我們緩存的對象和web應用的對象不一樣,它存在著隨時變化的可能,並且當他發生變化時,需要能及時反饋給用戶。

web應用我們以blog為例,當某位用戶添加了新文章到cnblogs的首頁,可能不會立即被其他用戶看到,因為cnblogs首頁的緩存信息還沒有被修改。通常根據需要這些緩存信息可能會是1分鍾,也有可能是10分鍾,只有當緩存過期了以後,系統才會生成新的首頁內容。其目的是減少首頁的數據庫查詢訪問量。

webgame遊戲則不太一樣,我花資源升級,就希望在頁面上能立即看到變化,因此,當我們完成某項業務邏輯操作後,需要人工的更新資源對象的緩存。
            try
            {
                //  遊戲邏輯處理
                //  數據庫數據提交

                緩存更新();
            }
            catch (Exception ex)
            {

                //  日誌處理
            }
按照通常的做法,每次邏輯操作都包含在一個事務裡面,如果邏輯操作失敗時,則可以對事務做撤銷處理,儘量避免數據異常。

當然,這個也不是絕對的,前兩天在QQ上談論到緩存更新的問題時,某位同學帖出了他的代碼,代碼裡,緩存的更是是在數據庫事務提交之前。如果提交發生失敗,則整個遊戲系統已緩存的數據為主。這個問題咋一看來,和我們的思路不一樣,我們就認為這樣做有問題,後來回家的路上仔細的想了想,其實這樣做也不無道理,因為它是在數據庫提交之前更新緩存,也就是說,如果發生錯誤,唯一可能錯誤的地方就是寫數據庫時寫失敗了。但如果整個遊戲系統是以緩存數據為準,只要遊戲邏輯在計算時沒發生錯誤,將錯誤的數據寫入緩存,那麼就算當前的數據修改提交到數據庫失敗了,數據還有可能在下一次修改時,提交一份正確的數據到數據庫。整個系統不會因為數據庫癱瘓了而無法運行。這點感覺和網遊的服務器設計思路近似,畢竟對於網遊來說,不可能每次玩家的操作都將數據寫回數據庫,玩家的數據都以在服務器內存裡的數據為準,以定時的方式將內存數據會寫到數據庫。

其實這兩種設計思路的差別就在於,數據是以數據庫為中心還是以內存數據為中心。對與web系統來說,自然是以數據庫為中心。從網遊的角度來說,自然以內存數據為中心。而webgame是這兩大系統的結合,其數據訪問思路自然綜合了這兩種觀點,具體到某個遊戲,則需要根據遊戲的需要而加以取捨了。

除了以字典為主的緩存設計外,還有一個重要的緩存對象的設計需要說一下,那就是地圖。目前常見的Webgame(Travian,武林三國)都是以一張400*400的世界地圖為玩家的交戰地圖。通常是一次性全部加載到內存裡。存放的格式,自然是以x,y軸坐標為依據的二維表裡。雖然首次加載是數據會比較慢一些,內存佔用的空間會多一些,但當玩家查看地圖頁時,你會發現頁面生成的數據比從數據庫裡獲取相應數據要快很多。再加上現在服務器內存動則 4G,8G的。則幾十兆的地圖數據還是毛毛雨了。

使用道具 舉報

RE: web策略類遊戲開發 [修改]
您需要登錄後才可以回帖 登錄 | 註冊

關閉

站長推薦

秀出你的創意窩窩裝扮
你知道嗎 你可以用 CSS 裝扮您的窩窩風格

查看 »

Bluelovers.風


Archiver|手機版|Bluelovers.風

  

GMT+8, 2012-5-20 02:39:46 , Processed in 0.157106 second(s), 15 queries , 70 ios, 12.05 mbs.

Powered by Discuz! X2 (Build 20111221)

© 2001-2011 Comsenz Inc.

回頂部