Làm thế nào để thiết kế một AssetManager?


26

Cách tiếp cận tốt nhất để thiết kế AssestManager sẽ chứa các tham chiếu đến đồ họa, âm thanh, v.v. của trò chơi là gì?

Những tài sản này có nên được lưu trữ trong cặp khóa / giá trị Bản đồ không? Tức là tôi yêu cầu tài sản "nền" và Bản đồ trả về bitmap liên quan? Có cách nào tốt hơn không?

Cụ thể tôi đang viết một trò chơi Android / Java, nhưng câu trả lời có thể chung chung.

Câu trả lời:


16

Nó phụ thuộc vào phạm vi trò chơi của bạn. Một người quản lý tài sản là hoàn toàn cần thiết cho các tiêu đề lớn hơn, ít hơn cho các trò chơi nhỏ hơn.

Đối với các tiêu đề lớn hơn, bạn phải quản lý các vấn đề như sau:

  • Tài sản được chia sẻ - là kết cấu gạch đang được sử dụng bởi nhiều mô hình?
  • Tuổi thọ tài sản - tài sản bạn đã tải 15 phút trước không còn cần thiết nữa? Tham chiếu đếm tài sản của bạn để đảm bảo bạn biết khi nào một cái gì đó kết thúc với vv
  • Trong DirectX 9 nếu một số loại tài sản nhất định được tải và thiết bị đồ họa của bạn bị 'mất' (điều này xảy ra nếu bạn nhấn Ctrl + Alt + Del trong số những thứ khác) - trò chơi của bạn sẽ cần tạo lại chúng
  • Đang tải tài sản trước khi cần chúng - bạn không thể xây dựng các trò chơi thế giới mở lớn mà không có điều này
  • Tải tài sản hàng loạt - Chúng tôi thường đóng gói rất nhiều tài sản vào một tệp để cải thiện thời gian tải - việc tìm kiếm xung quanh đĩa rất tốn thời gian

Đối với các tiêu đề nhỏ hơn, những điều này không phải là vấn đề, các khung như XNA có người quản lý tài sản bên trong chúng - có rất ít điểm trong việc phát minh lại nó.

Nếu bạn thấy mình cần một người quản lý tài sản, thực sự không có giải pháp nào phù hợp với một kích thước, nhưng tôi đã thấy rằng một bản đồ băm với khóa là hàm băm * của tên tệp (được hạ thấp và phân tách tất cả 'cố định') hoạt động tốt cho các dự án tôi đã làm việc.

Thông thường không nên dùng tên mã hóa cứng trong ứng dụng của bạn, thường tốt hơn là có định dạng dữ liệu khác (chẳng hạn như xml) mô tả tên tệp thành 'ID'.

  • Là một lưu ý phụ thú vị, bạn thường nhận được một xung đột băm cho mỗi dự án.

Chỉ vì bạn cần quản lý tài sản không cần AssetManager, một danh từ quan trọng được viết hoa có lẽ có quá nhiều phương pháp, hiệu suất kém và ngữ nghĩa bộ nhớ lầy lội. Để so sánh, hãy nghĩ về những gì xảy ra nếu bạn có nhiều quản lý dự án (thường là tốt), và sau đó khi bạn có nhiều người quản lý dự án (thường là xấu).

2
@Joe Wreschnig - làm thế nào bạn giải quyết năm yêu cầu được đề cập bởi icStatic mà không cần sử dụng trình quản lý tài sản?
antinome

8

(Cố gắng tránh "không sử dụng người quản lý tài sản" -discussion ở đây, vì tôi coi đó là điều không chính đáng.)

Bản đồ khóa / giá trị là một cách tiếp cận rất có thể sử dụng.

Chúng tôi có một triển khai ResourceManager nơi các nhà máy cho các loại tài nguyên khác nhau có thể đăng ký.

Phương thức "getResource" sử dụng các mẫu để tìm Nhà máy chính xác cho nguồn lực mong muốn và trả về một ResourceHandle cụ thể (một lần nữa sử dụng mẫu để trả về một SpecResourceHandle).

Các tài nguyên được Resourcemanager (bên trong ResourceHandle) giới thiệu và phát hành khi không cần thiết nữa.

Addon đầu tiên chúng tôi viết là phương thức "tải lại (XYZ)", cho phép chúng tôi thay đổi tài nguyên từ bên ngoài công cụ đang chạy mà không thay đổi bất kỳ mã hoặc tải lại trò chơi. (Điều này rất cần thiết khi các nghệ sĩ làm việc trên bảng điều khiển;))

Hầu hết thời gian chúng tôi chỉ có phiên bản của Trình quản lý tài nguyên, nhưng đôi khi chúng tôi tạo một phiên bản mới chỉ cho một cấp độ hoặc bản đồ. Bằng cách này, chúng tôi chỉ có thể gọi "tắt máy" trên levelResourceManager và đảm bảo không có gì bị rò rỉ.

(tóm tắt) ví dụ

// very abbreviated!
// this code would never survive our coding guidelines ;)

