Không phải là một Hệ thống Thành phần Thực thể khủng khiếp cho việc tách rời / ẩn thông tin?


11

Tiêu đề là cố ý cường điệu và nó có thể chỉ là thiếu kinh nghiệm của tôi với mẫu nhưng đây là lý do của tôi:

Cách "thông thường" hoặc được cho là đơn giản để thực hiện các thực thể là bằng cách thực hiện chúng như các đối tượng và phân lớp hành vi chung. Điều này dẫn đến vấn đề kinh điển "là EvilTreemột lớp con của Treehay Enemy?". Nếu chúng ta cho phép nhiều thừa kế, vấn đề kim cương phát sinh. Thay vào đó, chúng ta có thể kéo chức năng kết hợp của TreeEnemytăng lên thứ bậc dẫn đến các lớp của Chúa hoặc chúng ta có thể cố tình bỏ qua hành vi trong các lớp TreeEntitycác lớp (làm cho chúng giao diện trong trường hợp cực đoan) để EvilTreecó thể tự thực hiện điều đó - dẫn đến việc lặp lại code nếu chúng ta đã từng có một SomewhatEvilTree.

Entity-Component Systems cố gắng giải quyết vấn đề này bằng cách chia TreeEnemyđối tượng thành các thành phần khác nhau - chẳng hạn Position, HealthAI- và thực hiện hệ thống, chẳng hạn như một AISystemthay đổi vị trí của một Entitiy theo quyết định AI. Cho đến nay rất tốt nhưng nếu EvilTreecó thể nhận được một sức mạnh và gây sát thương thì sao? Đầu tiên chúng ta cần một CollisionSystemvà một DamageSystem(có lẽ chúng ta đã có những thứ này). Các CollisionSystemnhu cầu liên lạc với DamageSystem: Mỗi khi có hai điều va chạm, CollisionSystemsẽ gửi một thông điệp tới DamageSystemnó để có thể trừ đi sức khỏe. Thiệt hại cũng bị ảnh hưởng bởi sức mạnh nên chúng ta cần lưu trữ ở đâu đó. Chúng ta có tạo một cái mới PowerupComponentmà chúng ta gắn vào các thực thể không? Nhưng sau đóDamageSystemcần phải biết về một cái gì đó mà không muốn biết gì hơn - sau tất cả, cũng có những thứ gây sát thương không thể tăng sức mạnh (ví dụ a Spike). Chúng tôi có cho phép PowerupSystemsửa đổi một StatComponentcũng được sử dụng để tính toán thiệt hại tương tự như câu trả lời này không? Nhưng bây giờ hai hệ thống truy cập cùng một dữ liệu. Khi trò chơi của chúng tôi trở nên phức tạp hơn, nó sẽ trở thành một biểu đồ phụ thuộc vô hình nơi các thành phần được chia sẻ giữa nhiều hệ thống. Tại thời điểm đó, chúng ta chỉ có thể sử dụng các biến tĩnh toàn cầu và loại bỏ tất cả các mẫu soạn sẵn.

Có một cách hiệu quả để giải quyết điều này? Một ý tưởng tôi đã có là để cho các thành phần có một số chức năng nhất định, ví dụ như đưa ra StatComponent attack()một số nguyên theo mặc định nhưng có thể được tạo khi xảy ra sự cố powerup:

attack = getAttack compose powerupBy(20) compose powerdownBy(40)

Điều này không giải quyết được vấn đề attackphải được lưu trong một thành phần được truy cập bởi nhiều hệ thống nhưng ít nhất tôi có thể nhập đúng chức năng nếu tôi có ngôn ngữ hỗ trợ đủ:

// In StatComponent
type Strength = PrePowerup | PostPowerup
type Damage = Int
type PrePowerup = Int
type PostPowerup = Int
attack: Strength = getAttack //default value, can be changed by systems
getAttack: PrePowerup

// these functions can be defined in other components or in PowerupSystems
powerupBy: Strength -> PostPowerup
powerdownBy: Strength -> PostPowerup
subtractArmor: Strength -> Damage

// in DamageSystem
dealDamage: Damage -> () = attack compose subtractArmor compose hurtSomeEntity

Bằng cách này, tôi ít nhất đảm bảo thứ tự chính xác của các chức năng khác nhau được thêm bởi các hệ thống. Dù bằng cách nào, có vẻ như tôi đang nhanh chóng tiếp cận lập trình phản ứng chức năng ở đây nên tôi tự hỏi liệu tôi có nên sử dụng nó ngay từ đầu không (tôi chỉ mới xem xét FRP, vì vậy tôi có thể sai ở đây). Tôi thấy rằng ECS ​​là một cải tiến đối với hệ thống phân cấp lớp phức tạp nhưng tôi không tin nó là lý tưởng.

