Nếu bạn đang sử dụng OOP trong một ngôn ngữ hiện đại, mối nguy hiểm lớn nhất thường không phải là " mã spaghetti " mà là " mã ravioli". Bạn có thể kết thúc việc phân chia và chinh phục các vấn đề về nơi mà cơ sở mã của bạn bao gồm các phần của nó, các chức năng và đối tượng tuổi teen, tất cả được ghép lỏng lẻo và thực hiện các trách nhiệm đơn lẻ nhưng thiếu niên, tất cả đều được thử nghiệm với các bài kiểm tra đơn vị, với một mạng nhện tương tác trừu tượng đang diễn ra khiến cho rất khó để suy luận về những gì đang xảy ra liên quan đến những thứ như tác dụng phụ. Và thật dễ dàng để nghĩ rằng bạn đã thiết kế nó đẹp như vậy, vì các mảnh riêng lẻ thực sự có thể đẹp và tuân thủ RẮN, trong khi vẫn tìm thấy bộ não trên bờ vực bùng nổ từ sự phức tạp của tất cả các tương tác khi cố gắng hiểu toàn bộ hệ thống.
Và mặc dù rất dễ để suy luận về bất kỳ một trong những đối tượng hoặc chức năng này làm gì, vì chúng thực hiện một trách nhiệm đơn lẻ và đơn giản và thậm chí có thể đẹp trong khi thể hiện ít nhất sự phụ thuộc trừu tượng của chúng thông qua DI, vấn đề là khi bạn muốn để phân tích bức tranh lớn, thật khó để tìm ra hàng ngàn thứ tuổi teen với một mạng nhện tương tác cuối cùng sẽ làm gì. Tất nhiên mọi người nói, chỉ cần nhìn vào các vật thể lớn và các chức năng lớn được ghi lại và không đi sâu vào những người trẻ tuổi, và tất nhiên điều đó sẽ giúp ít nhất hiểu được những gì sẽ xảy ra theo cách cấp cao. ..
Tuy nhiên, điều đó không giúp được gì nhiều khi bạn cần thực sự thay đổi hoặc gỡ lỗi mã, tại thời điểm đó, bạn phải có khả năng tìm ra những gì tất cả những điều này cộng lại để thực hiện các ý tưởng cấp thấp hơn như tác dụng phụ và thay đổi trạng thái liên tục và làm thế nào để duy trì bất biến trên toàn hệ thống. Và thật khó để ghép các tác dụng phụ xảy ra giữa các tương tác của hàng ngàn thứ tuổi teen cho dù họ có sử dụng giao diện trừu tượng để giao tiếp với nhau hay không.
ECS
Vì vậy, điều cuối cùng mà tôi tìm thấy để giảm thiểu vấn đề này thực sự là các hệ thống thành phần thực thể, nhưng điều đó có thể là quá mức cần thiết cho nhiều dự án. Tôi đã yêu ECS đến mức bây giờ, ngay cả khi tôi viết các dự án nhỏ, tôi sử dụng công cụ ECS của mình (mặc dù dự án nhỏ đó có thể chỉ có một hoặc hai hệ thống). Tuy nhiên, đối với những người không quan tâm đến ECS, tôi đã cố gắng tìm hiểu tại sao ECS đơn giản hóa khả năng hiểu hệ thống rất nhiều và tôi nghĩ rằng tôi nên áp dụng một số điều nên áp dụng cho nhiều dự án ngay cả khi họ không sử dụng kiến trúc ECS.
Vòng lặp đồng nhất
Một khởi đầu cơ bản là ưu tiên các vòng lặp đồng nhất hơn, có xu hướng ngụ ý nhiều lượt truyền hơn trên cùng một dữ liệu, nhưng các đường chuyền đồng đều hơn. Ví dụ, thay vì làm điều này:
for each entity:
apply physics to entity
apply AI to entity
apply animation to entity
update entity textures
render entity
... bằng cách nào đó dường như sẽ giúp rất nhiều nếu bạn làm điều này thay vào đó:
for each entity:
apply physics to entity
for each entity:
apply AI to entity
etc.
Và điều đó có vẻ lãng phí khi lặp đi lặp lại trên cùng một dữ liệu nhiều lần, nhưng bây giờ mỗi lần vượt qua đều rất đồng nhất. Nó cho phép bạn nghĩ, "Được rồi, trong giai đoạn này của hệ thống, không có gì xảy ra với những vật thể này ngoại trừ vật lý. Nếu có những thứ bị thay đổi và tác dụng phụ xảy ra, tất cả chúng đều được thay đổi một cách rất thống nhất. " Và bằng cách nào đó tôi thấy điều đó giúp lý luận về codebase rất nhiều.
Mặc dù nó có vẻ lãng phí, nhưng nó cũng có thể giúp bạn tìm thấy nhiều cơ hội hơn để song song mã khi các tác vụ thống nhất đang được áp dụng trên tất cả mọi thứ trong mỗi vòng lặp. Và nó cũng có xu hướng để khuyến khíchmột mức độ tách rời lớn hơn. Tự nhiên khi bạn có những đường chuyền ly dị này mà không cố gắng làm mọi thứ với một đối tượng trong một lượt, bạn có xu hướng tìm nhiều cơ hội hơn để dễ dàng tách mã và giữ cho nó tách rời. Trong ECS, các hệ thống thường được tách rời hoàn toàn với nhau và không có "lớp" hoặc "chức năng" bên ngoài nào tự phối hợp chúng với nhau. ECS cũng không bị lỗi bộ nhớ cache lặp đi lặp lại vì nó không nhất thiết phải lặp trên cùng một dữ liệu nhiều lần (mỗi vòng lặp có thể truy cập các thành phần khác nhau nằm hoàn toàn ở nơi khác trong bộ nhớ, nhưng được liên kết với cùng một thực thể). Các hệ thống không phải được phối hợp thủ công vì chúng tự trị và chịu trách nhiệm cho việc lặp. Họ chỉ cần truy cập vào cùng một dữ liệu trung tâm.
Vì vậy, đó là một cách để bắt đầu có thể giúp bạn thiết lập một loại luồng kiểm soát đơn giản và thống nhất hơn trên hệ thống của bạn.
Làm phẳng xử lý sự kiện
Một cách khác là giảm sự phụ thuộc vào xử lý sự kiện. Xử lý sự kiện thường là cần thiết để tìm ra những điều bên ngoài đã xảy ra mà không bỏ phiếu, nhưng thường có nhiều cách để tránh các sự kiện đẩy tầng dẫn đến các luồng điều khiển và tác dụng phụ rất khó dự đoán. Xử lý sự kiện, về bản chất, có xu hướng xử lý những điều phức tạp xảy ra với một đối tượng nhỏ tại một thời điểm, khi chúng ta muốn tập trung vào những điều đơn giản và thống nhất xảy ra với nhiều đối tượng tại một thời điểm.
Vì vậy, ví dụ, thay vì thay đổi kích thước hệ điều hành thay đổi kích thước điều khiển cha mẹ, sau đó bắt đầu thay đổi kích thước và vẽ các sự kiện cho mỗi đứa trẻ có thể xếp nhiều sự kiện hơn cho ai biết, bạn chỉ có thể kích hoạt thay đổi kích thước sự kiện và đánh dấu cha mẹ và con cái như dirty
và cần phải được sơn lại. Bạn thậm chí có thể đánh dấu tất cả các điều khiển là cần thay đổi kích thước tại điểm mà một điểm LayoutSystem
có thể chọn và thay đổi kích thước mọi thứ và kích hoạt thay đổi kích thước sự kiện cho tất cả các điều khiển có liên quan.
Sau đó, hệ thống kết xuất GUI của bạn có thể được đánh thức bằng một biến điều kiện và lặp qua các điều khiển bẩn và sơn lại chúng bằng một đường chuyền rộng (không phải hàng đợi sự kiện) và toàn bộ đường chuyền đó được tập trung vào không có gì ngoại trừ vẽ UI. Nếu có sự phụ thuộc thứ tự phân cấp để sơn lại, thì hãy tìm ra các vùng hoặc hình chữ nhật bẩn và vẽ lại mọi thứ trong các vùng đó theo thứ tự z thích hợp để bạn không phải thực hiện chuyển đổi cây và chỉ có thể lặp lại dữ liệu trong một thời trang đơn giản và "phẳng", không phải là thời trang đệ quy và "sâu sắc".
Có vẻ như một sự khác biệt tinh tế như vậy nhưng tôi thấy điều này khá hữu ích từ quan điểm dòng kiểm soát vì một số lý do. Đó thực sự là về việc giảm số lượng những điều xảy ra với từng đối tượng riêng lẻ, cố gắng nhắm đến một thứ tương tự như SRP nhưng được áp dụng theo các vòng lặp và tác dụng phụ: " Nguyên tắc vòng lặp một nhiệm vụ ", " Loại đơn của một bên Nguyên tắc hiệu quả trên mỗi vòng lặp ".
Kiểu luồng điều khiển này cho phép bạn nghĩ về hệ thống nhiều hơn về các nhiệm vụ lớn, nặng nhưng cực kỳ đồng đều được áp dụng trong các vòng lặp, không phải tất cả các chức năng và tác dụng phụ có thể xảy ra với từng đối tượng riêng lẻ. Nhiều như điều này có vẻ không tạo ra sự khác biệt lớn, tôi thấy nó tạo ra tất cả sự khác biệt trên thế giới ít nhất là bằng khả năng của chính tôi để hiểu hành vi của cơ sở mã trong tất cả các lĩnh vực quan trọng khi thực hiện thay đổi hoặc gỡ lỗi (điều mà tôi cũng thấy ít cần phải làm hơn với phương pháp này).
Dòng chảy phụ thuộc vào dữ liệu
Đây có lẽ là phần gây tranh cãi nhất của ECS và thậm chí nó có thể là thảm họa đối với một số miền. Nó vi phạm trực tiếp Nguyên tắc đảo ngược phụ thuộc của RẮN, trong đó nêu rõ rằng các phụ thuộc phải chảy theo hướng trừu tượng, ngay cả đối với các mô-đun cấp thấp. Nó cũng vi phạm việc che giấu thông tin, nhưng ít nhất là đối với ECS, không nhiều như nó xuất hiện vì thông thường chỉ có một hoặc hai hệ thống sẽ truy cập vào bất kỳ dữ liệu nào của thành phần cụ thể.
Và tôi nghĩ rằng ý tưởng về sự phụ thuộc chảy vào trừu tượng hoạt động rất tốt nếu sự trừu tượng của bạn ổn định (như trong, không thay đổi). Phụ thuộc nên chảy theo hướng ổn định . Tuy nhiên, ít nhất theo kinh nghiệm của tôi, sự trừu tượng thường không ổn định. Các nhà phát triển sẽ không bao giờ làm cho họ hoàn toàn đúng và sẽ thấy cần phải thay đổi hoặc xóa các chức năng (thêm không quá tệ) cũng như không dùng một số giao diện một hoặc hai năm sau đó. Khách hàng sẽ thay đổi suy nghĩ của họ theo cách phá vỡ các khái niệm cẩn thận mà các nhà phát triển đã xây dựng, đưa xuống nhà máy trừu tượng cho ngôi nhà trừu tượng của các thẻ trừu tượng.
Trong khi đó, tôi thấy dữ liệu ổn định hơn nhiều. Ví dụ, dữ liệu nào mà một thành phần chuyển động cần trong trò chơi? Câu trả lời khá đơn giản. Nó cần một số loại ma trận biến đổi 4 x 4 và nó cần một tham chiếu / con trỏ tới cha mẹ để cho phép phân cấp chuyển động được tạo. Đó là nó. Quyết định thiết kế đó có thể kéo dài trọn đời của toàn bộ phần mềm.
Có thể có một số điểm tinh tế như liệu chúng ta nên sử dụng dấu phẩy động chính xác đơn hay điểm chính xác kép cho ma trận, nhưng cả hai đều là những quyết định đúng đắn. Nếu SPFP được sử dụng, thì độ chính xác là một thách thức. Nếu DPFP được sử dụng, thì tốc độ là một thách thức, nhưng cả hai đều là những lựa chọn tốt không cần phải thay đổi hoặc nhất thiết phải ẩn đằng sau một giao diện. Đại diện là một trong những chúng ta có thể cam kết và giữ ổn định.
Tuy nhiên, tất cả các chức năng cần thiết cho một IMotion
giao diện trừu tượng là gì , và quan trọng hơn, bộ chức năng tối thiểu lý tưởng mà nó sẽ cung cấp để làm những việc hiệu quả chống lại nhu cầu của tất cả các hệ thống con sẽ xử lý chuyển động là gì? Đó là như vậy, rất khó để trả lời mà không hiểu quá nhiều về toàn bộ thiết kế của ứng dụng cần trả trước. Và do đó, khi rất nhiều phần của codebase kết thúc tùy thuộc vào điều này IMotion
, chúng ta có thể thấy mình phải viết lại rất nhiều với mỗi lần lặp thiết kế trừ khi chúng ta có thể làm điều này ngay lần đầu tiên.
Tất nhiên trong một số trường hợp, biểu diễn dữ liệu có thể rất không ổn định. Một cái gì đó có thể phụ thuộc vào cấu trúc dữ liệu phức tạp có thể cần thay thế trong tương lai do sự không phù hợp trong cấu trúc dữ liệu trong khi nhu cầu chức năng của hệ thống liên quan đến cấu trúc dữ liệu dễ dàng được dự đoán trước. Vì vậy, đáng để thực dụng và quyết định mọi thứ trong từng trường hợp cụ thể là liệu phụ thuộc có chảy vào trừu tượng hay dữ liệu hay không, nhưng đôi khi, dữ liệu dễ ổn định hơn trừu tượng, và cho đến khi tôi chấp nhận ECS mà tôi thậm chí đã cân nhắc việc tạo ra sự phụ thuộc chủ yếu vào dữ liệu (với các hiệu ứng ổn định và đơn giản hóa đáng kinh ngạc).
Vì vậy, trong khi điều này có vẻ kỳ lạ, trong những trường hợp như vậy dễ dàng hơn để đưa ra một thiết kế ổn định cho dữ liệu qua giao diện trừu tượng, tôi thực sự khuyên bạn nên hướng sự phụ thuộc vào dữ liệu cũ đơn giản. Điều này có thể giúp bạn tiết kiệm nhiều lần lặp lại các lần viết lại. Tuy nhiên, liên quan đến việc kiểm soát các luồng và mã spaghetti và ravioli, điều này cũng sẽ có xu hướng đơn giản hóa các luồng kiểm soát của bạn khi bạn không phải có các tương tác phức tạp như vậy trước khi cuối cùng bạn nhận được dữ liệu liên quan.