Làm thế nào để tránh mã hóa cứng trong công cụ trò chơi


22

Câu hỏi của tôi không phải là một câu hỏi mã hóa; nó áp dụng cho tất cả các thiết kế công cụ trò chơi nói chung.

Làm thế nào để bạn tránh được mã hóa cứng?

Câu hỏi này sâu sắc hơn nhiều so với nó có vẻ. Nói, nếu bạn muốn chạy một trò chơi tải các tệp cần thiết cho hoạt động, làm thế nào để bạn tránh nói điều gì đó giống như load specificfile.wadtrong mã của động cơ? Ngoài ra, khi tập tin được tải, làm thế nào để bạn tránh nói load aspecificmap in specificfile.wad?

Câu hỏi này áp dụng cho khá nhiều thiết kế động cơ, và càng ít càng tốt cho động cơ nên được mã hóa cứng. Cách tốt nhất để đạt được điều đó là gì?

Câu trả lời:


42

Mã hóa dựa trên dữ liệu

Mỗi điều bạn đề cập là một cái gì đó có thể được chỉ định trong dữ liệu. Tại sao bạn đang tải aspecificmap? Bởi vì cấu hình trò chơi nói rằng đó là cấp độ đầu tiên khi người chơi bắt đầu một trò chơi mới hoặc vì đó là tên của điểm lưu hiện tại trong tệp lưu của người chơi mà họ vừa tải, v.v.

Làm thế nào để bạn tìm thấy aspecificmap? Bởi vì nó nằm trong tệp dữ liệu liệt kê id bản đồ và tài nguyên trên đĩa của chúng.

Chỉ cần có một bộ tài nguyên "cốt lõi" đặc biệt nhỏ, khó hợp pháp hoặc không thể tránh được mã hóa cứng. Với một chút công việc, điều này có thể được giới hạn trong một tên tài sản mặc định được mã hóa cứng giống như main.wadhoặc tương tự. Tập tin này có khả năng có thể được thay đổi trong thời gian chạy bằng cách chuyển một đối số dòng lệnh cho trò chơi, hay còn gọi là game.exe -wad mymain.wad.

Viết mã dựa trên dữ liệu dựa trên một vài nguyên tắc khác. Ví dụ, người ta có thể tránh việc các hệ thống hoặc mô-đun yêu cầu một tài nguyên cụ thể và thay vào đó đảo ngược các phụ thuộc đó. Đó là, không thực hiện DebugDrawertải lên debug.fonttrong mã khởi tạo của nó; thay vào đó, đã DebugDrawerxử lý tài nguyên trong mã khởi tạo của nó. Tay cầm đó có thể được tải từ tệp cấu hình trò chơi chính.

