Các lựa chọn thay thế cho nhiều kế thừa cho kiến ​​trúc của tôi (NPC trong trò chơi Chiến lược thời gian thực)?


11

Mã hóa thực sự không khó lắm . Phần khó là viết mã có ý nghĩa, dễ đọc và dễ hiểu. Vì vậy, tôi muốn có được một nhà phát triển tốt hơn và tạo ra một số kiến ​​trúc vững chắc.

Vì vậy, tôi muốn tạo một kiến ​​trúc cho các NPC trong một trò chơi video. Đây là một trò chơi Chiến lược thời gian thực như Starcraft, Age of Empires, Command & Conquers, v.v. Vì vậy, tôi sẽ có các loại NPC khác nhau. Một NPC có thể có từ một đến nhiều khả năng (phương pháp) của các: Build(), Farm()Attack().

Ví dụ:
Công nhân có thể Build()Farm()
Chiến binh có thể Attack()
Công dân có thể Build(), Farm()Attack()
Ngư dân có thể Farm()Attack()

Tôi hy vọng mọi thứ rõ ràng cho đến nay.

Vì vậy, bây giờ tôi có các loại NPC và khả năng của chúng. Nhưng chúng ta hãy đến với khía cạnh kỹ thuật / chương trình.
Điều gì sẽ là một kiến ​​trúc lập trình tốt cho các loại NPC khác nhau của tôi?

Được rồi tôi có thể có một lớp cơ sở. Thật ra tôi nghĩ đây là một cách tốt để tuân thủ nguyên tắc DRY . Vì vậy, tôi có thể có các phương thức như WalkTo(x,y)trong lớp cơ sở của mình vì mọi NPC sẽ có thể di chuyển. Nhưng bây giờ hãy đến với vấn đề thực sự. Tôi thực hiện khả năng của mình ở đâu? (hãy nhớ: Build(), Farm()Attack())

Vì các khả năng sẽ bao gồm cùng một logic, sẽ gây khó chịu / phá vỡ nguyên tắc DRY để triển khai chúng cho từng NPC (Công nhân, Chiến binh, ..).

Được rồi tôi có thể thực hiện các khả năng trong lớp cơ sở. Điều này sẽ đòi hỏi một số loại logic thẩm tra nếu một NPC có thể sử dụng khả năng X. IsBuilder, CanBuild, .. Tôi nghĩ rằng đó là rõ ràng những gì tôi muốn bày tỏ.
Nhưng tôi không cảm thấy tốt với ý tưởng này. Điều này nghe có vẻ như một lớp cơ sở cồng kềnh với quá nhiều chức năng.

Tôi sử dụng C # làm ngôn ngữ lập trình. Vì vậy, nhiều kế thừa không phải là một lựa chọn ở đây. Phương tiện: Có thêm các lớp cơ sở như Fisherman : Farmer, Attackersẽ không hoạt động.


3
Tôi đã xem xét ví dụ này có vẻ như là một giải pháp cho vấn đề này: en.wikipedia.org/wiki/Cysis_over_inherribution
Shawn Mclean

4
Câu hỏi này sẽ phù hợp hơn nhiều trên gamedev.stackexchange.com
Philipp

Câu trả lời:


14

Thành phần và kế thừa giao diện là những lựa chọn thay thế thông thường cho đa kế thừa cổ điển.

Tất cả những gì bạn mô tả ở trên bắt đầu bằng từ "có thể" là một khả năng có thể được biểu diễn bằng một giao diện, như trong ICanBuild, hoặc ICanFarm. Bạn có thể thừa hưởng nhiều giao diện như bạn nghĩ bạn cần.

public class Worker: ICanBuild, ICanFarm
{
    #region ICanBuild Implementation
    // Build
    #endregion

    #region ICanFarm Implementation
    // Farm
    #endregion
}

Bạn có thể chuyển vào các đối tượng xây dựng và canh tác "chung" thông qua hàm tạo của lớp. Đó là nơi sáng tác.


Chỉ cần suy nghĩ lớn: Các 'quan hệ' sẽ (nội bộ) là thay vì (Worker có Builder thay Worker là Builder). Ví dụ / mẫu bạn đã giải thích có ý nghĩa và âm thanh giống như một cách tách rời tốt đẹp để giải quyết vấn đề của tôi. Nhưng tôi đang có một thời gian khó khăn để có được mối quan hệ này.
Brettetete

3
Đây là trực giao với giải pháp của Robert nhưng bạn nên ưu tiên tính bất biến (thậm chí nhiều hơn bình thường) khi bạn sử dụng nhiều bố cục để tránh tạo ra các mạng lưới tác dụng phụ phức tạp. Lý tưởng nhất là việc triển khai xây dựng / trang trại / tấn công của bạn lấy dữ liệu và nhổ dữ liệu ra mà không chạm vào bất cứ thứ gì khác.
Doval

