Làm thế nào để kết nối hệ thống thực thể này?


33

Tôi đã thiết kế một hệ thống thực thể cho FPS. Về cơ bản nó hoạt động như thế này:

Chúng tôi có một "thế giới" -object, được gọi là GameWorld. Cái này chứa một mảng GameObject, cũng như một mảng của ElementManager.

GameObject giữ một mảng Thành phần. Nó cũng cung cấp một cơ chế sự kiện rất đơn giản. Bản thân các thành phần có thể gửi một sự kiện đến thực thể, được phát cho tất cả các thành phần.

Thành phần về cơ bản là thứ mang lại cho GameObject một số thuộc tính nhất định và vì GameObject thực sự chỉ là một thùng chứa của chúng, nên mọi thứ liên quan đến một đối tượng trò chơi đều xảy ra trong Thành phần. Ví dụ bao gồm ViewComponent, ChemistryComponent và LogicComponent. Nếu cần liên lạc giữa chúng, điều đó có thể được thực hiện thông qua việc sử dụng các sự kiện.

Trình quản lý thành phần chỉ là một giao diện giống như Thành phần và đối với mỗi lớp Thành phần, thường sẽ có một lớp Thành phần. Các trình quản lý thành phần này chịu trách nhiệm tạo các thành phần và khởi tạo chúng với các thuộc tính được đọc từ một cái gì đó giống như tệp XML.

ElementManager cũng đảm nhiệm việc cập nhật hàng loạt các thành phần, như PhysComponent nơi tôi sẽ sử dụng một thư viện bên ngoài (nơi thực hiện mọi thứ trên thế giới cùng một lúc).

Để cấu hình, tôi sẽ sử dụng một nhà máy cho các thực thể sẽ đọc tệp XML hoặc tập lệnh, tạo các thành phần được chỉ định trong tệp (cũng thêm một tham chiếu đến nó trong trình quản lý thành phần phù hợp để cập nhật hàng loạt) và sau đó tiêm chúng vào một đối tượng GameObject.

Bây giờ đến vấn đề của tôi: Tôi sẽ cố gắng sử dụng điều này cho các trò chơi nhiều người chơi. Tôi không có ý tưởng làm thế nào để tiếp cận điều này.

Thứ nhất: Khách hàng nên có những thực thể nào ngay từ đầu? Tôi nên bắt đầu với việc giải thích làm thế nào một công cụ một người chơi sẽ xác định những thực thể cần tạo.

Trong trình chỉnh sửa cấp độ, bạn có thể tạo "cọ" và "thực thể". Bàn chải dành cho những thứ như tường, sàn và trần nhà, về cơ bản là những hình dạng đơn giản. Các thực thể là GameObject tôi đã nói với bạn. Khi tạo các thực thể trong trình chỉnh sửa cấp, bạn có thể chỉ định các thuộc tính cho từng thành phần của nó. Các thuộc tính này được truyền trực tiếp đến một cái gì đó giống như hàm tạo trong tập lệnh của thực thể.

Khi bạn lưu mức cho động cơ tải, nó sẽ được phân tách thành một danh sách các thực thể và các thuộc tính liên quan của chúng. Các cọ vẽ được chuyển đổi thành một thực thể "worldspawn".

Khi bạn tải mức đó, nó chỉ kích hoạt tất cả các thực thể. Nghe có vẻ đơn giản nhỉ?

Bây giờ, để kết nối các thực thể tôi gặp phải nhiều vấn đề. Đầu tiên, những thực thể nào nên tồn tại trên máy khách ngay từ đầu? Giả sử rằng cả máy chủ và máy khách đều có tệp cấp độ, máy khách cũng có thể cung cấp tất cả các thực thể trong cấp độ, ngay cả khi chúng ở đó chỉ vì mục đích của quy tắc trò chơi trên máy chủ.

Một khả năng khác là máy khách kích hoạt một thực thể ngay khi máy chủ gửi thông tin về nó và điều đó có nghĩa là máy khách sẽ chỉ có các thực thể mà nó cần.

