Làm cách nào để cập nhật trạng thái thực thể và hình động trong trò chơi dựa trên thành phần?


10

Tôi đang cố gắng thiết kế một hệ thống thực thể dựa trên thành phần cho mục đích học tập (và sau này sử dụng trên một số trò chơi) và tôi gặp một số rắc rối khi cập nhật trạng thái thực thể.

Tôi không muốn có một phương thức update () bên trong Thành phần để ngăn chặn sự phụ thuộc giữa các Thành phần.

Những gì tôi hiện đang nghĩ là các thành phần giữ dữ liệu và các thành phần cập nhật hệ thống.

Vì vậy, nếu tôi có một trò chơi 2D đơn giản với một số thực thể (ví dụ: người chơi, kẻ thù1, kẻ thù2) có các thành phần Biến đổi, Chuyển động, Trạng thái, Hoạt hình và Kết xuất, tôi nghĩ rằng tôi nên có:

  • Một MovementSystem di chuyển tất cả các thành phần Movement và cập nhật các thành phần State
  • Và một Hệ thống kết xuất cập nhật các thành phần Hoạt hình (thành phần hoạt hình nên có một hoạt hình (nghĩa là một bộ khung / kết cấu) cho mỗi trạng thái và cập nhật nó có nghĩa là chọn hoạt hình tương ứng với trạng thái hiện tại (ví dụ: jump, move_left, v.v.) và cập nhật chỉ số khung). Sau đó, RenderSystem cập nhật các thành phần Kết xuất với kết cấu tương ứng với khung hiện tại của Hoạt hình của mỗi thực thể và hiển thị mọi thứ trên màn hình.

Tôi đã thấy một số triển khai như khung Artemis, nhưng tôi không biết làm thế nào để giải quyết tình huống này:

Hãy nói rằng trò chơi của tôi có các thực thể sau đây. Mỗi thực thể có một tập hợp các trạng thái và một hình động cho mỗi trạng thái:

  • người chơi: "nhàn rỗi", "di chuyển_right", "nhảy"
  • kẻ thù1: "di chuyển_up", "di chuyển_down"
  • địch2: "di chuyển_left", "di chuyển_right"

Các cách tiếp cận được chấp nhận nhất để cập nhật trạng thái hiện tại của mỗi thực thể là gì? Điều duy nhất tôi có thể nghĩ đến là có các hệ thống riêng biệt cho từng nhóm thực thể và các thành phần Bang và Hoạt hình riêng biệt để tôi có PlayerState, PlayerAnimation, Enemy1State, Enemy1Animation ... PlayerMovementSystem, PlayerRenderingSystem ... nhưng tôi nghĩ đây là một điều xấu giải pháp và phá vỡ mục đích của việc có một hệ thống dựa trên thành phần.

Như bạn có thể thấy, tôi khá lạc lõng ở đây, vì vậy tôi rất trân trọng bất kỳ sự giúp đỡ nào.

EDIT: Tôi nghĩ rằng giải pháp để thực hiện công việc này như tôi dự định là giải pháp này:

Bạn tạo ra chung chung và hoạt hình chung đủ để được sử dụng cho tất cả các thực thể. Dữ liệu họ chứa sẽ là công cụ sửa đổi để thay đổi những thứ như hoạt hình nào được phát hoặc trạng thái nào khả dụng. - Byte56

Bây giờ, tôi đang cố gắng tìm ra cách thiết kế 2 thành phần này đủ chung để tôi có thể sử dụng lại chúng. Có thể có một UID cho mỗi trạng thái (ví dụ: đi bộ, chạy ...) và lưu trữ hình ảnh động trong bản đồ vào AnimationComponent được khóa bởi mã định danh này là một giải pháp tốt?


Tôi giả sử bạn đã thấy điều này: Thay đổi trạng thái trong các thực thể hoặc thành phần ? Là câu hỏi của bạn về cơ bản khác với câu hỏi đó?
MichaelHouse

