Mimarim için çoklu kalıtımın alternatifleri (Gerçek Zamanlı Strateji oyununda NPC'ler)?


11

Kodlama aslında o kadar da zor değil . Zor kısmı mantıklı, okunabilir ve anlaşılır bir kod yazmaktır. Bu yüzden daha iyi bir geliştirici edinmek ve sağlam bir mimari oluşturmak istiyorum.

Bu yüzden bir video oyununda NPC'ler için bir mimari oluşturmak istiyorum . Starcraft, Age of Empires, Command & Conquers, vb. Bir NPC birçok yetenekleri bunlardan (yöntemleri) için birine sahip olabilir: Build(), Farm()ve Attack().

Örnekler:
İşçi olabilir Build()ve Farm()
savaşçı can Attack()
Bristol olabilir Build(), Farm()ve Attack()
balıkçı olabilir Farm()veAttack()

Umarım şimdiye kadar her şey açıktır.

Şimdi NPC Tiplerim ve yeteneklerim var. Ama teknik / programsal yöne gelelim.
Farklı NPC'lerim için iyi bir programatik mimari ne olurdu?

Tamam bir temel sınıf alabilirdim. Aslında bunun KURU ilkesine bağlı kalmanın iyi bir yolu olduğunu düşünüyorum . Böylece, WalkTo(x,y)her NPC hareket edebileceğinden, temel sınıfımdaki gibi yöntemlere sahip olabilirim . Ama şimdi asıl soruna gelelim. Yeteneklerimi nerede uygularım? (: hatırlamak Build(), Farm()ve Attack())

Yetenekler aynı mantıktan oluşacağından, her NPC için (Çalışan, Savaşçı, ..) uygulamak DRY prensibini can sıkıcı / kırıcı olacaktır.

Tamam ben olabilir temel sınıf içinde yeteneklerini uygulamak. Bu NPC yetenek X'i kullanabilirsiniz eğer doğrular Bu mantıkla çeşit gerektirecektir IsBuilder, CanBuild, .. ben ben ifade etmek istediğini net olduğunu düşünüyorum.
Ama bu fikirle pek iyi hissetmiyorum. Bu, çok fazla işlevselliğe sahip şişirilmiş bir taban sınıfına benziyor .

Programlama dili olarak C # kullanıyorum. Yani çoklu kalıtım burada bir seçenek değil. Araçlar: Gibi ekstra temel sınıflara sahip olmak Fisherman : Farmer, Attackerişe yaramaz.


3
Bunun bir çözümü gibi görünen bu örneğe bakıyordum: en.wikipedia.org/wiki/Composition_over_inheritance
Shawn Mclean

Yanıtlar:


14

Kompozisyon ve Arayüz Kalıtım, klasik çoklu kalıtımın genel alternatifleridir.

Yukarıda açıkladığınız "can" kelimesi ile başlayan her şey ICanBuild, veya ile olduğu gibi bir arayüzle temsil edilebilen bir özelliktir ICanFarm. İhtiyacınız olduğunu düşündüğünüz kadar çok arabirimi miras alabilirsiniz.

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

    #region ICanFarm Implementation
    // Farm
    #endregion
}

"Genel" bina ve çiftçilik nesnelerini sınıfınızın kurucusundan geçirebilirsiniz. Kompozisyon burada devreye giriyor.


Sadece yüksek sesle düşünme: 'ilişki' (dahili) olacağını sahiptir yerine DİR (İşçi İşçi Builder yerine Builder vardır). Açıkladığınız örnek / model mantıklı ve sorunumu çözmek için hoş bir ayrışmış yol gibi geliyor. Ama bu ilişkiyi kurmakta zorlanıyorum.
Brettetete

3
Bu Robert'ın çözümüyle dikeydir, ancak karmaşık yan etkiler ağları oluşturmaktan kaçınmak için kompozisyonu yoğun bir şekilde kullandığınızda değişmezliği (normalden daha fazla) tercih etmelisiniz. İdeal olarak, yapı / çiftlik / saldırı uygulamalarınız başka hiçbir şeye dokunmadan veri alır ve tükürür.
Doval

1
İşçi hala bir inşaatçı, çünkü miras aldın ICanBuild. Ayrıca, bina için araçlara sahip olmanız, nesne hiyerarşisinde ikincil önem taşımaktadır.
Robert Harvey

Mantıklı. Bu prensibi bir deneyeceğim. Dostça ve açıklayıcı cevabınız için teşekkürler. Buna gerçekten memnun olurum. Yine de bu sorunun bir süre açık kalmasına izin vereceğim :)
Brettetete

4
@Brettetete Yapı, Çiftlik, Saldırı, Av vb uygulamalarını karakterlerin sahip olduğu beceriler olarak düşünmek faydalı olabilir . BuildSkill, FarmSkill, AttackSkill, vb. Sonra kompozisyon çok daha mantıklı.
Eric King

4

Diğer posterlerin de belirttiği gibi, bir bileşen mimarisi bu sorunun çözümü olabilir.

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

Bu durumda, NPC'nin sahip olması gereken işlevleri uygulamak için güncelleme işlevini genişletin.

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

Bir bileşen kabının yinelenmesi ve her bir bileşenin güncellenmesi, her bileşenin belirli işlevlerinin uygulanmasına olanak tanır.

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

  list<component> components;
}

Her nesne türü istendiğinden, istediğiniz tek tek türleri oluşturmak için fabrika modelinin bir biçimini kullanabilirsiniz.

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

Bileşenler gittikçe karmaşıklaştıkça her zaman sorunla karşılaştım. Bileşen karmaşıklığını düşük tutmak (bir sorumluluk) bu sorunun hafifletilmesine yardımcı olabilir.


3

Bunun gibi arayüzler tanımlarsanız ICanBuild, oyun sisteminiz her NPC'yi tipine göre incelemelidir; Örneğin: if (npc is ICanBuild).

Şunlarla daha iyi durumdasınız:

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

Sonra:

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(...);
    }
}

... ya da elbette, farklı davranışları birleştirerek NPC türleri oluşturduğunuz farklı NPC türlerini tanımlamak için akıcı bir arayüz şeklinde bir tür alana özgü dil gibi bir dizi farklı mimariyi kullanabilirsiniz:

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

var workerFactory = new NPCFactory(workerType);

var worker = workerFactory.Build();

NPC kurucusu DefaultWalkBehavior, uçan ancak yürüyemeyen bir NPC durumunda geçersiz kılınabilecek bir tane uygulayabilir .


Sadece tekrar yüksek sesle düşünmek: Aslında if(npc is ICanBuild)ve ile arasında çok fazla fark yoktur if(npc.CanBuild). npc.CanBuildŞu anki tutumumdan yolu tercih edeceğim zaman bile
Brettetete

3
@Brettetete - Bu soruda bazı programcıların türü denetlemeyi neden gerçekten sevmediğini görebilirsiniz.
Scott Whitlock

0

Tüm yetenekleri, Yeteneklerden miras kalan ayrı sınıflara koyardım. Daha sonra birim tipi sınıfında yeteneklerin listesi ve saldırı, savunma, maksimum sağlık vb. Gibi diğer şeyler var. Ayrıca mevcut sağlık, konum vb.

Sabit kodlama yerine bir yapılandırma dosyasındaki veya veritabanındaki tüm birim türlerini tanımlayın.

Daha sonra seviye tasarımcısı oyunu yeniden derlemeden birim türlerinin yeteneklerini ve istatistiklerini kolayca ayarlayabilir ve ayrıca insanlar oyun için modlar yazabilir.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.