Home > 工具編程 | 遊戲編程 > 從頭開始思考遊戲的資料管理系統(一)

從頭開始思考遊戲的資料管理系統(一)

和一般應用軟件有點不同,大部份遊戲軟件都需要使用大量的遊戲資料 (Game Data)──或稱為資源 (Resource)、資產(Game Asset, 但通常Asset包括資料的原始格式, 不是最終運行遊戲所需的資料)。在製作遊戲時,如何管理這些資料是一個非常重要的問題。曾經看過和使用過不同的方案,現在歸回原點,分析基本的需求,隨筆記錄我選的方案的思路。

遊戲資料管理的重要性

如果從遊戲軟件的產出 (deliverables) 來分析,一個遊戲軟件可以分為三部份:
遊戲引擎: 比較固定的、和遊戲性無直接關係的程式
遊戲唯讀資料: 腳本、圖像、音效、關卡、影像
遊戲讀寫資料: 遊戲存檔、遊戲設定、玩家自製內容

從現今的遊戲實際容量來看,遊戲引擎可能佔1~10MB、遊戲讀寫資料10KB~10MB,其餘以GB為單位的全是唯讀資料。以開發人員來計算,開發引擎可能是數人,但製作那些唯讀資料的隊伍是以百計的,主要是遊戲性程式設計師、人物美工、場境美工、關卡設計師等等。

本文主要談遊戲唯讀資料,所以以下會「資料」或「遊戲資料」皆指「遊戲唯讀資料」。

遊戲資料可以有幾多筆?

接著我隨便估計一個「小」遊戲需要的資料筆數(筆數在下節定義)

20 關卡 x (200關卡貼圖 + 100關卡模型 + 200遊戲物件) = 10000
40 人物 x (3人物貼圖 + 2人物模型 + 20人物動畫) = 1000
200視覺效果x (5效果貼圖 + 5動畫) = 1000
20使用者介面x 25貼圖 = 500
(100物件腳本 + 80人物腳本 + 20使用者介面腳本) x 5原始代碼檔案 = 1000
180音效 + 20音樂 =200
總和 = 13,700

以這個推算遊戲的資料筆數數量級一般在10^4 至 10^6。我覺得極限只是會到10^7,因為大量資料是模型、貼圖、動畫等,平均值不會少於10KB,而10^7 x 10KB 已經是 10GB。

遊戲資料的概念分析

我心目中的遊戲資料是有以下特點

  1. 唯一性 (Uniqueness): 每筆資料有它唯一的Identifier,透過一個identifier可以讀取一筆資料。
  2. 不可分割 (Atomic): 當把一些資料分割成多筆資料是沒意義的,就算是一筆資料。例如假設一張貼圖不會讀取、使用其中一部份,則視為一筆資料。
  3. 依存關係 (Dependency): 一筆資料可能會靠identifiers引用其他資料。這和上不可分割屬性也有關係,因為是用identifier依存某一個資料,而不是該資料內的一部份。我未想到會有循環的依存關係,暫時把依存關係當作一個 Directed Acyclic Graph (DAG)。

一般檔案系統的檔案和這裡的資料有點不同。檔案的路徑可以視為Identifier,但很多檔案的內容是可以再分割的,而且沒有顯性(explicit)的依存關係。

選擇Identifier

我看過和想到的資料的Identifier可以是:

  1. 路徑 (path): 例如 Texture/wall.jpg
  2. URL: 例如 Texture/wall.jpg、http://www.mysite.com/news.jpg
  3. 整數: 例如 0x54AF4C58
  4. GUID: 例如 {3F2504E0-4F89-11D3-9A0C-0305E82C3301}

路徑

檔案系統的路徑可能是最直覺的資料Identifier。我以前做過的引擎,和很多商用引擎也會使用檔案和路徑這個表達方式。路徑通常是相對於某一個目錄、或一個壓縮檔(例如 id 的引擎會把檔案壓縮在一個zip檔裡)。

路徑的好處是和我們日常用的操作系統管理檔案的方式一樣。層階式 (hierarchical)的目錄結構讓使用者可以自行分類管理,你亦可以歷遍(traverse)目錄結構,例如讀取某一目錄的*.lua。它實作簡單,因為操作系統已提供所需功能。也可以使用一些現存的版本管理工具去直接管理這些資料,如SVN、Perforce等。

路徑的壞處包括: 大小寫問題、多國文字問題、儲存效率差、運行效率差。前二者太概都容易明白,不再詳述。儲存效率差是指用字串去記錄每個identifier,包括引用時的identifier都會花費不少空間,而且路徑(不單是檔案名)的長度可以很長(例如可能要256 位元組)。而運行效率差是因為比較兩個路徑慢,計算一個路徑的hash code (例如用路徑identifier做 hash_map或hash_set 的key) 也是很慢而且不是常數速度。在遊戲運行期,這些路徑文字資訊是冗餘的,因為玩家不會看到。

URL

URL 算是路徑的延伸。這延伸的好處是它是一個標準(不會有平台相關、大小寫、多國語言等問題),而且有些XML裡也會用URL作為引用的identifier。另外,可以選擇性支持不同的協定 (protocol),例如透過http存取互聯網上的資源。其他壞處和路徑差不多。

整數

整數是最簡單的identifier。通常關聯式資料庫(relational database)的表(table)都以一個整數欄作為列的primary key。相對前兩種identifier,整數的好處是儲存量小而速度快。

整數identifier的缺點是,整數對使用者而言沒有任何意義,也沒有層階式的管理系統。

