Tách Game Engine khỏi mã trò chơi trong các trò chơi tương tự, với phiên bản


15

Tôi có một trò chơi đã hoàn thành, tôi muốn từ chối trong các phiên bản khác. Đây sẽ là những trò chơi tương tự, với ít nhiều cùng một kiểu thiết kế, nhưng không phải lúc nào, về cơ bản mọi thứ có thể thay đổi, đôi khi rất ít, đôi khi lớn.

Tôi muốn mã lõi được phiên bản riêng biệt với trò chơi, vì vậy nếu nói rằng tôi sửa một lỗi được tìm thấy trong trò chơi A, bản sửa lỗi sẽ có mặt trong trò chơi B.

Tôi đang cố gắng tìm cách tốt nhất để quản lý điều đó. Ý tưởng ban đầu của tôi là thế này:

  • Tạo một enginemô-đun / thư mục / bất cứ thứ gì, có chứa mọi thứ có thể khái quát và độc lập 100% với phần còn lại của trò chơi. Điều này sẽ bao gồm một số mã, nhưng cũng có tài sản chung được chia sẻ giữa các trò chơi.
  • Đặt công cụ này vào gitkho lưu trữ riêng của nó , sẽ được đưa vào các trò chơi dưới dạnggit submodule

Phần tôi đang đấu tranh là làm thế nào để quản lý phần còn lại của mã. Giả sử bạn có cảnh menu của mình, mã này là dành riêng cho trò chơi, nhưng hầu hết chúng có xu hướng chung chung và có thể được sử dụng lại trong các trò chơi khác. Tôi không thể đặt nó vào engine, nhưng mã hóa lại cho mỗi trò chơi sẽ không hiệu quả.

Có thể sử dụng một số loại biến thể nhánh git có thể có hiệu quả để quản lý điều đó, nhưng tôi không cảm thấy đây là cách tốt nhất để đi.

Bất cứ ai cũng có một số ý tưởng, kinh nghiệm để chia sẻ hoặc bất cứ điều gì về điều đó?


Ngôn ngữ nào là động cơ của bạn trong? Một số ngôn ngữ có trình quản lý gói chuyên dụng có thể có ý nghĩa hơn so với sử dụng mô đun con git. Ví dụ: NodeJS có npm (có thể nhắm mục tiêu repo Git dưới dạng nguồn).
Dan Pantry

Câu hỏi của bạn về cách tốt nhất để cấu hình quản lý mã chung hay cách cấu hình quản lý mã "bán chung" hoặc cách kiến ​​trúc mã, cách thiết kế mã hay gì?
Dunk

Điều này có thể khác nhau trong từng Môi trường ngôn ngữ lập trình, nhưng, bạn có thể xem xét không chỉ Phần mềm phiên bản điều khiển mà còn biết bắt đầu với cách tách công cụ trò chơi khỏi mã trò chơi, (như gói, thư mục và API), và sau đó , áp dụng Phiên bản điều khiển.
umlcat

Làm thế nào để có một lịch sử sạch của một thư mục trong một nhánh: Tái cấu trúc động cơ của bạn để các repo riêng biệt (tương lai) nằm trong các thư mục riêng biệt, đó là cam kết cuối cùng của bạn. Sau đó tạo một nhánh mới, xóa mọi thứ bên ngoài thư mục đó và cam kết. Sau đó, đi đến cam kết đầu tiên của repo và hợp nhất nó với chi nhánh mới của bạn. Bây giờ bạn có một nhánh chỉ có thư mục đó: kéo nó trong các dự án khác và / hoặc hợp nhất nó lại với dự án hiện tại của bạn. Điều này giúp tôi rất nhiều với việc tách các công cụ trong các nhánh, nếu mã của bạn đã được tách riêng. Tôi không cần mô-đun git.
Barry Staes

Câu trả lời:


13

Tạo một mô-đun / thư mục động cơ / bất cứ thứ gì, có chứa mọi thứ có thể khái quát và độc lập 100% với phần còn lại của trò chơi. Điều này sẽ bao gồm một số mã, nhưng cũng có tài sản chung được chia sẻ giữa các trò chơi.

Đặt công cụ này vào kho git riêng của nó, sẽ được đưa vào các trò chơi dưới dạng mô hình con git

Đó chính xác là những gì tôi làm và nó hoạt động rất tốt. Tôi có một khung ứng dụng và thư viện kết xuất, và mỗi thư viện này được coi là mô hình con của các dự án của tôi. Tôi thấy SourceTree rất hữu ích khi nói đến các mô hình con, vì nó quản lý chúng tốt và sẽ không để bạn quên bất cứ điều gì, ví dụ: nếu bạn cập nhật mô hình con động cơ trong dự án A, nó sẽ thông báo cho bạn để thay đổi dự án B.

