Làm thế nào các thực thể trong một trò chơi tham chiếu lẫn nhau?


7

Tôi đã đọc rất nhiều về các mẫu thiết kế, nhưng khi sử dụng các mẫu này, có một câu hỏi mà tôi tiếp tục quay trở lại. Làm thế nào các thực thể của tôi nên truy cập thông tin về nhau?

Giả sử tôi đang sử dụng một mẫu đơn giản trong đó mỗi đối tượng cập nhật và tự vẽ:

class Monster extends GameEntity {
    public void update(double delta) {}
    public void draw(Graphics g) {}
}

Tôi đang viết vòng cập nhật của mình, nhưng tôi nhận ra rằng đối với Monster đòi hỏi kiến ​​thức về vị trí của người chơi, nơi các thành phố trên bản đồ chính hoặc một số dữ liệu ngẫu nhiên khác.

Làm thế nào để Monster có được điều này? Tôi có khởi tạo nó với một tham chiếu đến mọi thứ có thể cần không?

class Monster extends GameEntity {
    public Monster(Player player, City[] cityList) {
        this.player = player;
        this.cityList = cityList;
    }
}

Trông có vẻ khá lộn xộn. Hay tôi có một Người quản lý chính, có quyền truy cập vào mọi thứ, và chuyển nó cho mọi thứ?

class Monster extends GameEntity {
    public Monster(GiantGlobalManager manager) {
        this.manager = manager;
    }

    public void update(double delta) {
        cities = this.manager.getCities();
    }
}

Hoặc có lẽ có người quản lý như singleton? Chúng tôi cũng có thể phá vỡ các nhà quản lý vào các lĩnh vực quan tâm của họ.

class Monster extends GameEntity {
    public void update(double delta) {
        cities = CityManager.getInstance().getCities();
        player = PlayerManager.getInstance().getPlayer();

        guiManager.getInstance().createMenu();
    }
}

Nhưng điều này vẫn không cảm thấy hoàn toàn đúng. Bây giờ tôi phải duy trì tất cả các trình quản lý này và có vẻ như các đối tượng của tôi đã thực hiện tất cả các cuộc gọi tĩnh này. Thứ hai, nhiều đối tượng trong số này không nên có quyền truy cập vào mọi thứ được cung cấp bởi các lớp này. Để mọi thứ truy cập mọi thứ cảm thấy như một cảnh sát đối với tôi.

Bất cứ mẫu nào tôi đang sử dụng - cho dù tôi có phá vỡ các thực thể thành các thành phần hay chỉ di chuyển bản vẽ của mình và cập nhật các vòng lặp để tách các đối tượng - tôi vẫn cần một số cách để chúng giao tiếp. Một cách tốt để đi về điều này là gì? Tôi đang thiếu gì ở đây?

Tất cả điều này nên được thực hiện thông qua các sự kiện? Nhưng nhận được một danh sách các thành phố không thực sự là một sự kiện, nó chỉ là dữ liệu mà quái vật cần.

Câu trả lời:


6

Như bạn đã nhận thấy, có những ưu và nhược điểm đối với từng sơ đồ kiến ​​trúc. Mỗi mẫu có ý nghĩa riêng về cách các đối tượng sẽ tương tác và khi nào.

Trước tiên, tôi khuyên bạn nên giới thiệu nhanh về thiết kế RẮN .

Bây giờ, những gì chúng tôi cố gắng thực hiện với kiến ​​trúc tốt là đủ trừu tượng mà chúng tôi không bị cản trở bởi hệ thống khi chúng tôi muốn thêm một tính năng. Nếu chúng ta có thể thực hiện điều đó với một vài singletons hoặc phương pháp toàn cầu theo cách mở rộng quy mô cho sự phát triển trong tương lai , thật tuyệt! Nó thậm chí có thể kết thúc nhanh hơn.

Bây giờ, cách tiếp cận hướng đối tượng thường có một hoặc nhiều nhà máy sản xuất các thực thể chung mà bạn thêm các thành phần hoặc các thực thể cụ thể trong một thiết kế không ECS. Các nhà máy này thường được chứa gần với lớp trò chơi cốt lõi. Như một ví dụ về phiên bản không phải ECS:

class Monster extends GameEntity {
    // Called by factory
    public Monster(EntityService service) {
        // store a private reference to the service
    }
    public GameEntity Find( /* parameters depend on game's needs */ ) {
        // call methods on service to locate entities given these conditions
    }
    public void Destroy() {
        myService.Remove(this);
    }

}

class MonsterFactory extends GameEntityFactory {
    private EntityService myService;
    public MonsterFactory(EntityService service) { /* ... */ }
    public GameEntity Spawn()
    {
        GameEntity entity = new Monster(myService);
        myService.Add(entity);
        return entity;
    }
}

class Physics {
    private EntityService myService;
    public Physics(EntityService service) {
        /* retain service in order to provide updates to entities */
    }
    public void update(double delta) {
        // make changes to each applicable entity in
        // "myService," which the entity will refine
        // in its own update method
    }
}

