Tại sao việc lưu trữ các phương thức trong Thực thể và Thành phần là một ý tưởng tồi? (Cùng với một số câu hỏi khác về Hệ thống thực thể.)


16

Đây là phần tiếp theo của câu hỏi này , tôi đã trả lời, nhưng bài này đã giải quyết một chủ đề cụ thể hơn nhiều.

Câu trả lời này đã giúp tôi hiểu Entity Systems thậm chí còn tốt hơn bài báo.

Tôi đã đọc bài viết (vâng,) về Hệ thống thực thể và nó đã cho tôi biết như sau:

Các thực thể chỉ là một id và một mảng các thành phần (các bài báo nói rằng lưu trữ các thực thể trong các thành phần không phải là một cách tốt để làm việc, nhưng không cung cấp một sự thay thế).
Các thành phần là những phần dữ liệu, cho biết những gì có thể được thực hiện với một thực thể nhất định.
Hệ thống là "phương thức", chúng thực hiện thao tác dữ liệu trên các thực thể.

Điều này có vẻ thực tế trong nhiều tình huống, nhưng phần về các thành phần chỉ là các lớp dữ liệu đang làm phiền tôi. Ví dụ, làm thế nào tôi có thể triển khai lớp Vector2D (Position) của mình trong Hệ thống thực thể?

Lớp Vector2D chứa dữ liệu: tọa độ x và y, nhưng nó cũng có các phương thức , điều này rất quan trọng đối với tính hữu dụng của nó và phân biệt lớp với chỉ một mảng hai phần tử. Ví dụ phương pháp là: add(), rotate(point, r, angle), substract(), normalize(), và tất cả các tiêu chuẩn khác, phương pháp hữu ích, và hoàn toàn cần thiết mà các vị trí (trong đó có trường hợp của lớp Vector2D) nên có.

Nếu thành phần chỉ là một chủ sở hữu dữ liệu, nó sẽ không thể có các phương thức này!

Một giải pháp có thể bật lên là triển khai chúng bên trong các hệ thống, nhưng điều đó có vẻ rất phản trực giác. Những phương pháp này là những thứ mà tôi muốn thực hiện ngay bây giờ , để chúng được hoàn thiện và sẵn sàng để sử dụng. Tôi không muốn chờ đợi MovementSystemđể đọc một số thông điệp đắt tiền hướng dẫn nó thực hiện phép tính trên vị trí của một thực thể!

Và, bài báo nêu rất rõ rằng chỉ các hệ thống nên có bất kỳ chức năng nào , và lời giải thích duy nhất cho điều đó, mà tôi có thể tìm thấy, là "để tránh OOP". Trước hết, tôi không hiểu tại sao tôi không nên sử dụng các phương thức trong các thực thể và thành phần. Chi phí bộ nhớ thực tế là như nhau, và khi được kết hợp với các hệ thống, chúng sẽ rất dễ thực hiện và kết hợp theo những cách thú vị. Các hệ thống, chẳng hạn, chỉ có thể cung cấp logic cơ bản cho các thực thể / thành phần, vốn tự biết việc thực hiện. Nếu bạn hỏi tôi - về cơ bản là lấy những điều tốt đẹp từ cả ES và OOP, điều gì đó không thể được thực hiện theo tác giả của bài viết, nhưng đối với tôi có vẻ như là một thực hành tốt.

Nghĩ về nó theo cách này; có nhiều loại đối tượng có thể vẽ khác nhau trong một trò chơi. Hình ảnh cũ đơn giản, hình ảnh động ( update(), getCurrentFrame()v.v.), sự kết hợp của các loại nguyên thủy này và tất cả chúng chỉ có thể cung cấp một draw()phương thức cho hệ thống kết xuất, mà sau đó không cần quan tâm đến cách thức sprite của một thực thể, chỉ về giao diện (vẽ) và vị trí. Và sau đó, tôi sẽ chỉ cần một hệ thống hoạt hình sẽ gọi các phương thức dành riêng cho hoạt hình không liên quan gì đến kết xuất.

Và chỉ một điều khác ... Có thực sự có một sự thay thế cho mảng khi lưu trữ các thành phần? Tôi thấy không có nơi nào khác cho các thành phần được lưu trữ ngoài các mảng bên trong một lớp Thực thể ...

Có thể, đây là một cách tiếp cận tốt hơn: lưu trữ các thành phần như các thuộc tính đơn giản của các thực thể. Ví dụ, một thành phần vị trí sẽ được dán vào entity.position.