@ Byte56 Vâng, tôi đã đọc nó vài giờ trước. Giải pháp bạn đề xuất ở đó tương tự như ý tưởng mà tôi đã đưa ra ở đây. Nhưng vấn đề của tôi xuất hiện khi StateComponent và AnimationComponent không giống nhau cho tất cả các thực thể trong hệ thống. Tôi có nên chia hệ thống đó thành các hệ thống nhỏ hơn để xử lý các nhóm thực thể có cùng trạng thái và hoạt ảnh không? (xem phần cuối của bài viết gốc của tôi để làm rõ hơn)
miviclin

1
Bạn thực hiện statecomponentanimationcomponentđủ chung chung để được sử dụng cho tất cả các thực thể. Dữ liệu họ chứa sẽ là công cụ sửa đổi để thay đổi những thứ như hoạt hình nào được phát hoặc trạng thái nào khả dụng.
MichaelHouse

Khi bạn nói về sự phụ thuộc, bạn có nghĩa là phụ thuộc dữ liệu hoặc phụ thuộc thứ tự thực hiện? Ngoài ra, trong giải pháp đề xuất của bạn, MovementSystem bây giờ phải thực hiện tất cả các cách khác nhau để một cái gì đó có thể di chuyển? Có vẻ như nó đang phá vỡ ý tưởng về hệ thống dựa trên thành phần ...
ADB

@ADB Tôi đang nói về sự phụ thuộc dữ liệu. Để cập nhật hoạt hình (ví dụ: thay đổi từ hoạt hình move_right sang hoạt hình move_left) tôi cần biết trạng thái hiện tại của thực thể và tôi không thấy cách làm cho 2 thành phần này chung chung hơn.
miviclin

Câu trả lời:


5

IMHO Movementthành phần phải giữ trạng thái hiện tại ( Movement.state) và Animationthành phần sẽ quan sát các thay đổi Movement.statevà cập nhật hoạt ảnh hiện tại của nó ( Animation.animation), bằng cách sử dụng tra cứu đơn giản id trạng thái thành hoạt hình (như được đề xuất ở cuối OP). Rõ ràng điều này có nghĩa là Animationsẽ phụ thuộc vào Movement.

Một cấu trúc thay thế sẽ có một Statethành phần chung , có thể Animationquan sát và Movementsửa đổi, về cơ bản là mô hình-khung nhìn-bộ điều khiển (chuyển động trạng thái trong trường hợp này).

Một cách khác là để thực thể gửi một sự kiện đến các thành phần của nó khi trạng thái của nó thay đổi. Animationsẽ lắng nghe sự kiện này và cập nhật hoạt hình của nó cho phù hợp. Điều này giúp loại bỏ sự phụ thuộc, mặc dù bạn có thể lập luận rằng phiên bản phụ thuộc là một thiết kế minh bạch hơn.

Chúc may mắn.


Vì vậy, Animation quan sát Bang và Bang quan sát Phong trào ... Phụ thuộc vẫn còn nhưng tôi có thể thử. Liệu phương án cuối cùng có giống như thế này không: Phong trào thông báo các thay đổi cho thực thể và thực thể gửi một sự kiện tới Bang, và sau đó quy trình tương tự sẽ được lặp lại cho Trạng thái và Hoạt hình? Cách tiếp cận này có thể tác động đến hiệu suất?
miviclin

Trường hợp đầu tiên: Movementsẽ kiểm soát State (không quan sát). Trường hợp cuối cùng: Yeah Movementsẽ làm entity.dispatchEvent(...);hoặc như vậy, và tất cả các thành phần khác nghe loại sự kiện đó sẽ nhận được nó. Hiệu suất là kém hơn so với các cuộc gọi phương thức thuần túy, nhưng không nhiều. Bạn có thể gộp các đối tượng sự kiện chẳng hạn. Btw, bạn không phải sử dụng thực thể làm "nút sự kiện", bạn cũng có thể sử dụng một "bus sự kiện" chuyên dụng, loại bỏ hoàn toàn lớp thực thể của bạn.
Torious

