Tôi sẽ đề nghị bắt đầu bằng cách đọc 3 lời nói dối lớn của Mike Acton, vì bạn đã vi phạm hai trong số đó. Tôi nghiêm túc, điều này sẽ thay đổi cách bạn thiết kế mã của mình: http://cellperformance.beyond3d.com/articles/2008/03/three-big-lies.html
Vậy bạn vi phạm điều gì?
Lie # 3 - Mã quan trọng hơn dữ liệu
Bạn nói về tiêm phụ thuộc, có thể hữu ích trong một số trường hợp (và chỉ một số) nhưng phải luôn rung chuông cảnh báo lớn nếu bạn sử dụng nó, đặc biệt là trong phát triển trò chơi! Tại sao? Bởi vì nó thường là một sự trừu tượng không cần thiết. Và trừu tượng ở những nơi sai lầm là khủng khiếp. Vì vậy, bạn có một trò chơi. Trò chơi có người quản lý cho các thành phần khác nhau. Các thành phần đều được xác định. Vì vậy, tạo một lớp ở đâu đó trong mã vòng lặp trò chơi chính của bạn "có" các trình quản lý. Giống:
private CollissionManager _collissionManager;
private BulletManager _bulletManager;
Cung cấp cho nó một số hàm getter để nhận từng lớp trình quản lý (getBONSManager ()). Có thể bản thân lớp này là Singleton hoặc có thể truy cập được từ một người (dù sao bạn cũng có thể có một đơn vị Game trung tâm ở đâu đó). Không có gì sai với dữ liệu và hành vi được mã hóa cứng được xác định rõ.
Không tạo Trình quản lý cho phép bạn đăng ký Người quản lý bằng khóa, có thể được truy xuất bằng khóa đó bởi các lớp khác muốn sử dụng trình quản lý. Đó là một hệ thống tuyệt vời và rất linh hoạt, nhưng nơi nói về một trò chơi ở đây. Bạn biết chính xác những hệ thống trong trò chơi. Tại sao lại giả vờ như bạn không? Bởi vì đây là một hệ thống dành cho những người nghĩ rằng mã quan trọng hơn dữ liệu. Họ sẽ nói "Mã rất linh hoạt, dữ liệu chỉ cần điền vào". Nhưng mã chỉ là dữ liệu. Hệ thống tôi mô tả dễ dàng hơn, đáng tin cậy hơn, dễ bảo trì hơn và linh hoạt hơn rất nhiều (ví dụ: nếu hành vi của một người quản lý khác với người quản lý khác, bạn chỉ phải thay đổi một vài dòng thay vì làm lại toàn bộ hệ thống)
Lie # 2 - Mã nên được thiết kế xung quanh một mô hình của thế giới
Vì vậy, bạn có một thực thể trong thế giới trò chơi. Các thực thể có một số thành phần xác định hành vi của nó. Vì vậy, bạn tạo một lớp Thực thể với một danh sách các đối tượng Thành phần và hàm Update () gọi hàm Update () của mỗi Thành phần. Đúng?
Không :) Đó là thiết kế xung quanh một mô hình của thế giới: bạn có viên đạn trong trò chơi của mình, vì vậy bạn thêm một lớp Bullet. Sau đó, bạn cập nhật từng Bullet và chuyển sang cái tiếp theo. Điều này sẽ hoàn toàn giết chết hiệu suất của bạn và nó cung cấp cho bạn một cơ sở mã hóa khủng khiếp với mã trùng lặp ở khắp mọi nơi và không có cấu trúc logic của mã tương tự. (Kiểm tra câu trả lời của tôi ở đây để được giải thích chi tiết hơn về lý do tại sao thiết kế OO truyền thống bị mất hoặc tìm kiếm Thiết kế hướng dữ liệu)
Chúng ta hãy xem xét tình huống mà không có sự thiên vị OO của chúng tôi. Chúng tôi muốn những điều sau đây, không hơn không kém (xin lưu ý rằng không có yêu cầu để tạo một lớp cho thực thể hoặc đối tượng):
- Bạn có một loạt các thực thể
- Các thực thể bao gồm một số thành phần xác định hành vi của thực thể
- Bạn muốn cập nhật từng thành phần trong trò chơi từng khung hình, tốt nhất là theo cách được kiểm soát
- Khác với việc xác định các thành phần là thuộc về nhau, bản thân thực thể không cần phải làm gì. Đó là một liên kết / ID cho một vài thành phần.
Và hãy nhìn vào tình hình. Hệ thống thành phần của bạn sẽ cập nhật hành vi của mọi đối tượng trong trò chơi mọi khung hình. Đây chắc chắn là một hệ thống quan trọng của động cơ của bạn. Hiệu suất là quan trọng ở đây!
Nếu bạn quen thuộc với kiến trúc máy tính hoặc Thiết kế hướng dữ liệu, bạn sẽ biết cách đạt được hiệu suất tốt nhất: bộ nhớ được đóng gói chặt chẽ và bằng cách nhóm thực thi mã. Nếu bạn thực thi các đoạn mã A, B và C như thế này: ABCABCABC, bạn sẽ không có được hiệu suất như khi bạn thực thi nó như thế này: AAABBBCCC. Điều này không chỉ vì bộ đệm dữ liệu hướng dẫn và dữ liệu sẽ được sử dụng hiệu quả hơn mà còn bởi vì nếu bạn thực hiện tất cả các chữ "A" sau nhau, có rất nhiều chỗ để tối ưu hóa: xóa mã trùng lặp, tính toán trước dữ liệu được sử dụng bởi tất cả "A", v.v.
Vì vậy, nếu chúng ta muốn cập nhật tất cả các thành phần, chúng ta đừng biến chúng thành các lớp / đối tượng với chức năng cập nhật. Chúng ta đừng gọi chức năng cập nhật đó cho từng thành phần trong mỗi thực thể. Đó là giải pháp "ABCABCABC". Chúng ta hãy nhóm tất cả các cập nhật thành phần giống hệt nhau. Sau đó, chúng ta có thể cập nhật tất cả các thành phần A, tiếp theo là B, v.v. Chúng ta cần gì để thực hiện điều này?
Đầu tiên, chúng ta cần Quản lý thành phần. Đối với mọi loại thành phần trong trò chơi, chúng tôi cần một lớp người quản lý. Nó có chức năng cập nhật sẽ cập nhật tất cả các thành phần của loại đó. Nó có một hàm tạo sẽ thêm một thành phần mới của loại đó và một hàm loại bỏ sẽ phá hủy thành phần được chỉ định. Có thể có các hàm trợ giúp khác để nhận và đặt dữ liệu cụ thể cho thành phần đó (ví dụ: đặt mô hình 3D cho Thành phần mô hình). Lưu ý rằng người quản lý theo một cách nào đó là một hộp đen với thế giới bên ngoài. Chúng tôi không biết làm thế nào dữ liệu của từng thành phần được lưu trữ. Chúng tôi không biết làm thế nào mỗi thành phần được cập nhật. Chúng tôi không quan tâm, miễn là các thành phần hoạt động như họ cần.
Tiếp theo chúng ta cần một thực thể. Bạn có thể biến nó thành một lớp học, nhưng điều đó hầu như không cần thiết. Một thực thể có thể không có gì nhiều hơn một ID số nguyên duy nhất hoặc một chuỗi được băm (cũng là một số nguyên). Khi bạn tạo một thành phần cho Thực thể, bạn chuyển ID làm đối số cho Trình quản lý. Khi bạn muốn xóa thành phần, bạn lại chuyển ID. Có thể có một số lợi thế khi thêm một chút dữ liệu vào Thực thể thay vì chỉ làm ID, nhưng chúng sẽ chỉ là các hàm trợ giúp vì như tôi đã liệt kê trong các yêu cầu, tất cả hành vi thực thể được xác định bởi chính các thành phần. Đó là động cơ của bạn, vì vậy hãy làm những gì có ý nghĩa với bạn.
Những gì chúng ta cần là một Trình quản lý thực thể. Lớp này sẽ tạo ID duy nhất nếu bạn sử dụng giải pháp chỉ ID hoặc nó có thể được sử dụng để tạo / quản lý các đối tượng Thực thể. Nó cũng có thể giữ một danh sách tất cả các thực thể trong trò chơi nếu bạn cần điều đó. Trình quản lý thực thể có thể là lớp trung tâm của hệ thống thành phần của bạn, lưu trữ các tham chiếu đến tất cả các Trình quản lý thành phần trong trò chơi của bạn và gọi các chức năng cập nhật của chúng theo đúng thứ tự. Bằng cách đó, tất cả các vòng lặp trò chơi phải làm là gọi EntityManager.update () và toàn bộ hệ thống được tách biệt với phần còn lại của động cơ của bạn.
Đó là chế độ xem mắt chim, chúng ta hãy xem cách các nhà quản lý thành phần làm việc. Đây là những gì bạn cần:
- Tạo dữ liệu thành phần khi tạo (entityID) được gọi
- Xóa dữ liệu thành phần khi remove (entityID) được gọi
- Cập nhật tất cả dữ liệu thành phần (áp dụng) khi update () được gọi (nghĩa là không phải tất cả các thành phần cần cập nhật từng khung)
Cái cuối cùng là nơi bạn xác định hành vi / logic thành phần và phụ thuộc hoàn toàn vào loại thành phần bạn đang viết. AnimationComponent sẽ cập nhật dữ liệu Hoạt hình dựa trên khung hình. DragableComponent sẽ chỉ cập nhật một thành phần đang được chuột kéo. ChemistryComponent sẽ cập nhật dữ liệu trong hệ thống vật lý. Tuy nhiên, vì bạn cập nhật tất cả các thành phần cùng loại trong một lần, bạn có thể thực hiện một số tối ưu hóa không thể có khi mỗi thành phần là một đối tượng riêng biệt với chức năng cập nhật có thể được gọi bất cứ lúc nào.
Lưu ý rằng tôi vẫn chưa bao giờ kêu gọi tạo lớp XxxComponent để giữ dữ liệu thành phần. Tùy bạn. Bạn có thích Thiết kế hướng dữ liệu? Sau đó cấu trúc dữ liệu trong các mảng riêng cho từng biến. Bạn có thích Thiết kế hướng đối tượng? (Tôi không khuyến nghị điều đó, nó vẫn sẽ giết chết hiệu suất của bạn ở nhiều nơi) Sau đó, tạo một đối tượng XxxComponent sẽ giữ dữ liệu của từng thành phần.
Điều tuyệt vời về các nhà quản lý là đóng gói. Bây giờ đóng gói là một trong những triết lý bị lạm dụng khủng khiếp nhất trong thế giới lập trình. Đây là cách nó nên được sử dụng. Chỉ người quản lý biết dữ liệu thành phần nào được lưu trữ ở đâu, cách thức hoạt động của logic thành phần. Có một vài chức năng để lấy / đặt dữ liệu nhưng đó là nó. Bạn có thể viết lại toàn bộ trình quản lý và các lớp bên dưới của nó và nếu bạn không thay đổi giao diện chung, thậm chí không có ai thông báo. Thay đổi động cơ vật lý? Chỉ cần viết lại ChemistryComponentManager và bạn đã hoàn thành.
Sau đó, có một điều cuối cùng: giao tiếp và chia sẻ dữ liệu giữa các thành phần. Bây giờ điều này là khó khăn và không có giải pháp một kích cỡ phù hợp cho tất cả. Bạn có thể tạo các hàm get / set trong các trình quản lý để cho phép, ví dụ thành phần va chạm để lấy vị trí từ thành phần vị trí (ví dụ: PositionManager.getP vị trí (entityID)). Bạn có thể sử dụng một hệ thống sự kiện. Bạn có thể lưu trữ một số dữ liệu được chia sẻ trong thực thể (giải pháp xấu nhất theo ý kiến của tôi). Bạn có thể sử dụng (cái này thường được sử dụng) một hệ thống nhắn tin. Hoặc sử dụng kết hợp nhiều hệ thống! Tôi không có thời gian hoặc kinh nghiệm để đi vào từng hệ thống này, nhưng google và tìm kiếm stackoverflow là bạn của bạn.