Một vấn đề khác là làm thế nào để gửi thông tin. Tôi nghĩ rằng máy chủ có thể sử dụng nén delta, nghĩa là nó chỉ gửi thông tin mới khi có gì đó thay đổi, thay vì gửi ảnh chụp nhanh đến máy khách ở mọi khung hình. Mặc dù điều đó có nghĩa là máy chủ phải theo dõi những gì mỗi khách hàng biết vào lúc này.

Và cuối cùng, mạng nên được tiêm vào động cơ như thế nào? Tôi đang nghĩ về một thành phần, NetworkComponent, được đưa vào mọi thực thể được cho là được nối mạng. Nhưng làm thế nào nên các mạng thành phần bí quyết biến vào mạng, và như thế nào để truy cập vào đó, và cuối cùng là như thế nào các thành phần mạng tương ứng trên máy khách nên biết làm thế nào để thay đổi các biến mạng?

Tôi đang gặp khó khăn lớn khi tiếp cận điều này. Tôi thực sự sẽ đánh giá cao nếu bạn giúp tôi trên đường. Tôi cũng mở các mẹo về cách cải thiện thiết kế hệ thống thành phần, vì vậy đừng ngại đề xuất điều đó.

Câu trả lời:


13

Đây là một con quái vật chết tiệt (ân xá) của một câu hỏi với rất nhiều chi tiết +1 ở đó. Chắc chắn đủ để giúp những người vấp ngã trên nó.

Tôi chỉ muốn thêm 2 xu của mình về việc không gửi dữ liệu vật lý !! Tôi thực sự không thể nhấn mạnh điều này đủ. Ngay cả khi bạn đã tối ưu hóa đến mức bạn thực tế có thể gửi 40 quả cầu nảy xung quanh với va chạm vi mô và nó có thể chạy hết tốc lực trong phòng rung lắc thậm chí không làm giảm tốc độ khung hình. Tôi đang đề cập đến việc thực hiện "nén-mã hóa / mã hóa" của bạn, còn được gọi là phân biệt dữ liệu mà bạn đã thảo luận. Nó khá giống với những gì tôi sẽ đưa lên.

Xác định khác biệt dữ liệu VS: Chúng đủ khác nhau và thực sự không chiếm cùng một phương thức, nghĩa là bạn có thể thực hiện cả hai để tăng tối ưu hóa hơn nữa! Lưu ý: Tôi chưa sử dụng cả hai cùng nhau, nhưng đã làm việc với cả hai.

Mã hóa Delta hoặc phân biệt dữ liệu: Máy chủ mang dữ liệu về những gì khách hàng biết và chỉ gửi những khác biệt giữa dữ liệu cũ và những gì nên được thay đổi cho khách hàng. ví dụ: giả trong một ví dụ bạn có thể gửi dữ liệu "315 435 222 3546 33" khi dữ liệu đã "310 435 210 4000 40" Một số chỉ thay đổi một chút và không thay đổi chút nào! Thay vào đó, bạn sẽ gửi (theo đồng bằng) "5 0 12 -454 -7" ngắn hơn đáng kể.

Các ví dụ tốt hơn có thể là một cái gì đó thay đổi xa hơn nhiều so với ví dụ đó, giả sử tôi có một danh sách được liên kết với 45 đối tượng được liên kết trong đó ngay bây giờ. Tôi muốn giết 30 người trong số họ, vì vậy tôi làm điều đó, sau đó gửi cho mọi người dữ liệu gói mới là gì, nó sẽ làm chậm máy chủ nếu nó không được xây dựng để làm những việc như thế này và nó đã xảy ra vì nó đang cố gắng để sửa chính nó chẳng hạn. Trong mã hóa delta, bạn chỉ cần đặt (giả) "list.kill 30 at 5" và nó sẽ xóa 30 đối tượng khỏi danh sách sau lần thứ 5 sau đó xác thực dữ liệu, nhưng trên mỗi máy khách thay vì máy chủ.

