Tổ chức một hệ thống thực thể với các nhà quản lý thành phần bên ngoài?


13

Tôi đang thiết kế một công cụ trò chơi cho một trò chơi bắn súng 2D nhiều người chơi từ trên xuống, mà tôi muốn có thể sử dụng lại một cách hợp lý cho các trò chơi bắn súng từ trên xuống khác. Hiện tại tôi đang suy nghĩ về cách một cái gì đó giống như một hệ thống thực thể trong nó nên được thiết kế. Đầu tiên tôi nghĩ về điều này:

Tôi có một lớp gọi là EntityManager. Nó nên thực hiện một phương thức gọi là Cập nhật và một phương thức khác gọi là Draw. Lý do để tôi tách Logic và Kết xuất là vì sau đó tôi có thể bỏ qua phương thức Draw nếu chạy một máy chủ độc lập.

EntityManager sở hữu một danh sách các đối tượng thuộc loại BaseEntity. Mỗi thực thể sở hữu một danh sách các thành phần như EntityModel (đại diện có thể rút ra của một thực thể), EntityNetworkInterface và EntityPhysicalBody.

EntityManager cũng sở hữu một danh sách các trình quản lý thành phần như EntityRenderManager, EntityNetworkManager và EntityPhysicsManager. Mỗi trình quản lý thành phần giữ các tham chiếu đến các thành phần thực thể. Có nhiều lý do khác nhau để di chuyển mã này ra khỏi lớp riêng của thực thể và thay vào đó thực hiện mã này. Ví dụ: tôi đang sử dụng thư viện vật lý bên ngoài, Box2D, cho trò chơi. Trong Box2D, trước tiên bạn thêm các cơ thể và hình dạng vào một thế giới (thuộc sở hữu của EntityPhysicsManager trong trường hợp này) và thêm các cuộc gọi lại va chạm (sẽ được gửi đến chính đối tượng thực thể trong hệ thống của tôi). Sau đó, bạn chạy một chức năng mô phỏng mọi thứ trong hệ thống. Tôi thấy thật khó để tìm một giải pháp tốt hơn để làm điều này hơn là làm nó trong một trình quản lý thành phần bên ngoài như thế này.

Việc tạo thực thể được thực hiện như thế này: EntityManager thực hiện phương thức RegisterEntity (entityClass, nhà máy) để đăng ký cách tạo một thực thể của lớp đó. Nó cũng thực hiện phương thức CreateEntity (entityClass) sẽ trả về một đối tượng kiểu BaseEntity.

Bây giờ đến vấn đề của tôi: Làm thế nào tham chiếu đến một thành phần được đăng ký cho các nhà quản lý thành phần? Tôi không biết làm thế nào tôi sẽ tham khảo các nhà quản lý thành phần từ một nhà máy / đóng cửa.


Tôi không biết có lẽ đây là một hệ thống kết hợp hay không, nhưng có vẻ như "người quản lý" của bạn là những gì tôi thường nghe gọi là "hệ thống"; tức là thực thể là một ID trừu tượng; Thành phần là nhóm dữ liệu; và những gì bạn gọi là "người quản lý" là những gì thường được gọi là "Hệ thống". Tôi có diễn giải từ vựng chính xác không?
BRPocock


Hãy xem gamadu.com/artemis và xem liệu phương pháp của họ có trả lời câu hỏi của bạn không.
Patrick Hughes

1
Không có cách nào để thiết kế một hệ thống thực thể vì có ít sự đồng thuận về định nghĩa của nó. Những gì @BRPocock mô tả và cũng là những gì mà Artemis sử dụng từ chối được mô tả sâu hơn trên blog này: t-machine.org/index.php/carget/entity-systems cùng với wiki: entity-systems.wikidot.com
user8363

Câu trả lời:


6

Các hệ thống nên lưu trữ một cặp giá trị chính của Thực thể cho Thành phần trong một số loại Bản đồ, Đối tượng từ điển hoặc Mảng liên kết (tùy thuộc vào ngôn ngữ được sử dụng). Hơn nữa, khi bạn tạo Đối tượng Thực thể, tôi sẽ không lo lắng về việc lưu trữ nó trong trình quản lý trừ khi bạn cần có thể hủy đăng ký nó khỏi bất kỳ Hệ thống nào. Một thực thể là một tổng hợp của các thành phần, nhưng nó không nên xử lý bất kỳ cập nhật thành phần nào. Điều đó nên được xử lý bởi các hệ thống. Thay vào đó, hãy coi Thực thể của bạn như một khóa được ánh xạ tới tất cả các thành phần có trong hệ thống, cũng như một trung tâm liên lạc để các thành phần đó nói chuyện với nhau.