另外,多少位元才足夠呢? 從之前的估計,10^7筆資料用32-bit就足夠了。用另一個角度想,如果一個5年的項目有100個成員會添加資料,一周工作7天的話平均每人每日可以增加23534筆資料,嗯……應該足夠吧,除非資料經常大量刪去又創建資料(每次都產生新的identifier)。要澈底杜絕Identifier不夠用,可以重用舊的刪去了的Identifier,或是把現有的所有Identifier重新編號。

整數identifier在多人同時建立資料的時候,還要考慮如何令identifier不重覆。解決辦法之一是連線到一個identifier產生的伺服程式。

GUID

最後,GUID的好處是確保在不連線下,每人也可以得到不會重覆的identifier。缺點是GUID的大小為128-bit,是32-bit的4倍。

之後

這幾個簡單的段落已花了我一整個晚上,我想繼續寫有關這些遊戲資料的使用流程,並最後闡述現在做的引擎在這方面的設計。還是那一句,希望多些交流指導。

Comments:7

Lancelot 09-03-13 (五) 9:41

見過幾個遊戲與引擎的做法是,用字串(不管是路徑還是URL)當做識別字,但是在引擎的內部,建立一個字串表格或是字典的資料結構,將這些字串對應成一個個不同的整數,然後可以提昇資源管理上的效率以及降低資源使用的空間。

Ricky Lung 09-03-13 (五) 11:21

First of all, let me share some of my thoughts during my engine development.
A custom URL approach is used to address an unique resource within a file, eg “./model/dog.3ds:mesh1″. To access a resource created in memory: “memory:renderTargetTexture1″. This approach will make accessing resource even more easy in script.

Current I didn’t have the performance problem of string compaision, since the string identify will convert to a pointer at the first time a client use the resource. By the way, there are some special algorithms to use string key where many of the strings have a common prefix.

In your calculation, are you assumming all resource identifier of all level should load into memory? For maximum file path of size 512, having 13,700 resource will take around 6Mb of space. If only a sinlge level will load into memory at a time, there are only 4200 resource identifier have to live in memory at a time which needs only ~2Mb of memory.

Finally, what I am thinking is how to stream a very large scene. A large virtual world should divided into many zones, and the caching stregedy is apply on the zone level instead of per resource. Using such scheme will give you some SIMPLE form of hierarchical resource management and vitally give no restriction on the number of resource. What is limited is only how far a player can see or how detail within what he can see.

Actually what are you worring about? Netwroking issues?

Milo 09-03-13 (五) 13:21

Lancelot:

謝謝你的提供的經驗。你所述的型式,是 string ID 單向映射到 integer ID,還是雙向的? 就是說 string ID 和 integer ID 是等價的?

我現在的做法,也是會同時有路徑和內部的integer ID。不過路徑只是給開發者看的名稱,而不是真正的identifier,可以說是 integer ID 單向映射到路徑。更改路徑並不會影響 dependency,所有dependency 都會以 integer ID 做 lookup。另外,在遊戲正式版的唯讀資料中完全刪去這些文字。

尼采 09-03-13 (五) 13:37

我的看法是,那個Identifier 只用在引擎內部,是給開發者看,還是兩用的呢??

給開發者是的Identifier, 可以是URL, file path, 又或 fourCC or 8CC code 也可以..

Milo 09-03-13 (五) 13:46

Ricky:

Thanks for your sharing.

Performance and memory is one of the problem (may be a smaller one than I thought, but Wii has got only 24MB fast + 64MB slow memory). And I will elaborate other problems by using string as identifier. One of those is using string identifier directly in scripts or other resource. If the resource is renamed or removed, it is difficult to detect these errors in build-time. I will propose using integer identifier, incorporate with with explicit dependency information for each resource. So that these errors can be detected, or even notify user when they are changing some resources which invalidates the dependency information.

In my estimation, all the resources are not loaded in memory once in retail runtime version. However, if you need to lookup the resources, you may need to load the whole “FAT” (or use a hierarchical FAT?). This still consumes memory. And also, not only each resource requires memory of an identifier, each dependency relationship also, as the script example mentioned in the previous paragraph.

For large scenes, yes, static parts of a zone needed to be loaded as a chunk of resources (And they should because it promotes linearized reading which enhances loading speed). But even you stream a large chunk from secondary storage, you still need to identify individual resources, for example, to link the relationships between resources (e.g. mesh -> material -> texture).

Humm… I am not really worrying something. But I want to identify the requirements and different approaches to this problem. Try to analyze the pros and cons of each. And also see if I can providing some “novel” solution (at least better than the ones I saw and I used). I think that I got more experience in production so some of my thoughts were changed. And writing an article to clarify my thoughts and share with you guys will get new ideas as well!

Lancelot 09-03-13 (五) 16:08

我看過的方法,包括雙向的對應以及單向(string ID –> integer ID)對應。

雙向的對應其實主要是用在Debug上,debug的時候,把整數ID轉回成字串ID總是比較容易知道是哪個資源出問題。

Milo 09-03-19 (四) 0:06

尼采:

對不起,遲了5天才發現 approve 少了你的回應。 ^_^

有一些引擎是兩者都會用。但我目前設計的系統,identifier 只是內部用的,盡量 transparent to 所有製作人員,包括寫腳本的程序員。

Comment Form
Remember personal info

Trackbacks:0

Trackback URL for this entry
http://miloyip.seezone.net/wp-trackback.php?p=104
Listed below are links to weblogs that reference
從頭開始思考遊戲的資料管理系統(一) from Milo的遊戲開發

Home > 工具編程 | 遊戲編程 > 從頭開始思考遊戲的資料管理系統(一)

Search
Feeds
Meta

Return to page top