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à EvilTree
một lớp con của Tree
hay 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 Tree
và Enemy
tă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 Tree
và Entity
các lớp (làm cho chúng giao diện trong trường hợp cực đoan) để EvilTree
có 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 Tree
và Enemy
đối tượng thành các thành phần khác nhau - chẳng hạn Position
, Health
và AI
- và thực hiện hệ thống, chẳng hạn như một AISystem
thay đổi vị trí của một Entitiy theo quyết định AI. Cho đến nay rất tốt nhưng nếu EvilTree
có 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 CollisionSystem
và một DamageSystem
(có lẽ chúng ta đã có những thứ này). Các CollisionSystem
nhu cầu liên lạc với DamageSystem
: Mỗi khi có hai điều va chạm, CollisionSystem
sẽ gửi một thông điệp tới DamageSystem
nó để 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 PowerupComponent
mà chúng ta gắn vào các thực thể không? Nhưng sau đóDamageSystem
cầ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 PowerupSystem
sửa đổi một StatComponent
cũ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 đề attack
phả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ự?