Phần lớn về các mô hình Entity-Element-System là bạn có thể thực hiện khả năng truyền thông điệp từ một thành phần này đến các thành phần còn lại của một thực thể khá dễ dàng. Điều này cho phép một thành phần nói chuyện với thành phần khác mà không thực sự biết thành phần đó là ai hoặc cách xử lý thành phần mà nó đang thay đổi. Thay vào đó, nó chuyển một thông điệp và cho phép thành phần tự thay đổi (nếu nó tồn tại)

Chẳng hạn, Hệ thống Vị trí sẽ không có nhiều mã trong đó, chỉ theo dõi các Đối tượng Thực thể được ánh xạ tới Thành phần Vị trí của chúng. Nhưng khi một vị trí thay đổi, họ có thể gửi tin nhắn đến Thực thể có liên quan, đến lượt nó được gửi đến tất cả các thành phần của thực thể đó. Một vị trí thay đổi cho lý do bao giờ? Hệ thống vị trí gửi cho Thực thể một thông báo nói rằng vị trí đã thay đổi và ở đâu đó, thành phần kết xuất hình ảnh của thực thể đó nhận được thông báo đó và cập nhật nơi nó sẽ tự vẽ tiếp theo.

Ngược lại, Hệ thống Vật lý cần biết tất cả các đối tượng của nó đang làm gì; Nó phải có khả năng nhìn thấy tất cả các vật thể trên thế giới để kiểm tra va chạm. Khi xảy ra xung đột, nó cập nhật thành phần chỉ đường của Thực thể bằng cách gửi một số loại "Thông báo thay đổi hướng" đến thực thể thay vì tham khảo trực tiếp thành phần của Thực thể. Điều này ngăn cản người quản lý cần biết cách thay đổi hướng bằng cách sử dụng tin nhắn thay vì dựa vào một thành phần cụ thể ở đó (trong đó có thể không có ở đó, trong trường hợp đó, tin nhắn sẽ rơi vào tai người điếc thay vì một số lỗi xảy ra vì một đối tượng dự kiến ​​vắng mặt).

Bạn sẽ nhận thấy một lợi thế rất lớn từ việc này vì bạn đã đề cập rằng bạn có Giao diện Mạng. Một Thành phần Mạng sẽ lắng nghe tất cả các tin nhắn đến mà mọi người khác nên biết. Nó thích những tin đồn. Sau đó, khi Hệ thống mạng cập nhật, các thành phần Mạng gửi các tin nhắn đó đến các Hệ thống mạng khác trên các máy khách khác, sau đó gửi lại các tin nhắn đó đến tất cả các thành phần khác để cập nhật vị trí trình phát, v.v. gửi tin nhắn qua mạng nhưng đó là vẻ đẹp của Hệ thống, bạn có thể kiểm soát logic đó bằng cách đăng ký những thứ phù hợp với nó.

Nói ngắn gọn:

Thực thể là một thành phần của các thành phần có thể nhận tin nhắn. Thực thể có thể nhận tin nhắn, ủy quyền tin nhắn cho tất cả các thành phần của chúng để cập nhật chúng. (Vị trí đã thay đổi Tin nhắn, Hướng thay đổi tốc độ, v.v.) Nó giống như một hộp thư trung tâm mà tất cả các thành phần có thể nghe thấy nhau thay vì nói chuyện trực tiếp với nhau.

Thành phần là một phần nhỏ của Thực thể lưu trữ một số trạng thái của thực thể. Đây có thể phân tích một số tin nhắn nhất định và ném các tin nhắn khác ra. Chẳng hạn, "Thành phần chỉ đường" sẽ chỉ quan tâm đến "Thông báo thay đổi hướng" chứ không phải "Thông báo thay đổi vị trí". Các thành phần cập nhật trạng thái của riêng chúng dựa trên tin nhắn và sau đó cập nhật trạng thái của các thành phần khác bằng cách gửi tin nhắn từ Hệ thống của chúng.

Hệ thống quản lý tất cả các thành phần của một loại nhất định và chịu trách nhiệm cập nhật các thành phần nói trên từng khung, cũng như gửi tin nhắn từ thành phần mà chúng quản lý đến các Thực thể mà Thành phần thuộc về

Các hệ thống có thể có thể cập nhật song song tất cả các thành phần của chúng và lưu trữ tất cả các tin nhắn khi chúng đi. Sau đó, khi thực hiện tất cả các phương thức cập nhật của Hệ thống hoàn tất, bạn yêu cầu mỗi hệ thống gửi tin nhắn của chúng theo một thứ tự cụ thể. Điều khiển trước tiên có thể, tiếp theo là Vật lý, tiếp theo là hướng, vị trí, kết xuất, v.v ... Vấn đề là chúng được gửi theo thứ tự nào vì Thay đổi hướng Vật lý phải luôn luôn cân nhắc thay đổi hướng điều khiển.