Với kinh nghiệm là kiến ​​thức về những gì nên có trong mã động cơ so với những gì nên theo từng dự án. Tôi đề nghị rằng nếu bạn thậm chí không chắc chắn, bạn nên giữ nó trong mỗi dự án cho đến bây giờ. Khi thời gian trôi qua, bạn sẽ thấy trong số các dự án khác nhau của mình vẫn giữ nguyên và sau đó bạn có thể dần dần đưa yếu tố đó vào mã động cơ của mình. Nói cách khác: mã trùng lặp cho đến khi bạn gần như chắc chắn 100% rằng nó không thay đổi một cách riêng biệt cho mỗi dự án, sau đó khái quát hóa nó.

Lưu ý về Kiểm soát nguồn và nhị phân

Chỉ cần nhớ rằng nếu bạn đang mong đợi các tài sản nhị phân của mình thay đổi thường xuyên, bạn có thể không muốn đặt chúng trong kiểm soát nguồn dựa trên văn bản như git. Chỉ cần nói rằng ... có những giải pháp tốt hơn cho nhị phân. Điều đơn giản nhất bạn có thể làm bây giờ để giúp giữ cho kho lưu trữ "nguồn động cơ" của bạn sạch sẽ và có một kho lưu trữ "nhị phân động cơ" riêng biệt chỉ chứa các nhị phân, mà bạn cũng bao gồm như một mô hình con trong dự án của bạn. Bằng cách này, bạn giảm thiểu thiệt hại về hiệu năng đối với kho lưu trữ "nguồn động cơ" của mình, vốn đang thay đổi mọi lúc và do đó bạn cần lặp lại nhanh: cam kết, đẩy, kéo, v.v. Các hệ thống quản lý kiểm soát nguồn như git hoạt động trên deltas văn bản và ngay khi bạn giới thiệu các tệp nhị phân, bạn sẽ giới thiệu các đồng bằng lớn từ góc độ văn bản - điều cuối cùng khiến bạn mất thời gian.Phụ lục GitLab . Bạn của bạn.


Họ không thực sự thay đổi thường xuyên, nhưng tôi quan tâm đến điều đó. Tôi không biết gì về phiên bản nhị phân. Có những giải pháp nào?
Malharhak

@Malharhak Chỉnh sửa để trả lời bình luận của bạn.
Kỹ sư

@Malharak Đây là một chút thông tin về chủ đề này.
Kỹ sư

1
+1 để giữ mọi thứ trong dự án càng lâu càng tốt. Mã thông thường cho độ phức tạp cao hơn. Nó nên được tránh cho đến khi hoàn toàn cần thiết.
Gusdor

1
@Malharhak Không, đặc biệt là mục tiêu của bạn chỉ là giữ "bản sao" cho đến khi bạn lưu ý rằng mã nói là bất biến và có thể được coi là phổ biến. Gusdor nhắc lại điều này - được cảnh báo - người ta có thể dễ dàng lãng phí hàng đống thời gian bằng cách phát hiện ra mọi thứ quá sớm, sau đó cố gắng tìm cách giữ cho mã đó đủ chung để duy trì phổ biến, nhưng có thể thích nghi đủ để phù hợp với các dự án khác nhau ... một toàn bộ rất nhiều thông số và chuyển mạch và nó biến thành một mớ hỗn độn xấu xí mà vẫn không phải là những gì bạn cần bởi vì bạn kết thúc thay đổi nó mỗi dự án mới nào . Đừng bỏ qua quá sớm . Hãy kiên nhẫn.
Kỹ sư

6

Tại một số thời điểm, một công cụ PHẢI chuyên môn hóa và biết các công cụ về trò chơi. Tôi sẽ đi vào một tiếp tuyến ở đây.

Lấy tài nguyên trong một RTS. Một trò chơi có thể có CreditsCrystalmột trò chơi khác MetalPotatoes

Bạn nên sử dụng các khái niệm OO đúng cách và đi tối đa. tái sử dụng mã. Rõ ràng là một khái niệm Resourcetồn tại ở đây.

Vì vậy, chúng tôi quyết định tài nguyên có những điều sau đây:

  1. Một cái móc trong vòng lặp chính để tăng / giảm bản thân
  2. Một cách để có được số tiền hiện tại (trả về một int)
  3. Một cách để trừ / thêm tùy ý (người chơi chuyển tài nguyên, mua hàng ....)