ResourceManager* pRm = new ResourceManager;
pRm->initialize( );
pRm->registerFactory( new TextureFactory );
// [...]
TextureHandle tex = pRm->getResource<Texture>( "test.otx" ); // in real code we use some macro magic here to use CRCs for filenames
tex->storeToHardware( 0 ); // channel 0

pRm->releaseResource( pRm );

// [...]
pRm->shutdown(); // will log any leaked resource

6

Các lớp Manager dành riêng hầu như không bao giờ là công cụ kỹ thuật phù hợp. Nếu bạn chỉ cần tài sản một lần (như nền hoặc bản đồ), bạn chỉ nên yêu cầu một lần và để nó chết bình thường khi bạn hoàn thành nó. Nếu bạn cần lưu trữ một loại đối tượng cụ thể, bạn nên sử dụng một nhà máy trước tiên kiểm tra bộ đệm và tải một cái gì đó, đặt nó vào bộ đệm và sau đó trả về nó - và nhà máy đó có thể chỉ là một hàm tĩnh truy cập vào một biến tĩnh , không phải là một loại của riêng nó.

Steve Yegge (trong số nhiều người, nhiều người khác) đã viết một câu chuyện hay về cách các lớp quản lý vô dụng, bằng cách mô hình singleton, cuối cùng trở thành. http://sites.google.com.vn/site/steveyegge2/singleton-considered-stool


2
Được, chắc chắn rồi. Nhưng trong các trường hợp như Android (hoặc các trò chơi khác), bạn cần tải rất nhiều đồ họa / âm thanh vào bộ nhớ trước khi bắt đầu trò chơi, không phải trong thời gian. Làm cách nào tôi có thể sử dụng những gì bạn đang nói (nhà máy) để thực hiện việc này trong màn hình tải? Chỉ cần nhấn mọi đối tượng trong nhà máy trên màn hình tải để nó lưu trữ chúng?
Bryan Denny

Tôi không quen thuộc với các chi tiết của Android nhưng tôi không biết ý của bạn là gì trước khi "bắt đầu trò chơi". Có thực sự không thể tải tài nguyên khi bạn cần (hoặc khi bạn cần 'sớm') thay vì khi bạn bắt đầu chương trình? Tôi thấy điều đó cực kỳ khó xảy ra, ví dụ như bạn không bao giờ có thể có nhiều kết cấu hơn là phù hợp với RAM ít ỏi của Android.

@Joe hãy nhìn vào câu hỏi khác của tôi về "màn hình tải": gamedev.stackexchange.com/questions/1171/... Hitting một sản phẩm nào có nghĩa là bộ nhớ cache lần dài để đi để vào đĩa và có thể dẫn đến một số hit hiệu suất FPS trên những cuộc gọi đầu tiên . Nếu bạn đã biết những gì bạn sẽ đạt được trước thời hạn, cũng có thể đánh nó trong khi tải để lưu trước bộ đệm, phải không?
Bryan Denny

Một lần nữa tôi không thể nói chuyện với Android, nhưng thường thì vào đĩa là chính xác những gì bạn có thể làm mà không cần truy cập FPS, vì luồng vào đĩa sẽ không sử dụng bất kỳ CPU nào cả. Bạn chỉ cần dự trù ngân sách để thực hiện trước đủ để bạn không nhận được pop-in. Nếu bạn sắp lưu trước bộ đệm mọi thứ vì bạn biết trước những gì bạn cần, bạn thực sự không cần AssetManager, vì bạn hoàn toàn không cần phải quản lý tài sản - tất cả đã sẵn sàng.

1
@Joe, không phải nhà máy cũng là "Quản lý chuyên dụng" sao?
MSN

2

Tôi đã luôn nghĩ rằng một người quản lý tài sản tốt nên có nhiều chế độ hoạt động. Các chế độ này rất có thể là các mô-đun nguồn riêng biệt tuân thủ một giao diện chung. Hai chế độ hoạt động cơ bản sẽ là:

  • Chế độ sản xuất - tất cả tài sản là cục bộ và tước bỏ tất cả dữ liệu meta
  • Chế độ phát triển - các thử nghiệm được lưu trữ trong cơ sở dữ liệu (ví dụ: MySQL, v.v.) với dữ liệu meta bổ sung. Cơ sở dữ liệu sẽ là một hệ thống hai lớp với cơ sở dữ liệu cục bộ lưu trữ cơ sở dữ liệu dùng chung. Người tạo nội dung sẽ có thể chỉnh sửa và cập nhật cơ sở dữ liệu được chia sẻ và các bản cập nhật được tự động chuyển sang hệ thống nhà phát triển / QA. Nó cũng có thể tạo nội dung giữ chỗ. Vì mọi thứ đều nằm trong cơ sở dữ liệu, các truy vấn có thể được thực hiện trên cơ sở dữ liệu và các báo cáo được tạo để phân tích trạng thái của sản xuất.

Bạn sẽ cần một công cụ có thể lấy tất cả các thử nghiệm từ cơ sở dữ liệu được chia sẻ và tạo bộ dữ liệu sản xuất.

