Mẹo xử lý thông báo Hệ thống thực thể dựa trên thành phần


8

Tôi đang cố gắng thực hiện một hệ thống thực thể dựa trên thành phần nhưng hơi bối rối về cách tôi nên xử lý tin nhắn. Có hai vấn đề tôi muốn giải quyết để tôi có thể kiểm tra hệ thống. Dưới đây là mã tôi có cho đến nay,

Lớp thực thể:

class Entity{
public:
    Entity(unsigned int id):
    id_(id)
    {};
    void handleMessage(BaseMessage &message){
        for(auto element: components_){
            element.second->handleMessage(message);
        }
    }
    template<class T>
    void attachComponent(T *component){
        //Consider making safer in case someone tries to attach same component type twice
        components_[typeid(T).hash_code()] = component;
    }
    template<class T>
    void detachComponent(void){
        components_.erase(typeid(T).hash_code());
    }
    template<class T>
    T* getComponent(void)const{
        return *components_.find(typeid(T).hash_code());
    }
    unsigned int getInstanceID(void)const{
        return id_;
    }
private:
    unsigned int id_;
    std::map<size_t, BaseComponent*> components_;
};

Các lớp Thành phần và Thông báo Cơ sở:

class BaseComponent{
public:
    virtual void handleMessage(BaseMessage &message){};
};

class BaseMessage{
public:
    virtual int getType(void) = 0;
};

1. Xử lý loại tin nhắn

Câu hỏi đầu tiên của tôi là làm thế nào tôi nên xử lý các loại thông báo khác nhau (xuất phát từ BaseMessage).

Tôi đã nghĩ ra hai cách để xử lý các loại tin nhắn của các loại tin nhắn dẫn xuất. Một là tạo ra một hàm băm (tức là sử dụng FNV) từ một chuỗi đặt tên loại thông báo và sử dụng hàm băm đó để xác định loại thông báo. Vì vậy, handleMessage(BaseMessage &message)hàm, trước tiên sẽ trích xuất hàm băm này từ tin nhắn và sau đó thực hiện static_cast thành loại thích hợp.

Phương thức thứ hai là sử dụng một khuôn mẫu như sau (tương tự như các attachComponentphương thức của lớp thực thể),

template<class T>
handleMessage(T& message){};

và thực hiện các chuyên môn cho từng loại thông báo mà thành phần cụ thể sẽ thực hiện.

Có bất kỳ nhược điểm nào khi sử dụng phương pháp thứ hai không? Còn về hiệu suất, tại sao tôi không thấy loại sử dụng này thường xuyên hơn?

2. Xử lý đầu vào

Câu hỏi thứ hai của tôi là cách tối ưu (về độ trễ và dễ sử dụng) để xử lý đầu vào là gì?

Suy nghĩ của tôi là tạo ra một InputHandlerComponentthanh ghi với lớp bàn phím để nghe các phím bấm cụ thể được xác định có thể có trong một số tệp. Ví dụ

keyboard.register( player.getComponent<InputHandler>() , 'W')

Tôi ước có một hướng dẫn ngắn gọn hơn cho các hệ thống dựa trên thành phần nhưng tôi đoán có nhiều cách khác nhau để làm những việc tương tự. Tôi có nhiều câu hỏi hơn nhưng tôi nghĩ sẽ khôn ngoan hơn khi lần đầu tiên thử thực hiện những gì tôi có thể.

Câu trả lời:


3

Trong một hệ thống tổ chức điển hình khi rời khỏi hệ thống để có tất cả các logic cho trò chơi và các thành phần của bạn để lưu trữ dữ liệu duy nhất , và các tổ chức của bạn chỉ là một dạng đơn giản. Cách xử lý đầu vào sẽ dễ dàng hơn nhiều và không đề cập đến tính linh hoạt của trò chơi của bạn.