Có một giải pháp xung quanh điều này? Có chức năng / mẫu nào tôi thiếu để tách ECS sạch hơn không? Là FRP hoàn toàn phù hợp hơn cho vấn đề này? Có phải những vấn đề này chỉ phát sinh từ sự phức tạp vốn có của những gì tôi đang cố gắng lập trình; tức là FRP có vấn đề tương tự?



Tôi thực sự nhớ blog của Eric (từ khi đó là về C #).
OldFart

Câu trả lời:


21

ECS hoàn toàn phá hỏng dữ liệu ẩn. Đây là một sự đánh đổi của mô hình.

ECS là tuyệt vời trong việc tách rời. Một ECS tốt cho phép một hệ thống di chuyển tuyên bố rằng nó hoạt động trên bất kỳ thực thể nào có vận tốc và thành phần vị trí, mà không phải quan tâm đến loại thực thể nào tồn tại hoặc hệ thống nào khác truy cập vào các thành phần này. Điều này ít nhất tương đương với khả năng tách rời để có các đối tượng trò chơi thực hiện các giao diện nhất định.

Hai hệ thống truy cập cùng một thành phần là một tính năng, không phải là một vấn đề. Nó hoàn toàn được mong đợi, và nó không kết hợp hệ thống theo bất kỳ cách nào. Đúng là các hệ thống sẽ có một biểu đồ phụ thuộc ngầm, nhưng những phụ thuộc đó là vốn có trong thế giới mô hình. Nếu nói rằng hệ thống thiệt hại không nên có sự phụ thuộc ngầm vào hệ thống powerup là tuyên bố rằng việc tăng sức mạnh không ảnh hưởng đến thiệt hại, và điều đó có lẽ sai. Tuy nhiên, trong khi sự phụ thuộc tồn tại, các hệ thống không được ghép nối - bạn có thể xóa hệ thống powerup khỏi trò chơi mà không ảnh hưởng đến hệ thống thiệt hại, vì giao tiếp xảy ra thông qua thành phần stat và hoàn toàn ẩn.

Giải quyết các phụ thuộc và hệ thống đặt hàng này có thể được thực hiện ở một vị trí trung tâm duy nhất, tương tự như cách giải quyết phụ thuộc trong hệ thống DI hoạt động. Vâng, một trò chơi phức tạp sẽ có một biểu đồ phức tạp của các hệ thống, nhưng sự phức tạp này là cố hữu, và ít nhất là nó được chứa.


7

Hầu như không có cách nào có được xung quanh thực tế là một hệ thống cần truy cập vào nhiều thành phần. Để một cái gì đó giống như VelocitySystem hoạt động, có lẽ nó sẽ cần quyền truy cập vào VelocityComponent và PositionComponent. Trong khi đó, RenderingSystem cũng cần truy cập dữ liệu này. Bất kể bạn làm gì, tại một số điểm, hệ thống kết xuất cần biết nơi kết xuất đối tượng và VelocitySystem cần biết nơi để di chuyển đối tượng đến.

Những gì bạn cần cho điều này là sự minh bạch của phụ thuộc. Mỗi hệ thống cần phải rõ ràng về dữ liệu nào nó sẽ đọc và dữ liệu nào nó sẽ ghi vào. Khi một hệ thống muốn tìm nạp một thành phần cụ thể, nó chỉ cần có thể thực hiện việc này một cách rõ ràng . Ở dạng đơn giản nhất, nó chỉ đơn giản có các thành phần cho từng loại mà nó yêu cầu (ví dụ: RenderSystem cần RenderComponents và PositionComponents) làm đối số của nó và trả về bất cứ thứ gì nó đã thay đổi (ví dụ: chỉ RenderComponents).

Bằng cách này, tôi ít nhất đảm bảo sắp xếp chính xác các chức năng khác nhau được thêm vào bởi các hệ thống

Bạn có thể đã đặt hàng trong một thiết kế như vậy. Không có gì để nói rằng đối với ECS, hệ thống của bạn phải độc lập với trật tự hoặc bất kỳ điều gì như vậy.

Là FRP hoàn toàn phù hợp hơn cho vấn đề này? Có phải những vấn đề này chỉ phát sinh từ sự phức tạp vốn có của những gì tôi đang cố gắng lập trình; tức là FRP có vấn đề tương tự?

Sử dụng thiết kế hệ thống thành phần thực thể này và FRP không loại trừ lẫn nhau. Trong thực tế, các hệ thống có thể được coi là không có gì khác khi không có trạng thái, chỉ đơn giản là thực hiện các phép biến đổi dữ liệu (các thành phần).

FRP sẽ không giải quyết vấn đề phải sử dụng thông tin bạn yêu cầu để thực hiện một số thao tác.

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.