Làm thế nào tôi nên cấu trúc một hệ thống tải tài sản mở rộng?


19

Đối với một công cụ trò chơi sở thích trong Java, tôi muốn mã hóa một trình quản lý tài sản / tài nguyên đơn giản nhưng linh hoạt. Tài sản là âm thanh, hình ảnh, hình ảnh động, mô hình, kết cấu, et cetera. Sau một vài giờ duyệt web và một số thử nghiệm mã, tôi vẫn không chắc làm thế nào để thiết kế thứ này.

Cụ thể, tôi đang tìm cách tôi có thể thiết kế trình quản lý theo cách để nó tóm tắt cách thức các loại tài sản cụ thể được tải và nơi tài sản được tải từ đó. Tôi muốn có thể hỗ trợ cả hệ thống tệp và lưu trữ RDBMS mà không cần phần còn lại của chương trình cần biết về nó. Tương tự, tôi muốn thêm một tài sản mô tả hoạt hình (FPS, khung để kết xuất, tham chiếu đến hình ảnh sprite, et cetera) đó là XML. Tôi sẽ có thể viết một lớp cho việc này với chức năng tìm và đọc tệp XML và tạo và trả về một AnimationAssetlớp có thông tin đó. Tôi đang tìm kiếm một thiết kế dựa trên dữ liệu .

Tôi có thể tìm thấy nhiều thông tin về những gì một người quản lý tài sản nên làm, nhưng không biết làm thế nào để làm điều đó. Các thế hệ liên quan dường như dẫn đến một số dạng xếp tầng của các lớp, hoặc một số dạng của các lớp trợ giúp. Tuy nhiên tôi chưa thấy một ví dụ rõ ràng nào không giống như một vụ hack cá nhân, hay một điểm đồng thuận.

Câu trả lời:


23

Tôi sẽ bắt đầu bằng cách không nghĩ về một người quản lý tài sản . Suy nghĩ về kiến ​​trúc của bạn theo các thuật ngữ được xác định một cách lỏng lẻo (như "người quản lý") có xu hướng cho phép bạn quét sạch nhiều chi tiết dưới tấm thảm, và do đó, việc giải quyết một giải pháp trở nên khó khăn hơn.

Tập trung vào các nhu cầu cụ thể của bạn, điều này dường như sẽ làm với việc tạo ra một cơ chế tải tài nguyên trừu tượng hóa bộ lưu trữ gốc bên dưới và cho phép mở rộng bộ loại được hỗ trợ. Chẳng có gì thực sự trong câu hỏi của bạn liên quan đến, ví dụ, bộ nhớ đệm của các tài nguyên đã được tải - điều này tốt, bởi vì theo nguyên tắc trách nhiệm duy nhất, có lẽ bạn nên xây dựng bộ đệm tài sản như một thực thể riêng biệt và tổng hợp hai giao diện ở nơi khác , khi thích hợp.

Để giải quyết mối quan tâm cụ thể của bạn, bạn nên thiết kế trình tải của mình để nó không tự tải bất kỳ tài sản nào, mà thay vào đó ủy thác trách nhiệm cho các giao diện được điều chỉnh để tải các loại tài sản cụ thể. Ví dụ:

interface ITypeLoader {
  object Load (Stream assetStream);
}

Bạn có thể tạo các lớp mới thực hiện giao diện này, với mỗi lớp mới được tùy chỉnh để tải một loại dữ liệu cụ thể từ một luồng. Bằng cách sử dụng một luồng, trình tải loại có thể được viết dựa trên giao diện phổ biến, không lưu trữ và không phải mã hóa cứng để tải từ đĩa hoặc cơ sở dữ liệu; điều này thậm chí sẽ cho phép bạn tải tài sản của mình từ các luồng mạng (có thể rất hữu ích trong việc triển khai tải lại tài sản khi trò chơi của bạn đang chạy trên bảng điều khiển và các công cụ chỉnh sửa của bạn trên PC được kết nối mạng).

Trình tải tài sản chính của bạn cần có khả năng đăng ký và theo dõi các trình tải cụ thể loại này:

class AssetLoader {
  public void RegisterType (string key, ITypeLoader loader) {
    loaders[key] = loader;
  }

  Dictionary<string, ITypeLoader> loaders = new Dictionary<string, ITypeLoader>();
}

"Khóa" được sử dụng ở đây có thể là bất cứ thứ gì bạn thích - và nó không cần phải là một chuỗi, nhưng chúng không dễ bắt đầu. Chìa khóa sẽ quyết định đến cách bạn mong muốn người dùng xác định một tài sản cụ thể và sẽ được sử dụng để tra cứu trình tải phù hợp. Vì bạn muốn che giấu sự thật rằng việc triển khai có thể đang sử dụng một hệ thống tệp hoặc cơ sở dữ liệu, bạn không thể có người dùng tham chiếu đến các tài sản theo đường dẫn hệ thống tệp hoặc bất cứ thứ gì tương tự.

Người dùng nên tham khảo một tài sản với thông tin tối thiểu. Trong một số trường hợp, chỉ một tên tệp là đủ, nhưng tôi thấy rằng thường sử dụng một cặp loại / tên nên mọi thứ đều rất rõ ràng. Do đó, người dùng có thể tham khảo một thể hiện được đặt tên của một trong các tệp XML hoạt hình của bạn là "AnimationXml","PlayerWalkCycle".