2

Về vấn đề của bạn, nếu STATE chỉ được sử dụng trong Ảnh động, thì bạn thậm chí không cần phải phơi bày điều đó với các Thành phần khác. Nếu nó có nhiều hơn một lần sử dụng, thì bạn cần phải phơi bày nó.

Hệ thống các Thành phần / Hệ thống con mà bạn mô tả cảm thấy dựa trên phân cấp nhiều hơn so với dựa trên thành phần. Rốt cuộc, những gì bạn mô tả như các thành phần trong thực tế là các cấu trúc dữ liệu. Điều đó không có nghĩa là nó là một hệ thống tồi, chỉ là tôi không nghĩ rằng nó phù hợp với cách tiếp cận dựa trên thành phần quá tốt.

Như bạn đã lưu ý, sự phụ thuộc là một vấn đề lớn trong các hệ thống dựa trên thành phần. Có nhiều cách khác nhau để đối phó với điều đó. Một số yêu cầu mỗi thành phần để khai báo các phụ thuộc của họ và kiểm tra nghiêm ngặt. Những người khác truy vấn cho các thành phần thực hiện một giao diện cụ thể. Vẫn còn những người khác chuyển tham chiếu đến các thành phần phụ thuộc khi chúng khởi tạo từng thành phần.

Không phụ thuộc vào phương thức bạn sử dụng, bạn sẽ cần một GameObject thuộc loại nào đó để hoạt động như một bộ sưu tập các Thành phần. Những gì GameObject cung cấp có thể thay đổi rất nhiều và bạn có thể đơn giản hóa các phụ thuộc giữa các thành phần của mình bằng cách đẩy một số dữ liệu thường được sử dụng lên cấp GameObject. Unity thực hiện điều đó với phép biến đổi chẳng hạn, buộc tất cả các đối tượng trò chơi phải có một.

Liên quan đến vấn đề bạn yêu cầu về các trạng thái / hoạt hình khác nhau cho các đối tượng trò chơi khác nhau, đây là những gì tôi sẽ làm. Đầu tiên, tôi sẽ không quá thích thú ở giai đoạn triển khai này: chỉ thực hiện những gì bạn cần bây giờ để nó hoạt động, sau đó thêm chuông và còi khi bạn cần.

Vì vậy, tôi sẽ bắt đầu với thành phần 'Bang': PlayerStateComponent, Enemy1State, Enemy2State. Thành phần nhà nước sẽ đảm nhận việc thay đổi trạng thái vào thời điểm thích hợp. Trạng thái là thứ gì đó khá nhiều mà tất cả các đối tượng của bạn sẽ có, vì vậy nó có thể nằm trong GameObject.

Sau đó, sẽ có một AnimationCompoment. Điều này sẽ có một từ điển các hình ảnh động được khóa vào trạng thái. Trong update (), thay đổi hình ảnh động nếu trạng thái thay đổi.

Có một bài viết tuyệt vời về xây dựng khung mà tôi không thể tìm thấy. Nó nói rằng khi bạn không có kinh nghiệm trong miền, bạn nên chọn một vấn đề và thực hiện đơn giản nhất để giải quyết vấn đề hiện tại . Sau đó, bạn thêm một vấn đề / trường hợp sử dụng khác và mở rộng trên khung khi bạn tiếp tục, để nó phát triển một cách hữu cơ. Tôi thực sự thích cách tiếp cận đó, đặc biệt khi làm việc với khái niệm mới như bạn đang làm.