Lưu ý rằng khái niệm này Resourcecó thể đại diện cho tiêu diệt hoặc điểm trong trò chơi! Nó không mạnh lắm.

Bây giờ hãy nghĩ về một trò chơi. Chúng ta có thể sắp xếp tiền tệ bằng cách giao dịch bằng đồng xu và thêm dấu thập phân vào đầu ra. Những gì chúng ta không thể làm là tài nguyên "tức thời". Giống như nói "phát điện lưới"

Hãy nói rằng bạn thêm một InstantResourcelớp với các phương thức tương tự. Bây giờ bạn (bắt đầu) làm ô nhiễm động cơ của bạn với tài nguyên.


Vấn đề

Hãy lấy ví dụ RTS một lần nữa. Giả sử người chơi tặng bất cứ thứ gì Crystalcho người chơi khác. Bạn muốn làm một cái gì đó như:

if(transfer.target == engine.getPlayerId()) {
    engine.hud.addIncoming("You got "+transfer.quantity+" of "+
        engine.resourceDictionary.getNameOf(transfer.resourceId)+
        " from "+engine.getPlayer(transfer.source).name);
}
engine.getPlayer(transfer.target).getResourceById(transfer.resourceId).add(transfer.quantity)
engine.getPlayer(transfer.source).getResourceById(transfer.resourceId).add(-transfer.quantity)

Tuy nhiên điều này thực sự khá lộn xộn. Đó là mục đích chung, nhưng lộn xộn. Mặc dù nó áp đặt resourceDictionaryđiều đó có nghĩa là bây giờ tài nguyên của bạn phải có tên! VÀ nó là cho mỗi người chơi, vì vậy bạn không thể có tài nguyên nhóm nữa.

Đây là sự trừu tượng "quá nhiều" (không phải là một ví dụ tuyệt vời tôi sẽ thừa nhận) thay vào đó bạn nên nhấn vào điểm mà bạn chấp nhận rằng trò chơi của bạn có người chơi và pha lê, sau đó bạn có thể có (ví dụ)

engine.getPlayer(transfer.target).crystal().receiveDonation(transfer)
engine.getPlayer(transfer.source).crystal().sendDonation(transfer)

Với một lớp Playervà một lớp học CurrentPlayernơi CurrentPlayer's crystalđối tượng sẽ tự động hiển thị các công cụ trên HUD cho việc chuyển giao / gửi tặng.

Điều này gây ô nhiễm động cơ với pha lê, việc tặng pha lê, các thông điệp trên HUD cho người chơi hiện tại và tất cả những thứ đó. Nó nhanh hơn và dễ dàng hơn để đọc / ghi / duy trì (điều này quan trọng hơn, vì nó không nhanh hơn đáng kể)


Chú thích cuối

Trường hợp tài nguyên không xuất sắc. Tôi hy vọng bạn vẫn có thể nhìn thấy điểm mặc dù. Nếu bất cứ điều gì tôi đã chứng minh rằng "tài nguyên không thuộc về động cơ" như những gì một trò chơi cụ thể cần và những gì có thể áp dụng cho tất cả các khái niệm tài nguyên là RẤT những thứ khác nhau. Những gì bạn thường sẽ tìm thấy là 3 (hoặc 4) "lớp"

  1. "Lõi" - đây là định nghĩa của sách giáo khoa về động cơ, nó là một biểu đồ cảnh với các móc sự kiện, nó liên quan đến các shader và gói mạng và một khái niệm trừu tượng về người chơi
  2. "GameCore" - Đây là loại game khá chung chung nhưng không phải tất cả các trò chơi - ví dụ như tài nguyên trong RTS hoặc đạn dược trong FPS. Logic trò chơi bắt đầu thấm vào đây. Đây là nơi mà khái niệm tài nguyên trước đây của chúng tôi sẽ là. Chúng tôi đã thêm những điều này có ý nghĩa đối với hầu hết các tài nguyên RTS.
  3. "GameLogic" RẤT cụ thể cho trò chơi thực tế đang được thực hiện. Bạn sẽ tìm thấy các biến có tên như creaturehoặc shiphoặc squad. Sử dụng thừa kế bạn sẽ nhận được các lớp học mà span tất cả 3 lớp (ví dụ Crystal là một Resource trong đó là một GameLoopEventListener tiếng nói)
  4. "Tài sản" này là vô dụng đối với bất kỳ trò chơi nào khác. Lấy ví dụ các tập lệnh AI kết hợp trong nửa đời 2, chúng sẽ không được sử dụng trong RTS với cùng một công cụ.

Tạo một trò chơi mới từ một công cụ cũ