Cách duy nhất khác là có một loại bảng tra cứu kỳ lạ bên trong các hệ thống, tham chiếu các thực thể khác nhau. Nhưng điều đó dường như rất không hiệu quả và phức tạp hơn để phát triển hơn là chỉ đơn giản là lưu trữ các thành phần trong thực thể.


Alexandre bạn chỉ đang thực hiện rất nhiều chỉnh sửa để có được một huy hiệu khác? Bởi vì đó là nghịch ngợm nghịch ngợm, nó tiếp tục va chạm rất nhiều chủ đề cổ xưa.
jhocking

Câu trả lời:


25

Tôi nghĩ sẽ hoàn toàn ổn khi có các phương pháp đơn giản để truy cập, cập nhật hoặc thao tác dữ liệu trong các thành phần. Tôi nghĩ rằng chức năng nên tránh xa các thành phần là chức năng logic. Các chức năng tiện ích là tốt. Hãy nhớ rằng, hệ thống thành phần thực thể chỉ là một hướng dẫn, không phải là các quy tắc nghiêm ngặt bạn cần tuân theo. Đừng đi theo cách của bạn để theo dõi họ. Nếu bạn nghĩ nó có ý nghĩa hơn để làm theo một cách, thì hãy làm theo cách đó :)

BIÊN TẬP

Để làm rõ, mục tiêu của bạn không phải là tránh OOP . Điều đó sẽ khá khó khăn trong hầu hết các ngôn ngữ phổ biến được sử dụng ngày nay. Bạn đang cố gắng giảm thiểu sự kế thừa , một khía cạnh lớn của OOP, nhưng không bắt buộc. Bạn muốn thoát khỏi Object-> MobileObject-> Creature-> Bipedal-> Kiểu thừa kế kiểu người.

Tuy nhiên, nó có thể có một số thừa kế! Bạn đang đối phó với một ngôn ngữ bị ảnh hưởng nặng nề bởi sự kế thừa, rất khó để không sử dụng bất kỳ ngôn ngữ nào. Ví dụ: bạn có thể có một Componentlớp hoặc giao diện mà tất cả các thành phần khác của bạn mở rộng hoặc thực hiện. Cùng thỏa thuận với Systemlớp học của bạn . Điều này làm cho mọi thứ dễ dàng hơn nhiều. Tôi thực sự khuyên bạn nên xem qua khuôn khổ Artemis . Đó là nguồn mở và nó có một số dự án ví dụ. Mở những thứ đó lên và xem nó hoạt động như thế nào.

Đối với Artemis, các thực thể được lưu trữ trong một mảng, đơn giản. Tuy nhiên, các thành phần của chúng được lưu trữ trong một mảng hoặc mảng (tách biệt với các thực thể). Mảng cấp cao nhất nhóm các mảng cấp thấp hơn theo loại thành phần. Vì vậy, mỗi loại thành phần có mảng riêng. Mảng cấp thấp hơn được lập chỉ mục bởi ID thực thể. (Bây giờ tôi không chắc liệu tôi có làm theo cách đó không, nhưng đó là cách nó được thực hiện ở đây). Artemis sử dụng lại ID thực thể, vì vậy ID thực thể tối đa không lớn hơn số lượng thực thể hiện tại của bạn, nhưng bạn vẫn có thể có các mảng thưa thớt nếu thành phần không phải là thành phần được sử dụng thường xuyên. Dù sao, tôi sẽ không chọn điều đó quá nhiều. Phương pháp này để lưu trữ các thực thể và các thành phần của chúng dường như hoạt động. Tôi nghĩ rằng nó sẽ vượt qua đầu tiên tuyệt vời trong việc thực hiện hệ thống của riêng bạn.

Các thực thể và các thành phần được lưu trữ trong một trình quản lý riêng biệt.

Chiến lược mà bạn đề cập, làm cho các thực thể lưu trữ các thành phần của riêng chúng ( entity.position), là loại chống lại chủ đề thành phần thực thể, nhưng nó hoàn toàn chấp nhận được nếu bạn cảm thấy điều đó hợp lý nhất.


1
Hmm, điều đó rất đơn giản hóa tình hình, cảm ơn! Tôi nghĩ rằng có một số phép thuật "bạn sẽ hối hận về sau" điều này xảy ra, và tôi không thể nhìn thấy nó!
jcora

1
Na, tôi hoàn toàn sử dụng chúng trong hệ thống thành phần thực thể của tôi. Tôi thậm chí có một số thành phần kế thừa từ cha mẹ chung, thở hổn hển . Tôi nghĩ rằng sự hối tiếc duy nhất bạn sẽ làm là nếu bạn cố gắng làm việc xung quanh không sử dụng các phương pháp như thế. Đó là tất cả về việc làm những gì có ý nghĩa nhất đối với bạn. Nếu nó có ý nghĩa để sử dụng kế thừa hoặc đặt một số phương thức trong một thành phần, đi cho nó.
MichaelHouse

