Làm cách nào tôi có thể truy cập chính xác các thành phần trong Hệ thống thực thể-thành phần C ++ của mình?


18

(Những gì tôi mô tả dựa trên thiết kế này: Khung hệ thống thực thể là gì ? , Cuộn xuống và bạn sẽ tìm thấy nó)

Tôi đang gặp một số vấn đề khi tạo một hệ thống thành phần thực thể trong C ++. Tôi có lớp Thành phần của mình:

class Component { /* ... */ };

Đó thực sự là một giao diện, cho các thành phần khác được tạo ra. Vì vậy, để tạo một thành phần tùy chỉnh, tôi chỉ cần thực hiện giao diện và thêm dữ liệu sẽ được sử dụng trong trò chơi:

class SampleComponent : public Component { int foo, float bar ... };

Các thành phần này được lưu trữ bên trong một lớp Thực thể, cung cấp cho mỗi phiên bản của Thực thể một ID duy nhất:

class Entity {
     int ID;
     std::unordered_map<string, Component*> components;
     string getName();
     /* ... */
};

Các thành phần được thêm vào thực thể bằng cách băm tên của thành phần (đây có thể không phải là một ý tưởng tuyệt vời). Khi tôi thêm một thành phần tùy chỉnh, nó được lưu trữ dưới dạng một loại Thành phần (lớp cơ sở).

Bây giờ, mặt khác, tôi có giao diện Hệ thống, sử dụng giao diện Node bên trong. Lớp Node được sử dụng để lưu trữ một số thành phần của một thực thể (vì Hệ thống không quan tâm đến việc sử dụng tất cả các thành phần của thực thể). Khi Hệ thống phải update(), nó chỉ cần lặp qua các Nút mà nó được lưu trữ được tạo từ các thực thể khác nhau. Vì thế:

/* System and Node implementations: (not the interfaces!) */

class SampleSystem : public System {
        std::list<SampleNode> nodes; //uses SampleNode, not Node
        void update();
        /* ... */
};

class SampleNode : public Node {
        /* Here I define which components SampleNode (and SampleSystem) "needs" */
        SampleComponent* sc;
        PhysicsComponent* pc;
        /* ... more components could go here */
};

Bây giờ là vấn đề: giả sử tôi xây dựng SampleNodes bằng cách chuyển một thực thể đến SampleSystem. Sau đó, SampleNode sẽ "kiểm tra" nếu thực thể có các thành phần bắt buộc được sử dụng bởi SampleSystem. Vấn đề xuất hiện khi tôi cần truy cập vào thành phần mong muốn bên trong Thực thể: thành phần được lưu trữ trong Componentbộ sưu tập (lớp cơ sở), vì vậy tôi không thể truy cập thành phần đó và sao chép nó sang nút mới. Tôi đã tạm thời giải quyết vấn đề bằng cách chuyển Componentxuống loại có nguồn gốc, nhưng tôi muốn biết liệu có cách nào tốt hơn để làm việc này không. Tôi hiểu nếu điều này có nghĩa là thiết kế lại những gì tôi đã có. Cảm ơn.

Câu trả lời:


23

Nếu bạn sắp lưu trữ Componenttất cả các bộ sưu tập lại với nhau thì bạn phải sử dụng một lớp cơ sở chung làm loại được lưu trữ trong bộ sưu tập, và do đó bạn phải chuyển sang đúng loại khi bạn cố truy cập Componentvào bộ sưu tập. Tuy nhiên, các vấn đề của việc cố gắng chuyển sang lớp dẫn xuất sai có thể được loại bỏ bằng cách sử dụng thông minh các mẫu và typeidhàm, tuy nhiên:

Với một bản đồ được tuyên bố như vậy:

std::unordered_map<const std::type_info* , Component *> components;

một hàm addComponent như:

components[&typeid(*component)] = component;

và một getComponent:

template <typename T>
T* getComponent()
{
    if(components.count(&typeid(T)) != 0)
    {
        return static_cast<T*>(components[&typeid(T)]);
    }
    else 
    {
        return NullComponent;
    }
}

Bạn sẽ không nhận được một miscast. Điều này là do typeidsẽ trả về một con trỏ tới thông tin loại của loại thời gian chạy (loại dẫn xuất nhất) của thành phần. Vì thành phần được lưu trữ với thông tin loại đó là khóa, nên diễn viên không thể gây ra sự cố do các loại không khớp. Bạn cũng có thể kiểm tra loại thời gian biên dịch trên loại mẫu vì nó phải là loại có nguồn gốc từ Thành phần hoặc static_cast<T*>sẽ có các loại không khớp với unordered_map.

Mặc dù vậy, bạn không cần lưu trữ các thành phần của các loại khác nhau trong bộ sưu tập chung. Nếu bạn từ bỏ ý tưởng về một s Entitycó chứa Component, và thay vào đó, mỗi Componentcửa hàng có một Entity(trong thực tế, nó có thể sẽ chỉ là một ID số nguyên), thì bạn có thể lưu trữ từng loại thành phần dẫn xuất trong bộ sưu tập của loại dẫn xuất thay vì loại cơ sở phổ biến và tìm Components "thuộc về" Entitythông qua ID đó.