Đây là RẤT phổ biến. Giai đoạn 1 là tách ra các lớp 3 và 4 (và 2 nếu trò chơi là loại HOÀN TOÀN khác nhau) Giả sử chúng ta đang tạo RTS từ một RTS cũ. Chúng ta vẫn có tài nguyên, không phải là tinh thể và công cụ - vì vậy các lớp cơ sở trong lớp 2 và 1 vẫn có ý nghĩa, tất cả những gì tinh thể được tham chiếu trong 3 và 4 có thể bị loại bỏ. Vì vậy chúng tôi làm. Tuy nhiên, chúng tôi có thể kiểm tra nó như một tài liệu tham khảo cho những gì chúng tôi muốn làm.


Ô nhiễm ở lớp 1

Điều này có thể xảy ra. Trừu tượng và hiệu suất là kẻ thù. Ví dụ, UE4 cung cấp rất nhiều trường hợp sáng tác được tối ưu hóa (vì vậy nếu bạn muốn X và Y có ai đó đã viết mã X và Y cùng nhau rất nhanh - nó biết rằng nó đang thực hiện cả hai) và kết quả là THỰC SỰ khá lớn. Điều này không tệ nhưng nó tốn thời gian. Lớp 1 sẽ quyết định những thứ như "cách bạn truyền dữ liệu cho các shader" và cách bạn làm động mọi thứ. Làm điều đó cách tốt nhất cho dự án của bạn là LUÔN LUÔN tốt. Chỉ cần cố gắng và lập kế hoạch cho tương lai, sử dụng lại mã là bạn của bạn, kế thừa nơi nó có ý nghĩa.


Phân loại các lớp

Cuối cùng (tôi hứa) đừng quá sợ các lớp. Động cơ là một thuật ngữ cổ xưa từ ngày xưa của các đường ống chức năng cố định, nơi các động cơ hoạt động khá giống nhau về mặt đồ họa (và kết quả là có rất nhiều điểm chung), đường ống lập trình đã biến điều này trên đầu và vì thế "lớp 1" bị ô nhiễm với bất kỳ hiệu ứng nào các nhà phát triển muốn đạt được. AI là tính năng phân biệt (vì vô số cách tiếp cận) của động cơ, bây giờ nó là AI và đồ họa.

Mã của bạn không nên được nộp trong các lớp này. Ngay cả công cụ Unreal nổi tiếng cũng có NHIỀU phiên bản khác nhau cho từng trò chơi khác nhau. Có một vài tệp (khác với cấu trúc dữ liệu có thể) không thay đổi. Điều này là tốt! Nếu bạn muốn tạo một trò chơi mới từ một trò chơi khác, sẽ mất hơn 30 phút. Điều quan trọng là lập kế hoạch, để biết những gì cần sao chép và dán và những gì để lại phía sau.


1

Đề xuất cá nhân của tôi về cách xử lý nội dung kết hợp giữa chung chung và cụ thể là làm cho nội dung động. Tôi sẽ lấy màn hình menu của bạn làm ví dụ. Nếu tôi hiểu nhầm những gì bạn đang yêu cầu, hãy cho tôi biết những gì bạn muốn biết và tôi sẽ điều chỉnh câu trả lời của tôi.

Có 3 thứ (gần như) luôn hiện diện trên một cảnh menu: nền, logo trò chơi và chính menu. Những điều này thường khác nhau dựa trên trò chơi. Những gì bạn có thể làm cho nội dung này là tạo MenuScreenGenerator trong công cụ của bạn, bao gồm 3 tham số đối tượng: BackGround, Logo và Menu. Cấu trúc cơ bản của 3 phần này cũng là một phần của động cơ của bạn, nhưng động cơ của bạn không thực sự nói cách các phần này được tạo ra, chỉ là những thông số bạn nên cung cấp cho chúng.

Sau đó, trong mã trò chơi thực tế của bạn, bạn tạo các đối tượng cho BackGround, Logo và Menu và bạn chuyển nó cho MenuScreenGenerator của bạn. Một lần nữa, bản thân trò chơi của bạn không xử lý cách tạo menu, đó là cho công cụ. Trò chơi của bạn chỉ cần nói cho động cơ biết nó sẽ trông như thế nào và nó nên ở đâu.

Về cơ bản, công cụ của bạn phải là một API mà trò chơi cho biết những gì sẽ hiển thị. Nếu được thực hiện đúng cách, công cụ của bạn sẽ thực hiện công việc khó khăn và bản thân trò chơi của bạn chỉ nên cho công cụ biết nên sử dụng tài sản nào, hành động nào cần thực hiện và thế giới trông như thế nào.

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.