2
Tôi đã học được từ câu trả lời cuối cùng của tôi về chủ đề này. Disclaimer: Tôi không nói rằng đây là các cách để làm điều đó. :)
MichaelHouse

1
Vâng, tôi biết làm thế nào nản chí có thể học được một mô hình mới. May mắn thay, bạn có thể sử dụng các khía cạnh của mô hình cũ để làm cho mọi thứ dễ dàng hơn! Tôi đã cập nhật câu trả lời của mình với thông tin lưu trữ. Nếu bạn nhìn vào Artemis, hãy xem EntityManagerđó là nơi lưu trữ mọi thứ.
MichaelHouse

1
Đẹp! Đó sẽ là một động cơ khá ngọt ngào khi nó được thực hiện. Chúc may mắn với điều đó! Cảm ơn đã hỏi những câu hỏi thú vị.
MichaelHouse

10

Bài báo 'Đó' không phải là một bài tôi đặc biệt đồng ý, vì vậy câu trả lời của tôi sẽ hơi nghiêm trọng.

Điều này có vẻ thực tế trong nhiều tình huống, nhưng phần về các thành phần chỉ là các lớp dữ liệu đang làm phiền tôi. Ví dụ, làm thế nào tôi có thể triển khai lớp Vector2D (Position) của mình trong Hệ thống thực thể?

Ý tưởng không đảm bảo rằng không có gì trong chương trình của bạn là bất cứ thứ gì ngoài ID thực thể, thành phần hoặc hệ thống - để đảm bảo rằng dữ liệu thực thể và hành vi được tạo thông qua việc soạn thảo các đối tượng thay vì sử dụng cây thừa kế phức tạp hoặc tệ hơn là cố gắng đặt tất cả các chức năng có thể vào một đối tượng. Để thực hiện các thành phần và hệ thống này, bạn chắc chắn sẽ có dữ liệu bình thường như vectơ, trong hầu hết các ngôn ngữ, được biểu diễn tốt nhất dưới dạng một lớp.

Bỏ qua bit trong bài viết cho thấy đây không phải là OOP - nó cũng giống như OOP như mọi cách tiếp cận khác. Khi hầu hết các trình biên dịch hoặc runtimes ngôn ngữ thực hiện phương pháp đối tượng, nó là cơ bản giống như bất kỳ chức năng khác, ngoại trừ có một cuộc tranh luận ẩn được gọi là thishay self, mà là một con trỏ đến một vị trí trong bộ nhớ nơi dữ liệu của đối tượng được lưu trữ. Trong một hệ thống dựa trên thành phần, ID thực thể có thể được sử dụng để tìm vị trí của các thành phần có liên quan (và do đó là dữ liệu) cho một thực thể nhất định. Do đó, ID thực thể tương đương với con trỏ này / tự và các khái niệm về cơ bản là giống nhau, chỉ cần sắp xếp lại một chút.

Và, bài báo nêu rất rõ rằng chỉ các hệ thống nên có bất kỳ chức năng nào, và lời giải thích duy nhất cho điều đó, mà tôi có thể tìm thấy, là "để tránh OOP". Trước hết, tôi không hiểu tại sao tôi không nên sử dụng các phương thức trong các thực thể và thành phần.

Tốt Phương pháp là một cách hiệu quả để tổ chức mã của bạn. Điều quan trọng cần loại bỏ khỏi ý tưởng "tránh OOP" là tránh sử dụng tính kế thừa ở mọi nơi để mở rộng chức năng. Thay vào đó, hãy chia chức năng thành các thành phần có thể được kết hợp để làm điều tương tự.

Nghĩ về nó theo cách này; có nhiều loại đối tượng có thể vẽ khác nhau trong một trò chơi. Hình ảnh cũ đơn giản, hình ảnh động (update (), getCienFrame (), v.v.), kết hợp các loại nguyên thủy này và tất cả chúng chỉ có thể cung cấp phương thức draw () cho hệ thống kết xuất [...]

Ý tưởng của một hệ thống dựa trên thành phần là bạn sẽ không có các lớp riêng biệt cho các lớp này, nhưng sẽ có một lớp Object / Entity duy nhất và hình ảnh sẽ là Object / Entity có ImageRenderer, Animations sẽ là Object / Thực thể có AnimationRenderer, v.v. Các hệ thống có liên quan sẽ biết cách kết xuất các thành phần này và do đó không cần phải có bất kỳ lớp cơ sở nào có phương thức Draw ().

