Chào mọi người. Tôi là một nhà phát triển Unity có kinh nghiệm. Tôi quyết định tham gia cuộc thi của Hackernoon và Tatum Games. Trong bài viết đầu tiên, tôi sẽ thảo luận về kiến trúc của các dự án phát triển game Unity và điểm qua các cách tiếp cận phổ biến nhất mà tôi đã gặp. Và, tất nhiên, tôi sẽ cho bạn biết lý do tại sao tôi lại là một kẻ bạo dâm như vậy và tại sao tôi lại đến với HMVС (HMVP) yêu quý của mình. PS Tất cả những gì được mô tả ở đây là một ý kiến chủ quan. Mọi người cần xem xét các chi tiết cụ thể của sự phát triển và toàn bộ dự án, nhưng nói chung, kiến trúc tốt nhất là kiến trúc không tồn tại và kết hợp các cách tiếp cận khác nhau theo phong cách thuận tiện và hiệu quả cho nhóm :D MonoBehaviour và lập trình hướng thành phần (COP) Hãy bắt đầu với cách tiếp cận cơ bản nhất được hầu hết những người mới bắt đầu sử dụng. Tôi không muốn nói rằng cách tiếp cận này là xấu, chỉ là hầu hết các nhà phát triển đã quen suy nghĩ về OOP (lập trình hướng đối tượng). Việc sử dụng đúng COP (lập trình hướng thành phần) đòi hỏi một tư duy hơi khác. Đồng thời, việc triển khai COP dựa trên MonoBehaviour của Unity có vẻ không lý tưởng. Vì vậy, cách tiếp cận cơ bản ngụ ý rằng toàn bộ trò chơi sẽ được xây dựng trên một GameObject với các thành phần MonoBehaviour, cho phép bạn chia các hệ thống con khác nhau thành các phần nhỏ và xây dựng trò chơi từ đó. { private Awake() { } } Tuy nhiên, ma quỷ là trong các chi tiết. Cách tiếp cận này dẫn đến những khó khăn về quy mô, đặc biệt là đối với các dự án lớn, liên kết không cần thiết, phản ánh các sự cố dưới vỏ bọc của Unity và ràng buộc chặt chẽ với API Unity, điều này có thể gây ra sự cố sau này, đặc biệt nếu bạn muốn sao chép mã trên máy khách và máy chủ. Độc thân Singleton cho vị trí quản lý là điều đầu tiên và kinh khủng nhất có thể xuất hiện trong đầu bạn. Về bản chất, Singleton là một đối tượng chứa trong cảnh hoặc toàn bộ dự án trong một phiên bản duy nhất, cần thiết để liên kết và quản lý các hệ thống riêng biệt trong trò chơi của chúng ta. Một ví dụ đơn giản về Singleton mà tôi thường thấy ở các Junior developer: public sealed class MySingleton { private static MySingleton _instance = null; private MySingleton() { } public static MySingleton Instance { get { if (_instance == null) { _instance = new MySingleton(); } return _instance; } } public void OperationX() { } } Trên các dự án nhỏ, điều này sẽ không dẫn đến các vấn đề không cần thiết. Dự án càng lớn thì càng khó kiểm soát. Chưa kể rò rỉ bộ nhớ, Singleton dẫn đến các vấn đề về xây dựng thử nghiệm, tổ chức đa luồng và tập lệnh quá dài (thường thấy ở các nhà phát triển mới làm quen). Đây là một chút về cách tổ chức Singleton thường trông như thế nào (và còn lâu mới là chính xác nhất): Vì vậy, làm thế nào để bạn biết nếu Singleton là ác? Khi nó liên kết tất cả logic trong trò chơi của bạn và kiểm soát mọi thứ Khi một loạt các liên kết được ném trực tiếp vào nó Khi kích thước của nó trở nên khổng lồ Khi bạn đã thành thạo trong việc gỡ lỗi hoặc quản lý bộ nhớ, đặc biệt nếu Singleton là một GameObject Khi nào thì tốt hơn để sử dụng Singleton? dự án nhỏ hơn Khi quản lý bộ nhớ không phải là vấn đề và bạn quản lý sự kiện thay vì chuyển tiếp liên kết trực tiếp Ví dụ: đối với các hệ thống nhỏ để quản lý âm thanh hoặc dưới dạng thu thập điểm cuối cho các hệ thống phân tích Bây giờ hãy thảo luận về DI container. Bộ chứa DI và Zenject chết tiệt Oh-oh, nhiều người đang thúc đẩy Zenject và triển khai các phụ thuộc bằng cách sử dụng bộ chứa DI. Trên thực tế, nhiều người sử dụng khung khổng lồ này như một Singleton thông thường. Như thường lệ, tôi đã thấy nó trên các dự án: Về bản chất, một bộ chứa DI là cần thiết để đặt các tham chiếu và giải quyết các phụ thuộc trong các đối tượng cuối cùng. Ví dụ đơn giản nhất từ cùng một Zenject: public class TestInstaller : MonoInstaller { public override void InstallBindings() { Container.Bind<string>().FromInstance("Hello World!"); Container.Bind<Greeter>().AsSingle().NonLazy(); } } public class Greeter { public Greeter(string message) { Debug.Log(message); } } Cách tiếp cận này là tốt, nhưng chỉ cho đến khi mọi thứ bắt đầu trở nên phức tạp: DI-Container về cơ bản giống như Singleton nhưng được cải tiến, tạo ra các liên kết với chính container Rất thường xuyên, các lớp trình cài đặt "dài hàng km" được tạo để thực hiện các liên kết phụ thuộc Những người mới đến khó hiểu do phải chịu nhiều trách nhiệm hơn, mặc dù đây là một cách tiếp cận có thể mở rộng tốt sau này Khó gỡ lỗi do vùng chứa và các ràng buộc phổ biến Rất dễ dàng để biến một bộ chứa DI thông thường thành Bộ định vị dịch vụ Tất nhiên, các bộ chứa DI là một cách tốt để tổ chức mã trong tay phải, nhưng bạn cần được đào tạo ở mức độ cao để giữ cho mọi thứ không trở thành bacchanalia. MVC ở dạng thuần túy Tại sao ở dạng tinh khiết của nó? Bởi vì nó đủ dễ hiểu. Chúng tôi có một bộ điều khiển, mô hình và chế độ xem để tổ chức một dự án. Nhưng miễn là có MVC, thì sẽ có nhiều kiểu con của nó như MVP, MVVM, v.v. Nhưng bây giờ chúng ta sẽ tập trung vào một ví dụ cơ bản và dễ tiếp cận với tất cả các ví dụ: Lợi ích là rõ ràng - chúng tôi tách quyền kiểm soát (đầu vào của người dùng) khỏi dữ liệu và khỏi chế độ xem (những gì người dùng nhìn thấy trên màn hình). Giao tiếp thường được điều khiển theo sự kiện và được khởi tạo trong bộ chứa ứng dụng. Điều này loại bỏ sự cần thiết của nhiều sự gắn kết; chúng tôi chỉ giao tiếp với các sự kiện. Tuy nhiên, có một số nhược điểm ở đây: Khi dự án mở rộng quy mô, ứng dụng lớp cài đặt của chúng tôi (cùng một vùng chứa) sẽ phát triển Sự sắp xếp theo chiều ngang của MVC tạo ra một số lượng lớn các lớp khác nhau được kết nối lỏng lẻo với nhau Bây giờ hãy thảo luận về một cách tiếp cận khác. MVC trong vùng chứa Một kịch bản khác có thể xảy ra là liên kết bộ ba MVC của chúng tôi vào một bộ chứa DI. Bằng cách này, chúng tôi có thể kiểm soát tốt hơn các kết nối giữa các ứng dụng, nhưng rất dễ dàng để biến mọi thứ thành Bộ định vị dịch vụ. Cách tiếp cận khác vì thay vì liên kết bộ điều khiển với sự kiện, chúng tôi giải quyết bộ điều khiển của mình thông qua vùng chứa rồi làm việc với sự kiện. Tuy nhiên, tất cả đều giống nhau, các vấn đề phát sinh ở đây giống như với DI-container thông thường, nhưng có sự phức tạp ngày càng tăng và nhiều lớp được tạo ra. Tuy nhiên, chúng tôi tách đại diện, mô hình và bộ điều khiển. HMVC/HMVP Đây là nơi tôi muốn nói chuyện lâu hơn một chút, vì tôi, với tư cách là một kẻ bạo dâm, đã trở nên khá thích cách tiếp cận này. Với nó, chúng tôi tạo ra một bộ phận giống như cây của MVC, điều này mang lại một số lợi thế mặc dù cơ sở mã ngày càng tăng lên rất nhiều. Vì vậy, hãy xem sơ đồ tương tác mà tôi sử dụng thường xuyên nhất: Làm thế nào nó hoạt động? Ban đầu, chúng tôi tạo một cảnh trống bằng GameInstaller, trò chơi này sẽ tải các vùng chứa cho từng cảnh riêng biệt. Bản thân lớp GameInstaller lưu trữ bộ ba toàn cầu (cấp cao nhất) thường chịu trách nhiệm cho các hệ thống lớn (ví dụ: xử lý âm thanh) và lưu trữ các sự kiện chung cho toàn bộ vòng đời của trò chơi. Sau đó, GameInstaller tải bộ chứa cảnh mà bạn cần, bộ ba này sẽ khởi tạo bộ ba cấp cao nhất bên trong chính nó (ví dụ: bộ điều khiển trình phát chung), và đến lượt nó, sẽ khởi chạy bộ điều khiển con bên trong chính nó (ví dụ: bộ điều khiển súng thần công). Và cứ thế tiếp tục đi xuống. Giao tiếp của tất cả các chi nhánh diễn ra độc quyền thông qua các sự kiện và trường năng động. Nghe có vẻ phức tạp, nhưng nó đơn giản hơn nhiều: cách tiếp cận này cho phép chúng ta tách biệt tất cả các bộ ba một cách dễ dàng trong khi vẫn duy trì mối liên hệ phù hợp theo ngữ cảnh giữa các phần tử con của chúng. Việc khởi tạo mỗi người trình bày bắt đầu bằng việc lấy bối cảnh của các sự kiện từ cha mẹ. Tôi thấy một số lợi thế trong phương pháp này: Các cảnh dự án có thể được tải gần như ngay lập tức và các đối tượng của chúng tôi, bao gồm cả Chế độ xem, có thể được khởi tạo theo yêu cầu khi cây của chúng tôi được tải. Nếu chúng tôi không cần tải Chế độ xem có cài đặt hoặc cửa hàng trò chơi trước khi gửi sự kiện, chúng tôi sẽ không lưu trữ bất kỳ thứ gì khác ngoài sự kiện Cấu trúc chặt chẽ và sự cô lập của các bộ ba riêng lẻ Sự gắn kết yếu do các sự kiện Năng động với chi phí của các sự kiện Khá dễ gỡ lỗi trên các nhánh bộ ba hơn là thông qua các vùng chứa Có một vài nhược điểm: Nếu bạn cần xâu chuỗi một sự kiện thông qua một cây gồm 20 bộ ba, thì đó sẽ là một công việc khá dài, nhưng cách tiếp cận đòi hỏi thiết kế ban đầu tốt Cơ sở mã lớn cho dự án, mặc dù có cấu trúc tốt Nếu bạn cần buộc các nhánh lại với nhau, đây có thể là một thách thức lớn đối với bạn khi ném các sự kiện qua hàng chục lớp Nói chung, HMVC/HMVP cần thiết cho các dự án được tổ chức tốt với sự cô lập cao của các hệ thống con, yêu cầu bộ nhớ cao và tài nguyên trò chơi. Nhưng có thể mất nhiều thời gian hơn để làm quen với nó so với các phương pháp khác. Phần kết luận Mỗi cách tiếp cận để tổ chức một dự án đều có vị trí của nó. Tất cả chỉ phụ thuộc vào mục tiêu thiết kế. Nếu bạn cần kiến trúc chặt chẽ và xử lý bộ nhớ nhanh, đồng thời cần tài nguyên động và nhanh - hãy sử dụng HMVC. Nếu bạn cần nhanh chóng tạo nguyên mẫu cho dự án của mình mà không cần phiền phức - hãy viết mọi thứ bằng Singletons.