Như một ví dụ cụ thể từ cơ sở mã của chúng tôi, chúng tôi có một đối tượng "dữ liệu toàn cầu" được tải từ cơ sở dữ liệu tài nguyên (theo mặc định là ./resourcesthư mục nhưng có thể bị quá tải với một đối số dòng lệnh). ID cơ sở dữ liệu tài nguyên của dữ liệu toàn cầu này là tên tài nguyên được mã hóa cứng cần thiết duy nhất trong cơ sở mã (chúng tôi có những người khác vì đôi khi các lập trình viên trở nên lười biếng, nhưng cuối cùng chúng tôi vẫn sửa / xóa chúng. Đối tượng dữ liệu toàn cầu này có đầy đủ các thành phần với mục đích duy nhất là cung cấp dữ liệu cấu hình. Một trong các thành phần là thành phần UI Global Data chứa các thẻ điều khiển tài nguyên cho tất cả các tài nguyên UI chính (phông chữ, tệp Flash, biểu tượng, dữ liệu bản địa hóa, v.v.) trong số một số mục cấu hình khác. Khi nhà phát triển UI quyết định đổi tên tài sản UI chính từ /ui/mainmenu.swfthành/ui/lobby.swfhọ chỉ cập nhật dữ liệu tham khảo toàn cầu đó; không có mã động cơ cần phải thay đổi cả.

Chúng tôi sử dụng dữ liệu toàn cầu này cho tất cả mọi thứ. Tất cả các nhân vật có thể chơi, tất cả các cấp độ, giao diện người dùng, âm thanh, tài sản cốt lõi, cấu hình mạng, tất cả mọi thứ. (tốt, không phải tất cả mọi thứ , nhưng những thứ khác là lỗi cần sửa.)

Cách tiếp cận này có rất nhiều lợi thế khác. Đối với một, nó làm cho đóng gói tài nguyên và bó không thể thiếu cho toàn bộ quá trình. Các đường dẫn mã hóa cứng trong công cụ cũng có xu hướng có nghĩa là các đường dẫn tương tự đó phải được mã hóa cứng trong bất kỳ tập lệnh hoặc công cụ nào đóng gói tài sản trò chơi và sau đó các đường dẫn đó có thể không đồng bộ. Thay vào đó, dựa vào một tài sản cốt lõi và chuỗi tham chiếu duy nhất từ ​​đó, chúng ta có thể xây dựng một gói tài sản với một lệnh như thế bundle.exe -root config.data -out main.wadvà biết rằng nó sẽ bao gồm tất cả các tài sản mà chúng ta yêu cầu. Hơn nữa, vì trình đóng gói sẽ chỉ theo dõi các tài liệu tham khảo tài nguyên, chúng tôi biết rằng nó sẽ chỉ bao gồm các tài sản mà chúng tôi yêu cầu và bỏ qua tất cả các phần thừa còn lại tích lũy trong suốt vòng đời của dự án (cộng với chúng tôi có thể tự động tạo danh sách đó lông tơ để cắt tỉa).

Một trường hợp góc khó khăn của toàn bộ điều này là trong các kịch bản. Làm cho công cụ điều khiển dữ liệu dễ dàng về mặt khái niệm, nhưng tôi đã thấy rất nhiều dự án (sở thích với AAA) trong đó các tập lệnh được coi là dữ liệu và do đó "được phép" sử dụng các đường dẫn tài nguyên một cách bừa bãi. Đừng làm vậy. Nếu một tệp Lua cần một tài nguyên và nó chỉ gọi một hàm như thế textures.lua("/path/to/texture.png")thì đường dẫn tài sản sẽ gặp nhiều rắc rối khi biết rằng tập lệnh yêu cầu /path/to/texture.pngphải hoạt động chính xác và có thể coi kết cấu đó không được sử dụng và không cần thiết. Các tập lệnh nên được xử lý như bất kỳ mã nào khác: bất kỳ dữ liệu nào chúng cần bao gồm các tài nguyên hoặc bảng phải được chỉ định trong mục nhập cấu hình mà công cụ và đường ống tài nguyên có thể kiểm tra các phụ thuộc. Thay vào đó, dữ liệu có nội dung "tải tập lệnh foo.lua" sẽ nói "foo.luavà cung cấp cho nó các tham số này "trong đó các tham số bao gồm bất kỳ tài nguyên cần thiết nào. Ví dụ: nếu tập lệnh ngẫu nhiên sinh ra kẻ thù, hãy chuyển danh sách kẻ thù có thể vào tập lệnh từ tệp cấu hình đó. Sau đó, công cụ có thể tải trước kẻ thù với cấp độ ( vì nó biết danh sách đầy đủ các sinh sản có thể) và đường ống tài nguyên biết bó tất cả kẻ thù với trò chơi (vì chúng được tham chiếu chắc chắn bởi dữ liệu cấu hình). Nếu các tập lệnh tạo ra các chuỗi tên đường dẫn và chỉ gọi một loadhàm thì không công cụ cũng như đường ống tài nguyên có bất kỳ cách nào để biết cụ thể tài sản nào mà tập lệnh có thể tải.


Câu trả lời tốt, rất thực tế, và cũng giải thích những cạm bẫy và lỗi mà mọi người mắc phải khi thực hiện điều này! 1
whn

+1. Sẽ thêm rằng theo mô hình trỏ đến các tài nguyên chứa dữ liệu cấu hình cũng rất hữu ích nếu bạn muốn kích hoạt sửa đổi. Thật khó khăn và rủi ro hơn nhiều khi sửa đổi các trò chơi đòi hỏi bạn phải thay đổi các tệp dữ liệu gốc thay vì tự tạo và chỉ vào chúng. Thậm chí tốt hơn nếu bạn có thể trỏ vào nhiều tệp với thứ tự ưu tiên xác định.
Jeutnarg

12

Giống như cách bạn tránh mã hóa cứng trong các chức năng chung.

Bạn truyền tham số và bạn giữ thông tin của bạn trong các tệp cấu hình.

Trong tình huống đó, hoàn toàn không có sự khác biệt về công nghệ phần mềm giữa việc viết một công cụ và viết một lớp.

MgrAssets
public:
  errorCode loadAssetFromDisk( filePath )
  errorCode getMap( mapName, map& )

private:
  maps[name, map]

Sau đó, mã máy khách của bạn đọc tệp cấu hình "chính" (tệp này được mã hóa cứng hoặc được truyền dưới dạng đối số dòng lệnh) chứa thông tin cho biết vị trí tệp tài sản và bản đồ chứa chúng.

Từ đó, mọi thứ được điều khiển bởi tập tin cấu hình "chính".


1
Đúng, cộng với một số loại cơ chế để đưa vào logic tùy chỉnh. Có thể bằng cách nhúng một ngôn ngữ như C #, python, v.v. để mở rộng các tính năng cốt lõi của động cơ theo chức năng do người dùng xác định
qCring

3

Tôi thích các câu trả lời khác vì vậy tôi sẽ có một chút trái ngược. ;)

Bạn không thể tránh kiến ​​thức mã hóa về dữ liệu của bạn vào công cụ của bạn. Bất cứ nơi nào thông tin đến, động cơ phải biết để tìm kiếm nó. Tuy nhiên, bạn có thể tránh mã hóa thông tin thực tế vào động cơ của mình.