[...] mà sau đó không cần quan tâm đến cách thức sprite của một thực thể, chỉ về giao diện (vẽ) và vị trí. Và sau đó, tôi sẽ chỉ cần một hệ thống hoạt hình sẽ gọi các phương thức dành riêng cho hoạt hình không liên quan gì đến kết xuất.

Chắc chắn, nhưng điều này không hoạt động tốt với các thành phần. Bạn có 3 sự lựa chọn:

  • Mọi thành phần đều thực hiện giao diện này và có phương thức Draw (), ngay cả khi không có gì được rút ra. Nếu bạn đã làm điều này cho mỗi bit chức năng thì các thành phần sẽ trông khá xấu xí.
  • Chỉ các thành phần có thứ gì đó để vẽ mới thực hiện giao diện - nhưng ai quyết định thành phần nào sẽ gọi Draw () trên? Có một hệ thống phải bằng cách nào đó truy vấn từng thành phần để xem giao diện nào được hỗ trợ? Điều đó sẽ dễ bị lỗi và có khả năng khó thực hiện trong một số ngôn ngữ.
  • Các thành phần chỉ được xử lý bởi hệ thống sở hữu của chúng (đó là ý tưởng trong bài viết được liên kết). Trong trường hợp đó, giao diện không liên quan vì một hệ thống biết chính xác loại hoặc đối tượng mà nó hoạt động.

Và chỉ một điều khác ... Có thực sự có một sự thay thế cho mảng khi lưu trữ các thành phần? Tôi thấy không có nơi nào khác cho các thành phần được lưu trữ ngoài các mảng bên trong một lớp Thực thể ...

Bạn có thể lưu trữ các thành phần trong hệ thống. Mảng không phải là vấn đề, nhưng nơi bạn lưu trữ thành phần là.


+1 Cảm ơn quan điểm khác. Thật tốt khi có được một vài khi đối phó với một chủ đề mơ hồ như vậy! Nếu bạn đang lưu trữ các thành phần trong hệ thống, điều đó có nghĩa là các thành phần đó chỉ có thể được sửa đổi bởi một hệ thống? Ví dụ, cả hệ thống vẽ và hệ thống chuyển động đều sẽ truy cập vào thành phần vị trí. Bạn cất nó ở đâu?
MichaelHouse

Chà, họ sẽ chỉ lưu trữ một con trỏ đến các thành phần đó, điều đó có thể, miễn là tôi quan tâm ở đâu đó ... Ngoài ra, tại sao bạn lại lưu trữ các thành phần trong các hệ thống? Có một lợi thế cho điều đó?
jcora

Tôi có đúng không, @Kylotan? Đó là cách tôi sẽ làm điều đó, có vẻ hợp lý ...
jcora

Trong ví dụ của Adam / T-Machine, ý định là có 1 hệ thống cho mỗi thành phần, nhưng một hệ thống chắc chắn có thể truy cập và sửa đổi các thành phần khác. (Điều này cản trở lợi ích đa luồng của các thành phần, nhưng đó là một vấn đề khác.)
Kylotan

1
Lưu trữ các thành phần trong hệ thống cho phép địa phương tham chiếu tốt hơn cho hệ thống đó - chỉ hệ thống đó (nói chung) hoạt động với dữ liệu đó, vậy tại sao lại đi khắp bộ nhớ máy tính của bạn từ thực thể sang thực thể để có được nó? Nó cũng giúp đồng thời vì bạn có thể đặt toàn bộ hệ thống và dữ liệu của nó trên một lõi hoặc bộ xử lý (hoặc thậm chí là một máy tính riêng biệt, trong MMO). Một lần nữa, những lợi ích này giảm đi khi 1 hệ thống truy cập nhiều hơn 1 loại thành phần, do đó cần tính đến khi quyết định phân chia trách nhiệm thành phần / hệ thống.
Kylotan

2

Một vectơ là dữ liệu. Các hàm giống như các hàm tiện ích - chúng không đặc trưng cho trường hợp dữ liệu đó, chúng có thể được áp dụng cho tất cả các vectơ một cách độc lập. Một cách nghĩ hay về nó là: Các hàm này có thể được viết lại thành các phương thức tĩnh không? Nếu vậy, nó chỉ là tiện ích.


Tôi biết điều đó, nhưng vấn đề là các phương thức gọi nhanh hơn và có thể được thực hiện tại chỗ bởi một hệ thống, hoặc bất cứ điều gì khác có thể cần để thao túng vị trí của một thực thể. Tôi đã giải thích rằng, hãy kiểm tra nó, ngoài ra, câu hỏi có nhiều điều hơn thế, tôi tin điều đó.
jcora
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.