Việc triển khai tôi đề xuất là khá ngây thơ, nhưng đây là một số cải tiến có thể có khi bạn thêm trường hợp sử dụng:

  • thay thế biến GameObject bằng một từ điển. Mỗi thành phần sử dụng từ điển để lưu trữ các giá trị. (đảm bảo xử lý va chạm đúng cách ...)
  • thay thế từ điển của các giá trị đơn giản bằng các tham chiếu thay thế: class FloatVariable () {giá trị công khai [...]}
  • Thay vì nhiều thành phần trạng thái, hãy tạo một StateComponent chung trong đó bạn có thể xây dựng các máy trạng thái biến. Bạn cần có một tập hợp điều kiện chung mà trạng thái có thể thay đổi: nhấn phím, nhập chuột, thay đổi biến (bạn có thể buộc điều đó với FloatVariable ở trên).

Cách tiếp cận này hoạt động, tôi đã thực hiện một cái gì đó tương tự một năm trước, nhưng vấn đề với nó là hầu như mọi thành phần đều phụ thuộc vào các thành phần khác, vì vậy nó có vẻ kém linh hoạt hơn đối với tôi. Tôi cũng đã nghĩ đến việc đẩy các thành phần phổ biến nhất (ví dụ: biến đổi, kết xuất, trạng thái ...) vào thực thể nhưng tôi nghĩ điều này phá vỡ mục đích của các thành phần vì một số trong số chúng được gắn với thực thể và một số thực thể có thể không cần chúng. Đó là lý do tại sao tôi đang cố gắng thiết kế lại nó với các hệ thống chịu trách nhiệm cập nhật logic để các thành phần không biết gì về nhau khi chúng không tự cập nhật.
miviclin

0

Ngoài câu trả lời của ADB, bạn có thể sử dụng http://en.wikipedia.org/wiki/Dependency_injection , giúp khi bạn cần xây dựng nhiều thành phần thông qua việc chuyển chúng làm tài liệu tham khảo cho các nhà xây dựng của họ. Rõ ràng là chúng vẫn sẽ phụ thuộc lẫn nhau (nếu điều đó là bắt buộc trong cơ sở mã của bạn), nhưng bạn có thể đặt tất cả sự phụ thuộc đó vào một nơi mà các phụ thuộc được thiết lập và phần còn lại của mã không cần biết về sự phụ thuộc.

Cách tiếp cận này cũng hoạt động tốt nếu bạn sử dụng giao diện vì mỗi lớp thành phần chỉ yêu cầu những gì nó cần hoặc nơi cần đăng ký và chỉ có khung tiêm phụ thuộc (hoặc nơi bạn thiết lập mọi thứ, thường là ứng dụng) biết về ai cần gì .

Đối với các hệ thống đơn giản, bạn có thể thoát khỏi mà không cần sử dụng DI hoặc mã sạch, các lớp RenderingSystem của bạn có vẻ như bạn cần gọi chúng một cách tĩnh hoặc ít nhất là chúng có sẵn trong mỗi thành phần, điều này khiến chúng phụ thuộc lẫn nhau và khó thay đổi. Nếu bạn quan tâm đến cách tiếp cận sạch hơn, hãy kiểm tra các liên kết của liên kết DI wiki ở trên và đọc về Clean Code: http://clean-code-developer.com/


Tôi đã có một hệ thống trong đó các thành phần khá phụ thuộc lẫn nhau. Tôi đã sử dụng rất nhiều phương pháp tiêm phụ thuộc ở đó và mặc dù tôi thích nó hơn là phân cấp sâu, tôi đang cố gắng tạo một cái mới để tránh khớp nối thành phần nếu có thể. Tôi sẽ không gọi bất cứ điều gì tĩnh. Tôi sẽ có một Trình quản lý thành phần mà mọi hệ thống đều có quyền truy cập (mọi hệ thống nên có một tham chiếu đến nó) và RendererSystem sẽ lấy tất cả các thành phần hoạt hình từ trình quản lý thành phần và hiển thị trạng thái hiện tại của mỗi hoạt ảnh.
miviclin
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.