Một cách tiếp cận dựa trên dữ liệu "thuần túy" sẽ giúp bạn bắt đầu thực thi với (các) tham số dòng lệnh cần thiết để tải cấu hình ban đầu nhưng động cơ sẽ phải được mã hóa để biết cách diễn giải thông tin đó. Ví dụ như nếu các tập tin cấu hình của bạn là JSON, bạn cần phải cứng mã biến bạn tìm kiếm, ví dụ như động cơ sẽ phải biết để tìm kiếm "intro_movies""level_list"và vân vân.

Tuy nhiên, một công cụ "được xây dựng tốt" có thể hoạt động cho nhiều trò chơi khác nhau chỉ bằng cách hoán đổi dữ liệu cấu hình và dữ liệu mà nó tham chiếu.

Vì vậy, câu thần chú không phải là quá nhiều để tránh mã hóa cứng vì nó là để đảm bảo rằng bạn có thể thực hiện các thay đổi với ít nỗ lực nhất có thể.

Để tương phản với cách tiếp cận tệp dữ liệu (mà tôi hết lòng hỗ trợ), có thể bạn đồng ý biên dịch dữ liệu vào công cụ của mình. Nếu "chi phí" của việc đó thấp hơn, thì không có hại thực sự; nếu bạn là người duy nhất làm việc với nó thì bạn có thể trì hoãn việc xử lý tệp cho một ngày sau đó và không nhất thiết phải tự làm phiền mình. Một vài dự án trò chơi đầu tiên của tôi có các bảng dữ liệu lớn được mã hóa vào chính trò chơi, ví dụ như một danh sách các loại vũ khí và dữ liệu các loại của chúng:

struct Weapon
{
    enum IconID icon;
    enum ModelID model;
    int damage;
    int rateOfFire;
    // etc...
};

const struct Weapon g_weapons[] =
{
    { ICON_PISTOL, MODEL_PISTOL, 5, 6 },
    { ICON_RIFLE, MODEL_RIFLE, 10, 20 },
    // etc...
};

Vì vậy, bạn đặt dữ liệu này ở đâu đó dễ tham khảo và dễ dàng chỉnh sửa khi cần thiết. Lý tưởng nhất là đưa công cụ này vào một tệp cấu hình nào đó nhưng sau đó bạn cần thực hiện phân tích cú pháp và dịch và tất cả nhạc jazz đó, cộng với việc nối các tham chiếu liên cấu trúc có thể trở thành một nỗi đau mà bạn thực sự không muốn đôi pho vơi.


Nó không quá khó để phân tích json. "Chi phí" duy nhất liên quan là học tập. (Cụ thể, học cách sử dụng mô-đun hoặc thư viện thích hợp. Ví dụ: Go có hỗ trợ json tốt.)
Wildcard

Nó không quá khó khăn nhưng nó đòi hỏi phải làm điều đó ngoài việc học. Ví dụ: Tôi biết cách phân tích kỹ thuật JSON, tôi đã viết trình phân tích cú pháp cho nhiều định dạng tệp khác, nhưng tôi cần tìm và cài đặt giải pháp của bên thứ ba (và tìm ra các phụ thuộc và cách xây dựng nó) hoặc tự tạo. Mất nhiều thời gian hơn không làm điều đó.
dash-tom-bang

4
Mọi thứ cần nhiều thời gian hơn là không làm điều đó. Nhưng các công cụ bạn cần đã được viết. Giống như bạn không phải thiết kế trình biên dịch để viết trò chơi, hoặc tìm hiểu kỹ về mã máy, nhưng bạn phải học một ngôn ngữ cho nền tảng mà bạn đang làm việc. Vì vậy, học cách sử dụng một trình phân tích cú pháp json, cũng.
tự đại diện

Tôi không chắc lập luận của bạn là gì. Trong câu trả lời này, tôi ủng hộ YAGNI; nếu bạn không cần phải dành / lãng phí thời gian để làm điều gì đó không giúp ích gì cho bạn thì đừng. Nếu bạn muốn dành thời gian cho điều đó thì tuyệt vời. Có thể bạn sẽ cần dành thời gian sau đó, có thể bạn sẽ không làm thế, nhưng thực hiện nó trước chỉ khiến bạn mất tập trung vào nhiệm vụ thực sự làm game. Phát triển trò chơi là tầm thường; mỗi một nhiệm vụ đi vào làm một trò chơi đều đơn giản. Chỉ là hầu hết các trò chơi có một triệu nhiệm vụ đơn giản và một nhà phát triển có trách nhiệm chọn những nhiệm vụ đạt được mục tiêu đó nhanh nhất.
dash-tom-bang

2
Trên thực tế, tôi nêu lên câu trả lời của bạn; không có tranh luận thực sự như vậy. Tôi chỉ muốn lưu ý rằng JSON không khó phân tích. Đọc lại, tôi cho rằng tôi chủ yếu trả lời đoạn trích "nhưng sau đó bạn cần phải phân tích cú pháp và dịch và tất cả những bản jazz đó." Nhưng tôi đồng ý rằng đối với các trò chơi dự án cá nhân và như vậy, YAGNI. :)
tự đại diện
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.