Ưu điểm: (Chỉ có thể nghĩ về một trong mỗi ngay bây giờ)

  1. Tốc độ: Rõ ràng trong ví dụ cuối cùng của tôi, tôi đã mô tả. Nó sẽ là một sự khác biệt lớn hơn nhiều so với ví dụ trước. Nói chung, tôi không thể nói một cách trung thực từ kinh nghiệm nào trong số đó sẽ phổ biến hơn, vì tôi làm việc nhiều hơn với việc tính toán chết

Nhược điểm:

  1. Nếu bạn đang cập nhật hệ thống của mình và muốn thêm một số dữ liệu cần được chỉnh sửa thông qua đồng bằng, bạn sẽ phải tạo các chức năng mới để thay đổi dữ liệu đó! (ví dụ như trước đó "list.kill 30 at 5" Oh shit Tôi cần một phương thức hoàn tác được thêm vào máy khách! "list.kill undo")

Xác định chết: Nói một cách đơn giản, đây là một sự tương tự. Tôi đang viết bản đồ cho ai đó về cách đến một địa điểm và tôi chỉ bao gồm các điểm cần đi chung, vì nó đủ tốt (dừng tại tòa nhà, rẽ trái). Bản đồ của ai đó bao gồm tên đường và bao nhiêu độ để rẽ trái, điều đó có cần thiết không? (Không...)

Xác định chết là nơi mỗi khách hàng có một thuật toán không đổi trên mỗi khách hàng. Dữ liệu được thay đổi khá nhiều bằng cách cho biết dữ liệu nào cần thay đổi và cách thực hiện. Máy khách tự thay đổi dữ liệu. Một ví dụ là nếu tôi có một nhân vật không phải là người chơi của tôi, nhưng đang bị một người khác chơi cùng tôi di chuyển, tôi không nên cập nhật dữ liệu mỗi khung hình vì rất nhiều dữ liệu phù hợp!

Hãy nói rằng tôi có nhân vật của mình di chuyển theo một số hướng, nhiều máy chủ sẽ gửi dữ liệu đến các máy khách cho biết (gần như trên mỗi khung hình) nơi người chơi đang ở và nó đang di chuyển (vì lý do hoạt hình). Đó là rất nhiều dữ liệu không cần thiết! Tại sao tôi cần phải cập nhật từng khung hình, đơn vị ở đâu và hướng của nó VÀ nó đang di chuyển? Nói một cách đơn giản: Tôi không. Bạn chỉ cập nhật máy khách khi hướng thay đổi, khi động từ thay đổi (isMoving = true?) Và đối tượng là gì! Sau đó, mỗi khách hàng sẽ di chuyển đối tượng phù hợp.

Cá nhân đây là một chiến thuật của lẽ thường. Đó là một cái gì đó tôi nghĩ rằng tôi đã thông minh trong một thời gian dài trước đây, hóa ra nó được sử dụng mọi lúc.

Đáp án

Thành thật mà nói, hãy đọc bài viết của James và đọc những gì tôi nói về dữ liệu. Có, bạn chắc chắn nên sử dụng mã hóa delta, nhưng hãy nghĩ về việc sử dụng tính toán chết.

Cá nhân tôi sẽ khởi tạo dữ liệu trên máy khách, khi nó nhận được thông tin về nó từ máy chủ (một cái gì đó bạn đề xuất).

Chỉ những đối tượng có thể thay đổi mới được ghi nhận là có thể chỉnh sửa ở vị trí đầu tiên phải không? Tôi thích ý tưởng của bạn về việc bao gồm một đối tượng nên có dữ liệu mạng, thông qua hệ thống thành phần và thực thể của bạn! Đó là thông minh, và nên làm việc tốt. Nhưng bạn không bao giờ nên đưa bàn chải (hoặc bất kỳ dữ liệu nào hoàn toàn phù hợp) vào bất kỳ phương thức kết nối mạng nào. Họ không cần nó, vì đó là thứ thậm chí không thể thay đổi (đó là khách hàng).