1
Công nhân vẫn là một người xây dựng, bởi vì bạn được thừa hưởng từ ICanBuild. Rằng bạn cũng có các công cụ để xây dựng là mối quan tâm thứ yếu trong hệ thống phân cấp đối tượng.
Robert Harvey

Có ý nghĩa. Tôi sẽ thử cái này. Cảm ơn câu trả lời thân thiện và mô tả của bạn. Tôi thực sự đánh giá cao điều đó. Tôi sẽ để câu hỏi này mở một thời gian nữa :)
Brettetete

4
@Brettetete Có thể giúp nghĩ về các triển khai Xây dựng, Trang trại, Tấn công, Săn bắn, v.v. như các kỹ năng mà nhân vật của bạn có. BuildSkill, FarmSkill, AttackSkill, v.v ... Sau đó, bố cục có ý nghĩa hơn nhiều.
Eric King

4

Như các áp phích khác đã đề cập đến một kiến ​​trúc thành phần có thể là giải pháp cho vấn đề này.

class component // Some generic base component structure
{
  void update() {} // With an empty update function
} 

Trong trường hợp này mở rộng chức năng cập nhật để thực hiện bất kỳ chức năng nào mà NPC nên có.

class build : component
{
  override void update()
  { 
    // Check if the right object is selected, resources, etc
  }
}

Lặp lại một bộ chứa thành phần và cập nhật từng thành phần cho phép chức năng cụ thể của từng thành phần được áp dụng.

class npc
{
  void update()
  {
    foreach(component c in components)
      c.update();
  }

  list<component> components;
}

Vì mỗi loại đối tượng được mong muốn, bạn có thể sử dụng một số dạng của mẫu nhà máy để xây dựng các loại riêng lẻ mà bạn muốn.

npc worker;
worker.add_component<build>();
worker.add_component<walk>();
worker.add_component<attack>();

Tôi luôn gặp vấn đề khi các thành phần ngày càng phức tạp hơn. Giữ độ phức tạp thành phần thấp (một trách nhiệm) có thể giúp giảm bớt vấn đề đó.


3

Nếu bạn xác định các giao diện như thế ICanBuildthì hệ thống trò chơi của bạn phải kiểm tra mọi NPC theo loại, thường được sử dụng trong OOP. Ví dụ : if (npc is ICanBuild).

Bạn tốt hơn với:

interface INPC
{
    bool CanBuild { get; }
    void Build(...);
    bool CanFarm { get; }
    void Farm(...);
    ... etc.
}

Sau đó:

class Worker : INPC
{
    private readonly IBuildStrategy buildStrategy;
    public Worker(IBuildStrategy buildStrategy)
    {
        this.buildStrategy = buildStrategy;
    }

    public bool CanBuild { get { return true; } }
    public void Build(...)
    {
        this.buildStrategy.Build(...);
    }
}

... hoặc tất nhiên bạn có thể sử dụng một số kiến ​​trúc khác nhau, như một số loại ngôn ngữ cụ thể miền dưới dạng giao diện lưu loát để xác định các loại NPC khác nhau, v.v., trong đó bạn xây dựng các loại NPC bằng cách kết hợp các hành vi khác nhau:

var workerType = NPC.Builder
    .Builds(new WorkerBuildBehavior())
    .Farms(new WorkerFarmBehavior())
    .Build();

var workerFactory = new NPCFactory(workerType);

var worker = workerFactory.Build();

Trình xây dựng NPC có thể thực hiện một lệnh DefaultWalkBehaviorcó thể bị ghi đè trong trường hợp NPC bay nhưng không thể đi, v.v.


Vâng, chỉ cần suy nghĩ lại một lần nữa: Thực sự không có nhiều khác biệt giữa if(npc is ICanBuild)if(npc.CanBuild). Ngay cả khi tôi muốn npc.CanBuildcách từ thái độ hiện tại của tôi.
Brettetete

3
@Brettetete - Bạn có thể thấy trong câu hỏi này tại sao một số lập trình viên thực sự không thích kiểm tra loại.
Scott Whitlock

0

Tôi sẽ đặt tất cả các khả năng trong các lớp riêng biệt kế thừa từ Khả năng. Sau đó, trong lớp loại đơn vị có một danh sách các khả năng và các thứ khác như tấn công, phòng thủ, sức khỏe tối đa, v.v. Cũng có một lớp đơn vị có sức khỏe hiện tại, vị trí, v.v. và có loại đơn vị là biến thành viên.

Xác định tất cả các loại đơn vị trong tệp cấu hình hoặc cơ sở dữ liệu, thay vì mã hóa cứng.

Sau đó, người thiết kế cấp độ có thể dễ dàng điều chỉnh các khả năng và chỉ số của các loại đơn vị mà không cần biên dịch lại trò chơi, và mọi người cũng có thể viết mod cho trò chơi.

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.