Chủ đề chung ở đây là mỗi đối tượng được xây dựng với một tham chiếu đến một dịch vụ cung cấp một số chức năng cốt lõi trong đó hành vi cụ thể được trừu tượng hóa theo cách đối tượng sẽ luôn hành xử. Cần tạo ra một con quái vật? Triệu tập nhà máy chế tạo quái vật từ dịch vụ thực thể. Cần tìm một thực thể khác? Đó là trong dịch vụ thực thể. Cần gửi tin nhắn đến tất cả các thực thể thuộc một loại nhất định (chẳng hạn như các thực thể vật lý)? Chỉ cần lọc chúng ra khỏi dịch vụ thực thể. Bằng cách này, bạn có thể tránh bị treo vào các tham chiếu đến các thực thể "đã chết".

Đây chỉ là một ví dụ. Trong thực tế, nhu cầu của trò chơi của bạn sẽ quyết định làm thế nào điều này phải kết hợp với nhau.


Điều này đã giúp rất nhiều, cảm ơn bạn. Một câu hỏi mặc dù - trong ví dụ của bạn dịch vụ có chứa một tham chiếu đến nhà máy và nhà máy cũng có một tham chiếu đến dịch vụ không? Đây không phải là loại phân cấp đối tượng theo chu kỳ có gì đó cần tránh?
Aelast

Có, chủ yếu là vì bất kỳ đối tượng nào vẫn được tham chiếu trong hệ thống phân cấp sẽ không bao giờ nhận được rác được thu thập và cây dễ dàng giữ trong đầu của bạn hơn là biểu đồ. Tuy nhiên, đó là một điều ác cần thiết vì chúng ta cần phải có một số phương tiện để tiếp cận mọi thứ. Nhưng, vì dịch vụ "sở hữu" mọi thứ, thật dễ dàng để đảm bảo rằng không có tài liệu tham khảo còn sót lại. Các đối tượng chỉ cần tự chăm sóc bản thân, và tránh bám vào nhau.
jzx

3

Một biến thể nhỏ cho việc triển khai của jzx sẽ là sử dụng một phương pháp cập nhật tương tự như cách bạn đã vẽ. Không có gì lạ trong thiết kế kiến ​​trúc để có một số đối tượng bối cảnh thường chứa rất nhiều thông tin trạng thái.

 public interface GameEntity {
   void update(GameContext ctx);
   void draw(RenderContext ctx);
 }

Do đó, bên trong một GameEntitylớp triển khai cụ thể, bạn có thể thấy phương thức cập nhật của mình được viết như sau:

 public void update(GameContext ctx) {
   GameObjectManager goManager = ctx.getGameObjectManager();
   if(!hasTarget()) {
     List<GameEntity> entities = goManager.getVisibleEntitiesWithinRadius(position, 30);
     if(!entities.isEmpty()) {
       setTarget(CollectionUtils.selectRandomFromList(entities));
     }
   }
   // do other stuffs
   SomeGameSystem sgSystem = ctx.getSomeGameSystem();
   sgSystem.process(this, ctx);
 }

Bạn có thể xem xét đảo ngược phương pháp này trong đó một hệ thống hoạt động như một nhà máy và nơi logic cập nhật của bạn cư trú. Các hệ thống này được cập nhật theo thứ tự xác định và thực hiện các nhóm hoạt động nhỏ trên một nhóm lớn các thực thể dựa trên các tiêu chí. Điều này thường mang lại các hoạt động thân thiện với bộ nhớ cache nhanh hơn và nhiều hơn.

Về cơ bản, logic cập nhật ở trên có thể được đẩy vào một loạt các hệ thống như thế này

 public AISystem implements System {

   public AISystem(SpacialQuerySystem spacialQuerySystem) {

   }

   public AIComponent createComponent(GameEntity entity) {
     AIComponent component = new AIComponent();
     component.setOwner(entity);
     components.put(entity, component);
     return component;
   }

   public AIComponent getComponent(GameEntity entity) {
     return components.get(entity);
   }

   @Override
   public void update(double deltaTime) {
     for(Entry<GameEntity, AIComponent> entry : components.entrySet()) {
       /* do whatever with the entity and use the injected systems */
     }
   }

 }

Cuối cùng, cách tiếp cận này trở nên dễ dàng hơn nhiều để duy trì lâu dài vì bạn thường có thể xây dựng các hệ thống nền tảng mà bạn sử dụng lại trong các hệ thống phức tạp hơn mà cuối cùng thực hiện một số nhu cầu trò chơi. Ngoài ra, việc sử dụng hàm xây dựng xác định rõ ràng các phụ thuộc trong kiến ​​trúc của bạn mà không phải lọc qua mã để xác định những gì cần những gì.

Tất cả những gì còn lại là để có một số trình tải bản đồ hoặc để hiểu cách đọc dữ liệu cấp độ của bạn và gọi các hệ thống khác nhau để xây dựng các thực thể của bạn. Nếu bạn sử dụng các nhà máy nguyên mẫu và nhà máy nguyên mẫu, có lẽ điều đó có thể được trừu tượng hóa thành những nhà máy cuối cùng tương tác với các hệ thống nói trên.

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.