Tôi sẽ nói từ một chút kinh nghiệm, từ thiết kế OO cứng nhắc sang thiết kế Entity-Element-System (ECS).
Một thời gian trước tôi cũng giống như bạn , tôi có một loạt các loại khác nhau có tính chất tương tự và tôi đã xây dựng các đối tượng khác nhau và cố gắng sử dụng sự kế thừa để giải quyết nó. Một người rất thông minh đã nói với tôi rằng đừng làm điều đó, và thay vào đó, hãy sử dụng Thực thể-Thành phần-Hệ thống.
Bây giờ, ECS là một khái niệm lớn và thật khó để làm đúng. Có rất nhiều công việc đi vào nó, xây dựng các thực thể, các thành phần và hệ thống đúng cách. Tuy nhiên, trước khi chúng ta có thể làm điều đó, chúng ta cần xác định các điều khoản.
- Thực thể : đây là thứ , người chơi, động vật, NPC, bất cứ điều gì . Đó là một thứ cần các thành phần gắn liền với nó.
- Thành phần : đây là thuộc tính hay tài sản , chẳng hạn như là một "Name" hoặc "Age", hoặc "Cha mẹ", trong trường hợp của bạn.
- Hệ thống : đây là logic đằng sau một thành phần hoặc hành vi . Thông thường, bạn xây dựng một hệ thống cho mỗi thành phần, nhưng điều đó không phải lúc nào cũng có thể. Ngoài ra, đôi khi các hệ thống cần ảnh hưởng đến các hệ thống khác .
Vì vậy, đây là nơi tôi sẽ đi với điều này:
Đầu tiên và quan trọng nhất, tạo ra một ID
cho các nhân vật của bạn. An int
, Guid
bất cứ điều gì bạn thích. Đây là "Thực thể".
Thứ hai, bắt đầu suy nghĩ về các hành vi khác nhau mà bạn đang diễn ra. Những thứ như "Cây gia đình" - đó là một hành vi. Thay vì mô hình hóa như các thuộc tính trên thực thể, hãy xây dựng một hệ thống chứa tất cả thông tin đó . Hệ thống sau đó có thể quyết định phải làm gì với nó.
Tương tự như vậy, chúng tôi muốn xây dựng một hệ thống cho "Nhân vật còn sống hay đã chết?" Đây là một trong những hệ thống quan trọng nhất trong thiết kế của bạn, bởi vì nó ảnh hưởng đến tất cả các hệ thống khác. Một số hệ thống có thể xóa các ký tự "chết" (chẳng hạn như hệ thống "sprite"), các hệ thống khác có thể sắp xếp lại nội bộ để hỗ trợ tốt hơn cho trạng thái mới.
Chẳng hạn, bạn sẽ xây dựng một hệ thống "Sprite" hoặc "Vẽ" hoặc "Kết xuất". Hệ thống này sẽ có trách nhiệm xác định những gì nhân vật cần được hiển thị cùng và cách hiển thị nó. Sau đó, khi một nhân vật chết, loại bỏ chúng.
Ngoài ra, một hệ thống "AI" có thể cho nhân vật biết phải làm gì, đi đâu, v.v. Điều này sẽ tương tác với nhiều hệ thống khác và đưa ra quyết định dựa trên chúng. Một lần nữa, các nhân vật đã chết có thể bị xóa khỏi hệ thống này, vì họ không thực sự làm gì nữa.
Hệ thống "Tên" và hệ thống "Cây gia đình" của bạn có thể sẽ giữ nhân vật (còn sống hoặc đã chết) trong bộ nhớ. Hệ thống này cần nhớ lại thông tin đó, bất kể trạng thái của nhân vật là gì. (Jim vẫn là Jim, ngay cả sau khi chúng tôi chôn anh ấy.)
Điều này cũng mang lại cho bạn lợi ích của việc thay đổi khi hệ thống phản ứng hiệu quả hơn: hệ thống có bộ đếm thời gian riêng. Một số hệ thống cần phải bắn nhanh, một số thì không. Đây là nơi chúng ta bắt đầu tìm hiểu những gì làm cho trò chơi chạy hiệu quả. Chúng ta không cần tính toán lại thời tiết mỗi mili giây, chúng ta có thể làm điều đó cứ sau 5 phút.
Nó cũng mang lại cho bạn đòn bẩy sáng tạo hơn: bạn có thể xây dựng hệ thống "Pathfinder" có thể xử lý việc tính toán đường dẫn từ A đến B và có thể cập nhật khi cần thiết, cho phép hệ thống Chuyển động nói "tôi cần ở đâu Đăng nhập?" Bây giờ chúng ta có thể tách biệt hoàn toàn những mối quan tâm này, và lý do về chúng hiệu quả hơn. Phong trào không cần tìm đường đi, nó chỉ cần đưa bạn đến đó.
Bạn sẽ muốn để lộ một số phần của hệ thống ra bên ngoài. Trong Pathfinder
hệ thống của bạn có thể bạn sẽ muốn một Vector2 NextPosition(int entity)
. Bằng cách này, bạn có thể giữ các thành phần đó trong các mảng hoặc danh sách được kiểm soát chặt chẽ. Bạn có thể sử dụng các struct
loại nhỏ hơn, có thể giúp bạn giữ các thành phần trong các khối bộ nhớ nhỏ hơn, liền kề nhau, có thể giúp cập nhật hệ thống nhanh hơn nhiều . (Đặc biệt nếu ảnh hưởng bên ngoài đến một hệ thống là tối thiểu, thì bây giờ nó chỉ cần quan tâm đến trạng thái bên trong của nó, chẳng hạn như Name
.)
Nhưng, và tôi không thể nhấn mạnh điều này đủ, giờ đây Entity
chỉ là một ID
, bao gồm gạch, vật thể, v.v ... Nếu một thực thể không thuộc về một hệ thống, thì hệ thống sẽ không theo dõi nó. Điều này có nghĩa là chúng ta có thể tạo các đối tượng "Cây" của mình, lưu trữ chúng trong Sprite
và Movement
các hệ thống (cây sẽ không di chuyển, nhưng chúng có thành phần "Vị trí") và tránh xa các hệ thống khác. Chúng ta không còn cần một danh sách đặc biệt cho cây nữa, vì việc dựng hình một cây không khác gì một nhân vật, ngoài việc làm giấy. ( Sprite
Hệ thống nào có thể kiểm soát hoặc Paperdoll
hệ thống có thể kiểm soát.) Bây giờ chúng ta NextPosition
có thể viết lại một chút: Vector2? NextPosition(int entity)
và nó có thể trả về một null
vị trí cho các thực thể mà nó không quan tâm. Chúng tôi cũng áp dụng điều này cho chúng tôi NameSystem.GetName(int entity)
, nó trả lại null
cho cây và đá.
Tôi sẽ kết thúc vấn đề này, nhưng ý tưởng ở đây là cung cấp cho bạn một số nền tảng về ECS và cách bạn thực sự tận dụng nó để cung cấp cho bạn một thiết kế tốt hơn trong trò chơi của bạn. Bạn có thể tăng hiệu suất, tách các yếu tố không liên quan và giữ mọi thứ theo cách có tổ chức hơn. (Điều này cũng kết hợp tốt với các ngôn ngữ / thiết lập chức năng, như F # và LINQ, tôi khuyên bạn nên kiểm tra F # nếu bạn chưa có, nó kết hợp rất tốt với C # khi bạn sử dụng kết hợp chúng.)