Có rất nhiều cách để xử lý các sự kiện, chẳng hạn như: mẫu quan sát hoặc tín hiệu / vị trí. Bạn có thể thực hiện xử lý tin nhắn bằng các mẫu / hàm ảo hoặc con trỏ hàm, tuy nhiên có thể dễ dàng hơn khi sử dụng tín hiệu boost :: , thậm chí còn biết rằng nó không tuyệt vời cho hiệu suất. Nếu bạn muốn hiệu suất *, tôi khuyên bạn nên sử dụng các mẫu và hàm ảo hoặc con trỏ hàm.

* Tôi nhận thấy rằng mã của bạn thực sự có thể được tối ưu hóa. Có những lựa chọn thay thế cho typeidtoán tử, thực sự nhanh hơn so với sử dụng typeid. Chẳng hạn như sử dụng các mẫu / macro và số nguyên đơn giản để xác định ID của lớp.

Bạn có thể nhìn vào tôi Hệ thống Entity nếu bạn cần một số nguồn cảm hứng (được lấy cảm hứng từ khuôn khổ Java Artemis ). Mặc dù tôi chưa thực hiện một cách nào để xử lý các sự kiện (trừ các sự kiện liên quan đến thực thể), tôi đã để nó cho người dùng, nhưng sau khi nói ra thư viện entityx mà tôi tìm thấy trên reddit. Tôi hình dung tôi có thể thêm một hệ thống sự kiện vào thư viện của mình. Xin lưu ý rằng hệ thống thực thể của tôi không hoàn thành 100% về các tính năng tôi muốn, nhưng nó hoạt động và có hiệu suất tốt (nhưng tôi có thể tối ưu hóa, đặc biệt là với cách tôi lưu trữ các thực thể).

Đây là những gì bạn có thể làm để xử lý các sự kiện (lấy cảm hứng từ thực thể ):

BaseReceiver / BaseListener

  • Một lớp cơ sở để bạn có thể lưu trữ người nghe / người nhận.

Người nhận / người nghe

  • Đây là một lớp mẫu và yêu cầu bạn ghi đè hàm ảo recieve(const T&), trong đó T là thông tin sự kiện.
  • Các lớp muốn được thông báo bởi các sự kiện gửi thông tin cụ thể khi một sự kiện xảy ra phải kế thừa từ lớp này.

Xử lý sự kiện

  • Sự kiện phát ra / bắn
  • Có một danh sách các đối tượng Người nhận / Người nghe sẽ được thông báo bởi các sự kiện bị sa thải

Tôi đã triển khai thiết kế này trong C ++, ngay bây giờ, không sử dụng tín hiệu boost :: . Bạn có thể thấy nó ở đây .

Ưu

  • Gõ tĩnh
  • Các chức năng ảo (nhanh hơn tín hiệu boost :: cũng vậy)
  • Biên dịch lỗi thời gian nếu bạn không phát ra thông báo chính xác
  • Tương thích C ++ 98 (tôi tin)

Nhược điểm

  • Yêu cầu bạn xác định hàm functor (bạn không thể xử lý các sự kiện trong hàm toàn cục)
  • Không có hàng đợi sự kiện; chỉ cần đăng ký và bắn đi. (có thể không phải là những gì bạn muốn)
  • Không phải cuộc gọi trực tiếp (không nên quá tệ cho các sự kiện)
  • Không xử lý nhiều sự kiện trên cùng một lớp (điều này có thể được sửa đổi để nó cho phép nhiều sự kiện, thông qua nhiều kế thừa, nhưng có thể mất một thời gian để thực hiện)

Ngoài ra, tôi có một ví dụ trong hệ thống thực thể của mình, nó xử lý kết xuất và xử lý đầu vào, nó khá đơn giản nhưng nó trình bày nhiều khái niệm để hiểu thư viện của tôi.


Bạn có thể đưa ra một ví dụ về một cách nhanh hơn để khám phá các loại mà không cần typid?
Luke B.