Hi vọng điêu nay co ich. Đó là một địa ngục của Mẫu thiết kế, nhưng nó cực kỳ mạnh mẽ nếu được thực hiện đúng.


0

Tôi đang sử dụng một hệ thống tương tự trong công cụ của mình và cách tôi thực hiện là mỗi Thực thể chứa một danh sách Thành phần. Từ EntityManager, tôi có thể truy vấn từng Thực thể và xem những thực thể nào chứa Thành phần nhất định. Thí dụ:

class Component
{
    private uint ID;
    // etc...
}

class Entity
{
    List<Component> Components;
    // etc...
    public bool Contains(Type type)
    {
        foreach(Component comp in Components)
        {
            if(typeof(comp) == type)
                return true;
        }
        return false;
    }
}

class EntityManager
{
    List<Entity> Entities;
    // etc...
    public List<Entity> GetEntitiesOfType(Type type)
    {
        List<Entity> results = new List<Entity>();
        foreach(Entity entity in Entities)
        {
            if(entity.Contains(type))
                results.Add(entity);
        }
        return results;
    }
}

Rõ ràng đây không phải là mã chính xác (bạn thực sự cần một hàm mẫu để kiểm tra các loại thành phần khác nhau, thay vì sử dụng typeof) nhưng khái niệm là có. Sau đó, bạn có thể lấy các kết quả đó, tham khảo thành phần bạn đang tìm kiếm và đăng ký nó với nhà máy của bạn. Điều này ngăn chặn bất kỳ khớp nối trực tiếp giữa các thành phần và người quản lý của họ.


3
Trống rỗng: tại thời điểm mà Thực thể của bạn chứa dữ liệu, đó là một đối tượng, không phải là một thực thể, Một người mất hầu hết các lợi ích làm tê liệt (sic?) Của ECS trong cấu trúc này. Các hệ thống E / C / S "thuần túy" là quan hệ chứ không phải hướng đối tượng Không phải là "xấu" đối với một số trường hợp, nhưng chắc chắn là "phá vỡ mô hình quan hệ"
BRPocock

2
Tôi không chắc là tôi hiểu bạn. Sự hiểu biết của tôi (và vui lòng sửa cho tôi nếu tôi sai) là Hệ thống Thành phần-Thực thể cơ bản có một lớp Thực thể chứa các Thành phần và có thể có ID, tên hoặc một số định danh. Tôi nghĩ rằng chúng ta có thể có một sự hiểu lầm trong ý nghĩa của "loại" thực thể. Khi tôi nói Entity "type", tôi đang nói đến các loại Thành phần. Điều đó có nghĩa là, một Thực thể là loại "Sprite" nếu nó chứa Thành phần Sprite.
Mike Cluck

1
Trong một hệ thống Thực thể / Thành phần thuần túy, một Thực thể thường là nguyên tử: vd typedef long long int Entity; Thành phần là một bản ghi (nó có thể được triển khai như một lớp đối tượng hoặc chỉ là một struct) có tham chiếu đến Thực thể mà nó được đính kèm; và một Hệ thống sẽ là một phương thức hoặc tập hợp các phương thức. Mô hình ECS không tương thích với mô hình OOP, mặc dù Thành phần có thể là Đối tượng (hầu hết) chỉ là dữ liệu và là Đối tượng đơn lẻ chỉ có mã có trạng thái sống trong các thành phần ... mặc dù các hệ thống "lai" là phổ biến hơn những thứ "thuần túy", chúng mất đi nhiều lợi ích bẩm sinh.
BRPocock

2
@BRPocock lại hệ thống thực thể "thuần túy". Tôi nghĩ rằng một thực thể như một đối tượng là hoàn toàn tốt, nó không phải là một id đơn giản. Một điều là biểu diễn tuần tự, một cách bố trí trong bộ nhớ khác của một đối tượng / một khái niệm / một thực thể. Miễn là bạn có thể duy trì tính điều khiển dữ liệu, người ta không nên bị ràng buộc với mã không thành ngữ chỉ vì đó là cách "thuần túy".
Raine

1
@BRPocock đây là một cảnh báo công bằng, nhưng đối với các hệ thống thực thể giống như "máy t". Tôi hiểu tại sao, nhưng đó không phải là cách duy nhất để mô hình hóa các thực thể dựa trên thành phần. diễn viên là một thay thế thú vị. Tôi có xu hướng đánh giá cao họ hơn, đặc biệt là đối với các thực thể thuần túy logic.
Raine