Ở đây, AnimationXmlsẽ là chìa khóa mà bạn đã đăng ký AnimationXmlLoader, thực hiện IAssetLoader. Rõ ràng, PlayerWalkCyclexác định tài sản cụ thể. Đặt tên loại và tên tài nguyên, trình tải tài sản của bạn có thể truy vấn bộ lưu trữ liên tục của nó để tìm các byte thô của tài sản đó. Vì chúng tôi sẽ có tính tổng quát tối đa ở đây, bạn có thể thực hiện điều này bằng cách chuyển cho trình tải một phương tiện truy cập lưu trữ khi bạn tạo nó, cho phép bạn thay thế phương tiện lưu trữ bằng bất kỳ thứ gì có thể cung cấp luồng sau này:

interface IAssetStreamProvider {
  Stream GetStream (string type, string name);
}

class AssetLoader {
  public AssetLoader (IAssetStreamProvider streamProvider) {
    provider = streamProvider;
  }

  object LoadAsset (string type, string name) {
    var loader = loaders[type];
    var stream = provider.GetStream(type, name);

    return loader.Load(stream);
  }

  public void RegisterType (string type, ITypeLoader loader) {
    loaders[type] = loader;
  }

  IAssetStreamProvider provider;
  Dictionary<string, ITypeLoader> loaders = new Dictionary<string, ITypeLoader>();
}

Một nhà cung cấp luồng rất đơn giản sẽ chỉ cần tìm trong một thư mục gốc tài sản được chỉ định cho một thư mục con có tên typevà tải các byte thô của tệp có tên namevào một luồng và trả về nó.

Nói tóm lại, những gì bạn có ở đây là một hệ thống trong đó:

  • Có một lớp biết cách đọc byte thô từ một loại lưu trữ phụ trợ (đĩa, cơ sở dữ liệu, luồng mạng, bất cứ điều gì).
  • Có các lớp biết cách biến một luồng byte thô thành một loại tài nguyên cụ thể và trả về nó.
  • "Trình tải tài sản" thực tế của bạn chỉ có một tập hợp các aboves trên và biết cách đưa đầu ra của nhà cung cấp luồng vào trình tải cụ thể theo loại và do đó tạo ra một tài sản cụ thể. Bằng cách đưa ra các cách để định cấu hình nhà cung cấp luồng và trình tải cụ thể theo loại, bạn có một hệ thống có thể được mở rộng bởi khách hàng (hoặc chính bạn) mà không phải sửa đổi mã trình tải tài sản thực tế.

Một số cảnh báo và ghi chú cuối cùng:

  • Đoạn mã trên về cơ bản là C #, nhưng nên dịch sang bất kỳ ngôn ngữ nào với nỗ lực tối thiểu. Để tạo điều kiện cho điều này, tôi đã bỏ qua rất nhiều thứ như kiểm tra lỗi hoặc sử dụng đúng cách IDisposablevà các thành ngữ khác có thể không áp dụng trực tiếp trong các ngôn ngữ khác. Những người còn lại là bài tập về nhà cho người đọc.

  • Tương tự, tôi trả lại tài sản cụ thể như objecttrên, nhưng bạn có thể sử dụng chung hoặc mẫu hoặc bất cứ thứ gì để tạo ra một loại đối tượng cụ thể hơn nếu bạn muốn (bạn nên làm việc với nó).

  • Như trên, tôi không đối phó với bộ nhớ đệm ở đây. Tuy nhiên, bạn có thể thêm bộ nhớ đệm dễ dàng và với cùng loại tổng quát và cấu hình. Hãy thử nó và xem!

  • Có rất nhiều và rất nhiều cách để làm điều này, và chắc chắn không có cách nào hay sự đồng thuận, đó là lý do tại sao bạn không thể tìm thấy một cách. Tôi đã cố gắng cung cấp đủ mã để có được các điểm cụ thể mà không biến câu trả lời này thành một đoạn mã dài. Nó đã quá dài như nó là. Nếu bạn có câu hỏi làm rõ, hãy bình luận hoặc tìm tôi trong cuộc trò chuyện .


1
Câu hỏi hay và câu trả lời tốt thúc đẩy giải pháp hướng tới không chỉ là thiết kế hướng dữ liệu mà còn là cách bắt đầu suy nghĩ theo cách hướng dữ liệu.
Patrick Hughes

Câu trả lời rất hay và sâu sắc. Tôi thích cách bạn giải thích câu hỏi của tôi và nói với tôi chính xác những gì tôi cần biết trong khi tôi xây dựng nó rất kém. Cảm ơn! Trong mọi trường hợp, bạn có thể chỉ cho tôi một số tài nguyên về Luồng không?
dùng8363

"Luồng" chỉ là một chuỗi (có khả năng không có kết thúc có thể xác định) của byte hoặc dữ liệu. Tôi đã suy nghĩ cụ thể về Luồng của C # , nhưng có lẽ bạn quan tâm nhiều hơn đến các lớp luồng của Java - mặc dù được cảnh báo tôi không biết quá nhiều Java nên có thể không phải là một lớp lý tưởng để sử dụng.

Các luồng thường ở trạng thái, trong đó một đối tượng luồng đã cho thường có vị trí đọc hoặc ghi hiện tại trong luồng và bất kỳ IO nào bạn thực hiện trên vị trí đó xảy ra từ vị trí đó - đó là lý do tại sao tôi sử dụng chúng làm đầu vào cho giao diện nội dung ở trên, bởi vì về cơ bản họ đang nói "đây là một số dữ liệu thô và bắt đầu đọc từ đâu, đọc từ đó và làm việc của bạn."

Cách tiếp cận này tôn vinh một số nguyên tắc cốt lõi của cả RẮNOOP . Bravo.
Adam Naylor
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.