Trong những năm làm nhà phát triển, tôi chưa bao giờ thấy bất cứ điều gì như thế này, mặc dù tôi chỉ làm việc cho một số ít các công ty nên quan điểm của tôi không thực sự đại diện.

Cập nhật

OK, một số phiếu tiêu cực. Tôi sẽ mở rộng về thiết kế này.

Thứ nhất, bạn không thực sự cần các lớp học vì nếu bạn có:

TextureHandle tex = pRm->getResource<Texture>( "test.otx" );

bạn biết loại, vì vậy chỉ cần làm:

TextureHandle tex = new TextureHandle ("test.otx");

nhưng sau đó, điều tôi đã cố gắng nói ở trên là dù sao bạn cũng sẽ không sử dụng tên tệp rõ ràng, kết cấu cần tải sẽ được chỉ định bởi mô hình mà kết cấu được sử dụng, vì vậy bạn thực sự không cần tên người đọc được, nó có thể là một giá trị nguyên 32 bit, giúp CPU xử lý dễ dàng hơn nhiều. Vì vậy, trong hàm tạo cho TextureHandle, bạn có:

if (texture already loaded)
  update texture reference count
else
  asset_stream = new AssetStream (resource_id)
  asset_stream->ReadBytes
  create texture
  set texture ref count to 1

AssetStream sử dụng tham số resource_id để tìm vị trí của dữ liệu. Cách thức thực hiện việc này sẽ phụ thuộc vào môi trường bạn đang chạy:

Trong Phát triển: luồng tìm kiếm ID trong cơ sở dữ liệu (ví dụ sử dụng SQL) để lấy tên tệp và sau đó mở tệp, tệp có thể được lưu trữ cục bộ hoặc được kéo từ máy chủ nếu tệp cục bộ không tồn tại hoặc là hết hạn.

Trong Bản phát hành: luồng tìm kiếm ID trong bảng khóa / giá trị để lấy phần bù / kích thước thành một tệp lớn, được đóng gói (như tệp WAD của Doom).


Tôi đã bỏ phiếu cho bạn vì bạn đã đề xuất việc sắp xếp mọi thứ vào một bảng SQL với các khóa chính thay vì sử dụng một VCS thực sự. Ngoài ra tôi xem xét sử dụng ID mờ hơn là tối ưu hóa tên chuỗi sớm. Tôi đã sử dụng các chuỗi trên hai dự án lớn cho tất cả các tài sản không phải là khóa dịch, trong đó chúng tôi có hàng trăm nghìn khóa chuỗi rất dài (và sau đó chỉ chuyển sang bảng điều khiển). Chúng thường được chuẩn hóa để chúng ta có thể sử dụng so sánh con trỏ thay vì so sánh chuỗi, nhưng so sánh chuỗi thường bị chi phối bởi chi phí của bộ nhớ tìm nạp và không phải là so sánh thực tế.

@Joe: Tôi chỉ đưa ra SQL làm ví dụ và sau đó chỉ trong môi trường phát triển, bạn có thể sử dụng một VCS như nhau. Tôi chỉ đề xuất cơ sở dữ liệu SQL vì sau đó bạn có thể thêm thông tin bổ sung vào các đối tượng được lưu trữ và sử dụng các hàm SQL để truy vấn thông tin từ cơ sở dữ liệu (nhiều lợi ích quản lý hơn bất kỳ thứ gì khác). Đối với các ID mờ như tối ưu hóa sớm - một số người có thể thấy nó theo cách tôi đoán, nhưng tôi nghĩ rằng sẽ dễ dàng hơn để bắt đầu với điều đó hơn là hiển thị nó ở giai đoạn sau trong quá trình phát triển. Tôi không nghĩ nó sẽ ảnh hưởng đến sự phát triển nếu bạn sử dụng ID hoặc chuỗi.
Skizz

2

Những gì tôi muốn làm cho tài sản là để thiết lập một người quản lý một lần . Lấy cảm hứng từ công cụ Doom, các khối là các phần dữ liệu chứa tài sản, được lưu trữ trong một tệp cục khai báo tên, độ dài, loại (bitmap, âm thanh, đổ bóng, v.v.) và loại nội dung (tệp, một khối khác, bên trong các tập tin cục bộ). Khi khởi động, các khối này được nhập vào cây nhị phân, nhưng chưa được tải. Mỗi bản đồ (cũng là một cục) có một danh sách các phụ thuộc, đơn giản là tên của các khối mà bản đồ cần để hoạt động. Các cục này, trừ khi chúng đã được tải, được tải tại thời điểm bản đồ được tải. Ngoài ra, các khối bản đồ liền kề của bản đồ được tải, không chỉ cùng một lúc, nhưng khi động cơ không hoạt động vì một số lý do. Điều này có thể làm cho các bản đồ liền mạch, và không có màn hình tải.

Phương pháp của tôi là hoàn hảo cho các bản đồ thế giới mở, nhưng một trò chơi dựa trên cấp độ sẽ không được hưởng lợi từ sự liền mạch mà phương pháp này mang lại cho bạn. Hi vọng điêu nay co ich!

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.