Nếu nó giống như một cánh cửa, tôi sẽ cung cấp cho nó dữ liệu mạng nhưng chỉ là một boolean về việc nó có mở hay không, thì rõ ràng đó là loại đối tượng nào. Khách hàng nên biết cách thay đổi nó, ví dụ như mở, đóng nó, mỗi khách hàng nhận được rằng tất cả họ nên đóng nó, vì vậy bạn thay đổi dữ liệu boolean, sau đó làm động cửa để đóng.

Về việc làm thế nào để biết các biến nào trong mạng, tôi có thể có một thành phần thực sự là một đối tượng SUB và cung cấp cho nó các thành phần mà bạn muốn nối mạng. Một ý tưởng khác là không chỉ có AddComponent("whatever")mà còn AddNetComponent("and what have you")bởi vì nó có vẻ thông minh hơn cá nhân.


Đây là một câu trả lời dài vô lý! Tôi vô cùng xin lỗi về điều đó. Vì tôi dự định chỉ cung cấp một lượng nhỏ kiến ​​thức và sau đó là 2 xu của tôi về một số thứ. Vì vậy, tôi hiểu rằng rất nhiều trong số đó có thể là một chút không cần thiết để lưu ý.
Joshua Hedges

3

Sẽ viết một bình luận nhưng quyết định đây có thể là đủ thông tin cho một câu trả lời.

Đầu tiên, +1 cho một câu hỏi được viết độc đáo như vậy với hàng tấn chi tiết để đánh giá câu trả lời.

Để tải dữ liệu, tôi sẽ cho khách hàng tải thế giới từ tệp thế giới. Nếu các thực thể của bạn có Id trong chúng xuất phát từ tệp dữ liệu thì tôi cũng sẽ tải chúng theo mặc định để hệ thống mạng của bạn chỉ cần tham khảo chúng để biết đối tượng nào đang nói đến. Mọi người tải cùng một dữ liệu ban đầu có nghĩa là tất cả họ đều có cùng Id cho các đối tượng đó.

Thứ hai, không tạo thành phần NetworkComponent vì điều này sẽ không làm gì ngoài việc sao chép dữ liệu trong các thành phần hiện có khác (vật lý, hoạt hình và những thứ tương tự là một số thứ phổ biến để gửi qua). Để sử dụng cách đặt tên của riêng bạn, bạn có thể muốn tạo NetworkComponentManager. Điều này sẽ hơi khác so với mối quan hệ Thành phần đến Thành phần khác mà bạn có nhưng điều này có thể được khởi tạo khi bạn bắt đầu trò chơi nối mạng và có bất kỳ loại thành phần nào có khía cạnh kết nối mạng để họ cung cấp dữ liệu của họ cho người quản lý để họ có thể đóng gói và gửi nó đi. Đây là nơi chức năng Lưu / Tải của bạn có thể được sử dụng nếu bạn có một cơ chế tuần tự hóa / giải tuần tự hóa nào đó mà bạn cũng có thể sử dụng để đóng gói dữ liệu, như đã đề cập,

Với câu hỏi và mức độ thông tin của bạn, tôi không nghĩ rằng tôi cần phải đi sâu vào chi tiết hơn, nhưng nếu có gì không rõ ràng, vui lòng gửi bình luận và tôi sẽ cập nhật câu trả lời để giải quyết vấn đề này.

Hi vọng điêu nay co ich.


Vì vậy, những gì bạn đang nói là các thành phần nên được nối mạng nên thực hiện một số loại giao diện như thế này?: Void SetNetworkedVariable (tên chuỗi, giá trị NetworkedVariable); NetworkedVariable GetNetworkedVariable (tên chuỗi); Trong đó NetworkedVariable được sử dụng cho mục đích nội suy và các công cụ mạng khác. Tôi không biết làm thế nào để xác định các thành phần thực hiện điều này mặc dù. Tôi có thể sử dụng nhận dạng loại thời gian chạy, nhưng điều đó có vẻ xấu với tôi.
Carter
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.