Việc thực hiện thứ hai này hơi khó nghĩ hơn so với lần đầu tiên, nhưng có lẽ nó có thể bị ẩn đi vì các chi tiết triển khai đằng sau một giao diện nên người dùng hệ thống không cần phải quan tâm. Tôi sẽ không bình luận về cái nào tốt hơn vì tôi chưa thực sự sử dụng cái thứ hai, nhưng tôi không thấy việc sử dụng static_cast là một vấn đề với sự đảm bảo mạnh mẽ về các loại như cách triển khai đầu tiên cung cấp. Lưu ý rằng nó yêu cầu RTTI có thể hoặc không thể là một vấn đề tùy thuộc vào nền tảng và / hoặc niềm tin triết học.


3
Tôi đã sử dụng C ++ được gần 6 năm rồi, nhưng mỗi tuần tôi lại học được một số mẹo mới.
hiệp sĩ666

Cảm ơn đã trả lời. Tôi sẽ thử sử dụng phương pháp đầu tiên trước, và nếu sau này tôi có thể nghĩ ra cách sử dụng phương pháp khác. Nhưng, liệu addComponent()phương thức này có cần phải là một phương thức mẫu không? Nếu tôi xác định a addComponent(Component* c), mọi thành phần phụ tôi thêm sẽ được lưu trữ trong một Componentcon trỏ và typeidsẽ luôn tham chiếu đến Componentlớp cơ sở.
Federico

2
Typeid sẽ cung cấp cho bạn loại thực tế của đối tượng được trỏ đến ngay cả khi con trỏ thuộc lớp cơ sở
Chewy Gumball

Tôi thực sự thích câu trả lời của chewy, vì vậy tôi đã thử thực hiện nó trên mingw32. Tôi gặp vấn đề được đề cập bởi fede rico nơi addComponent () lưu trữ mọi thứ dưới dạng một thành phần vì typeid đang trả về thành phần làm kiểu cho mọi thứ. Ai đó ở đây đã đề cập rằng typeid sẽ cung cấp loại thực tế của đối tượng được trỏ đến ngay cả khi con trỏ là một lớp cơ sở, nhưng tôi nghĩ nó có thể thay đổi dựa trên trình biên dịch, v.v. Có ai khác có thể xác nhận điều này không? Tôi đã sử dụng g ++ std = c ++ 11 mingw32 trên windows 7. Cuối cùng tôi chỉ sửa đổi getComponent () thành một mẫu, sau đó lưu kiểu từ đó vào th
shwoseph

Đây không phải là trình biên dịch cụ thể. Bạn có thể không có biểu thức đúng như là một đối số cho hàm typeid.
Chewy Gumball

17

Chewy có quyền, nhưng nếu bạn đang sử dụng C ++ 11, bạn có một số loại mới bạn có thể sử dụng.

Thay vì sử dụng const std::type_info*làm chìa khóa trong bản đồ của bạn, bạn có thể sử dụng std::type_index( xem cppreference.com ), đây là một trình bao bọc xung quanh std::type_info. Tại sao bạn sẽ sử dụng nó? Thực std::type_indextế lưu trữ mối quan hệ với std::type_infomột con trỏ, nhưng đó là một con trỏ ít khiến bạn phải lo lắng.

Nếu bạn thực sự đang sử dụng C ++ 11, tôi khuyên bạn nên lưu trữ các Componenttài liệu tham khảo bên trong các con trỏ thông minh. Vì vậy, bản đồ có thể là một cái gì đó như:

std::map<std::type_index, std::shared_ptr<Component> > components

Thêm một mục mới có thể được thực hiện như vậy:

components[std::type_index(typeid(*component))] = component

nơi componentcó loại std::shared_ptr<Component>. Lấy một tham chiếu đến một loại nhất định Componentcó thể trông giống như:

template <typename T>
std::shared_ptr<T> getComponent()
{
    std::type_index index(typeid(T));
    if(components.count(std::type_index(typeid(T)) != 0)
    {
        return static_pointer_cast<T>(components[index]);
    }
    else
    {
        return NullComponent
    }
}

Lưu ý cũng sử dụng static_pointer_castthay vì static_cast.


1
Tôi thực sự đang sử dụng cách tiếp cận này trong dự án của riêng tôi.
vijoc

Điều này thực sự khá thuận tiện, vì tôi đã học C ++ bằng cách sử dụng chuẩn C ++ 11 làm tài liệu tham khảo. Mặc dù vậy, một điều tôi nhận thấy là tất cả các hệ thống thành phần thực thể mà tôi tìm thấy trên web đều sử dụng một số loại cast. Tôi bắt đầu nghĩ rằng sẽ không thể thực hiện được điều này, hoặc một thiết kế hệ thống tương tự mà không có phôi.
Federico

@Fede Lưu trữ Componentcon trỏ trong một container duy nhất không nhất thiết yêu cầu chuyển chúng xuống loại dẫn xuất. Nhưng, như Chewy đã chỉ ra, bạn có sẵn các tùy chọn khác cho mình mà không yêu cầu truyền vai. Bản thân tôi không thấy bất cứ điều gì "xấu" khi có loại phôi này trong thiết kế, vì chúng tương đối an toàn.
vijoc

@vijoc Đôi khi chúng bị coi là xấu do vấn đề kết hợp bộ nhớ mà chúng có thể giới thiệu.
akaltar
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.