"Trong một hệ thống thực thể điển hình, bạn để các hệ thống có tất cả logic cho trò chơi và các thành phần của bạn chỉ lưu trữ dữ liệu" - không có thứ gọi là hệ thống thực thể 'điển hình'.
Den

@LukeB. Nhìn vào mã nguồn của hệ thống thực thể của tôi, tôi không sử dụng typeid(nhìn vào Thành phần và Thành phần liên kết). Về cơ bản, tôi lưu trữ "loại" của một thành phần dưới dạng một số nguyên, tôi có một số nguyên toàn cầu mà tôi tăng cho mỗi loại thành phần. Và tôi lưu trữ các thành phần trong một mảng 2d, trong đó chỉ mục đầu tiên là ID của thực thể và chỉ mục thứ 2 là ID của loại thành phần, tức là các thành phần [entityId] [componentTypeId]
Miguel.martin 17/1/13

@ Miguel.martin. Cảm ơn bạn vì câu trả lời. Tôi nghĩ rằng sự nhầm lẫn của tôi chủ yếu đến từ thực tế có một số cách để thực hiện hệ thống thực thể và với tôi tất cả đều có nhược điểm và lợi ích của chúng. Ví dụ, trong cách tiếp cận bạn sử dụng, bạn phải đăng ký các loại thành phần mới bằng CRTP, điều mà cá nhân tôi không thích vì yêu cầu này không rõ ràng. Tôi nghĩ rằng tôi sẽ phải xem xét lại việc thực hiện của tôi.
Grieverlove

1
@MarsonMao đã sửa
Miguel.martin

1

Tôi đang làm việc trên một hệ thống thực thể dựa trên thành phần trong C #, nhưng các ý tưởng và mẫu chung vẫn được áp dụng.

Cách tôi xử lý các loại thông báo là mỗi lớp con thành phần gọi RequestMessage<T>(Action<T> action) where T : IMessagephương thức được bảo vệ của Thành phần . Trong tiếng Anh, điều đó có nghĩa là lớp con thành phần yêu cầu một loại tin nhắn cụ thể và cung cấp một phương thức được gọi khi thành phần nhận được một tin nhắn thuộc loại đó.

Phương thức đó được lưu trữ và khi thành phần nhận được một thông báo, nó sử dụng sự phản chiếu để lấy loại của thông điệp, sau đó được sử dụng để tra cứu phương thức liên quan và gọi nó.

Bạn có thể thay thế sự phản chiếu bằng bất kỳ hệ thống nào khác mà bạn muốn, băm là đặt cược tốt nhất của bạn. Nhìn vào nhiều công vănmẫu khách truy cập cho một số ý tưởng khác mặc dù.

Đối với đầu vào, tôi đã chọn làm một cái gì đó hoàn toàn khác với những gì bạn đang nghĩ. Trình xử lý đầu vào sẽ chuyển đổi riêng bàn phím / chuột / gamepad thành bảng liệt kê cờ (bitfield) của các hành động có thể (MoveForward, MoveBackwards, StrafeLeft, Sử dụng, v.v.) dựa trên cài đặt của người dùng / những gì người chơi đã cắm vào máy tính. Mỗi thành phần có một UserInputMessagekhung hình chứa cả bitfield và trục nhìn của người chơi dưới dạng vector 3d (hướng phát triển theo hướng bắn súng góc nhìn người thứ nhất). Điều này cũng giúp dễ dàng cho phép người chơi thay đổi các ràng buộc chính của họ.

Giống như bạn đã nói ở cuối câu hỏi của bạn, có nhiều cách khác nhau để tạo ra một hệ thống thực thể dựa trên thành phần. Tôi đã hướng hệ thống của mình rất nhiều vào trò chơi mà tôi đang tạo ra, do đó, một số điều tôi làm có thể không có ý nghĩa gì trong bối cảnh, nói, một RTS, nhưng nó vẫn là thứ được triển khai và nó đang hoạt động tôi cho đến nay

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.