0

1) Phương thức Factory của bạn phải được chuyển tham chiếu đến EntityManager đã gọi nó (Tôi sẽ sử dụng C # làm ví dụ):

delegate BaseEntity EntityFactory(EntityManager manager);

2) Have CreatEntity cũng nhận được một id (ví dụ: chuỗi, số nguyên, tùy thuộc vào bạn) bên cạnh lớp / loại thực thể và tự động đăng ký thực thể được tạo trên Từ điển bằng cách sử dụng id đó làm khóa:

class EntityManager
{
    // Rest of class omitted

    BaseEntity CreateEntity(string id, Type entityClass)
    {
        BaseEntity entity = factories[entityClass](this);
        registry.Add(id, entity);
        return entity;
    }

    Dictionary<Id, BaseEntity> registry;
}

3) Thêm Getter vào EntityManager để nhận bất kỳ thực thể nào bằng ID:

class EntityManager
{
    // Rest of class omitted

    BaseEntity GetEntity(string id)
    {
        return registry[id];
    }
}

Và đó là tất cả những gì bạn cần để tham khảo bất kỳ Trình quản lý thành phần nào trong phương thức Factory của bạn. Ví dụ:

BaseEntity CreateSomeSortOfEntity(EntityManager manager)
{
    // Create and configure entity
    BaseEntity entity = new BaseEntity();
    RenderComponent renderComponent = new RenderComponent();
    entity.AddComponent(renderComponent);

    // Get a reference to the render manager and register component
    RenderEntityManager renderer = manager.GetEntity("RenderEntityManager") as RenderEntityManager;
    if(renderer != null)
        renderer.Register(renderComponent)

    return entity;
}

Ngoài Id, bạn cũng có thể sử dụng một số loại thuộc tính Loại (enum tùy chỉnh hoặc chỉ dựa vào hệ thống loại ngôn ngữ) và tạo một getter trả về tất cả BaseEntities của một loại nhất định.


1
Không phải là mô phạm, nhưng một lần nữa, trong một hệ thống Thực thể (quan hệ) thuần túy, các thực thể không có loại nào, ngoại trừ việc truyền cho chúng nhờ vào các thành phần của chúng
Tấn

@BRPocock: Bạn có thể tạo một ví dụ tuân theo đức tính trong sạch không?
Zolomon

1
@Raine Có lẽ, tôi không có kinh nghiệm đầu tiên về điều này, nhưng đó là những gì tôi đọc được. Và có những tối ưu hóa bạn có thể thực hiện để giảm thời gian tìm kiếm các thành phần theo id. Đối với sự kết hợp bộ đệm, tôi nghĩ rằng nó có ý nghĩa vì bạn đang lưu trữ dữ liệu cùng loại liên tục trong bộ nhớ, đặc biệt là khi các thành phần của bạn là thuộc tính nhẹ hoặc đơn giản. Tôi đã đọc rằng một lỗi bộ nhớ cache duy nhất trên PS3 có thể tốn kém như một ngàn hướng dẫn CPU và cách tiếp cận lưu trữ dữ liệu cùng loại này là một kỹ thuật tối ưu hóa rất phổ biến trong phát triển trò chơi hiện đại.
David Gouveia

2
Trong tài liệu tham khảo: hệ thống thực thể thuần túy của dòng: ID thực thể thường là một cái gì đó như : typedef unsigned long long int EntityID;; lý tưởng là, mỗi Hệ thống có thể sống trên một CPU hoặc máy chủ riêng biệt và chỉ yêu cầu tìm nạp các thành phần có liên quan đến / hoạt động trong Hệ thống đó. Với một đối tượng Thực thể, người ta có thể phải khởi tạo cùng một đối tượng Thực thể trên mỗi máy chủ, khiến việc mở rộng quy mô trở nên khó khăn hơn. Một mô hình hệ thống thành phần thực thể thuần túy phân chia xử lý giữa các nút (quy trình, CPU hoặc máy chủ) theo hệ thống, thay vì theo thực thể, thông thường.
BRPocock

1
@DavidGouveia đã đề cập đến các tối ưu hóa của Google, tìm kiếm các thực thể bằng ID. Thực tế, các hệ thống (vài) tôi đã thực hiện theo cách này, có xu hướng không làm như vậy. Thường xuyên hơn, chọn Thành phần theo một số mẫu cho biết họ quan tâm đến một Hệ thống cụ thể, chỉ sử dụng Thực thể (ID) cho các Tham gia nhiều thành phần.
BRPocock
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.