Alternatif untuk pewarisan berganda untuk arsitektur saya (NPC dalam game Strategi Realtime)?

11

Coding tidak sulit sebenarnya . Bagian yang sulit adalah menulis kode yang masuk akal, dapat dibaca dan dimengerti. Jadi saya ingin mendapatkan pengembang yang lebih baik dan membuat arsitektur yang solid.

Jadi saya ingin membuat arsitektur untuk NPC dalam video-game. Ini adalah game Strategi Realtime seperti Starcraft, Age of Empires, Command & Conquers, dll. Jadi saya akan memiliki berbagai jenis NPC. Sebuah NPC dapat memiliki satu ke banyak kemampuan (metode) ini: Build(), Farm()dan Attack().

Contoh:
Pekerja bisa Build()dan Farm()
prajurit bisa Attack()
Citizen bisa Build(), Farm()dan Attack()
Nelayan bisa Farm()danAttack()

Saya harap semuanya jelas sejauh ini.

Jadi sekarang saya memiliki Jenis NPC saya dan kemampuan mereka. Tetapi mari kita sampai pada aspek teknis / programatik.
Apa yang akan menjadi arsitektur programatik yang baik untuk berbagai jenis NPC saya?

Oke saya bisa punya kelas dasar. Sebenarnya saya pikir ini adalah cara yang baik untuk tetap berpegang pada prinsip KERING . Jadi saya dapat memiliki metode seperti WalkTo(x,y)di kelas dasar saya karena setiap NPC akan dapat bergerak. Tapi sekarang mari kita datang ke masalah sebenarnya. Di mana saya menerapkan kemampuan saya? (ingat: Build(), Farm()dan Attack())

Karena kemampuan akan terdiri dari logika yang sama itu akan mengganggu / mematahkan prinsip KERING untuk mengimplementasikannya untuk setiap NPC (Worker, Warrior, ..).

Oke saya bisa menerapkan kemampuan dalam kelas dasar. Ini akan membutuhkan semacam logika yang memverifikasi jika NPC dapat menggunakan kemampuan X IsBuilder. CanBuild,, .. Saya pikir jelas apa yang ingin saya ungkapkan.
Tapi saya merasa tidak enak dengan ide ini. Ini terdengar seperti kelas dasar yang membengkak dengan terlalu banyak fungsi.

Saya menggunakan C # sebagai bahasa pemrograman. Jadi pewarisan berganda bukan pilihan di sini. Berarti: Memiliki kelas dasar tambahan seperti Fisherman : Farmer, Attackertidak akan berhasil.

Brettetete
sumber
3
Saya melihat contoh ini yang sepertinya solusi untuk ini: en.wikipedia.org/wiki/Composition_over_inheritance
Shawn Mclean
4
Pertanyaan ini akan cocok jauh lebih baik di gamedev.stackexchange.com
Philipp

Jawaban:

14

Warisan dan Komposisi Antarmuka adalah alternatif biasa untuk pewarisan berganda klasik.

Segala sesuatu yang Anda jelaskan di atas yang dimulai dengan kata "bisa" adalah kemampuan yang dapat direpresentasikan dengan antarmuka, seperti dalam ICanBuild, atau ICanFarm. Anda dapat mewarisi sebanyak mungkin antarmuka yang Anda butuhkan.

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

    #region ICanFarm Implementation
    // Farm
    #endregion
}

Anda dapat melewati objek bangunan dan pertanian "generik" melalui konstruktor kelas Anda. Di situlah komposisi masuk.

Robert Harvey
sumber
Hanya berpikir keras: The 'hubungan' akan (internal) menjadi memiliki bukannya yaitu (Pekerja memiliki Builder bukan Pekerja adalah Builder). Contoh / pola yang Anda jelaskan masuk akal dan terdengar seperti cara terpisah yang bagus untuk menyelesaikan masalah saya. Tetapi saya mengalami kesulitan untuk mendapatkan hubungan ini.
Brettetete
3
Ini ortogonal untuk solusi Robert, tetapi Anda harus mendukung ketetapan (bahkan lebih dari biasanya) ketika Anda menggunakan komposisi yang banyak untuk menghindari menciptakan jaringan efek samping yang rumit. Idealnya implementasi build / farm / attack Anda mengambil data dan memuntahkan data tanpa menyentuh apa pun.
Doval
1
Pekerja masih merupakan pembangun, karena Anda mewarisi dari ICanBuild. Bahwa Anda juga memiliki alat untuk membangun adalah perhatian sekunder dalam hierarki objek.
Robert Harvey
Masuk akal. Saya akan mencoba prinsip ini. Terima kasih atas jawaban Anda yang ramah dan deskriptif. Saya sangat menghargainya. Saya akan membiarkan pertanyaan ini terbuka untuk beberapa waktu :)
Brettetete
4
@Brettetete Ini mungkin membantu untuk memikirkan implementasi Build, Farm, Attack, Hunt, dll. Sebagai keterampilan , yang dimiliki karakter Anda. BuildSkill, FarmSkill, AttackSkill, dll. Kemudian komposisi jauh lebih masuk akal.
Eric King
4

Seperti poster lain yang menyebutkan arsitektur komponen bisa menjadi solusi untuk masalah ini.

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

Dalam hal ini memperluas fungsi pembaruan untuk mengimplementasikan fungsi apa pun yang seharusnya dimiliki NPC.

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

Iterasi wadah komponen dan memperbarui setiap komponen memungkinkan fungsi spesifik dari masing-masing komponen untuk diterapkan.

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

  list<component> components;
}

Karena setiap jenis objek diinginkan, Anda dapat menggunakan beberapa bentuk pola pabrik untuk membangun tipe individu yang Anda inginkan.

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

Saya selalu mengalami masalah karena komponen semakin kompleks. Menjaga kompleksitas komponen tetap rendah (satu tanggung jawab) dapat membantu meringankan masalah itu.

Connor Hollis
sumber
3

Jika Anda mendefinisikan antarmuka seperti itu ICanBuildmaka sistem game Anda harus memeriksa setiap NPC berdasarkan jenis, yang biasanya disukai OOP. Sebagai contoh: if (npc is ICanBuild).

Anda lebih baik dengan:

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

Kemudian:

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

... atau tentu saja Anda dapat menggunakan sejumlah arsitektur yang berbeda, seperti beberapa jenis bahasa khusus domain dalam bentuk antarmuka yang lancar untuk mendefinisikan berbagai jenis NPC, dll., di mana Anda membangun jenis NPC dengan menggabungkan perilaku yang berbeda:

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

var workerFactory = new NPCFactory(workerType);

var worker = workerFactory.Build();

Pembangun NPC dapat mengimplementasikan DefaultWalkBehavioryang bisa diganti dalam kasus NPC yang terbang tetapi tidak bisa berjalan, dll.

Scott Whitlock
sumber
Yah, hanya berpikir keras lagi: Sebenarnya tidak ada banyak perbedaan antara if(npc is ICanBuild)dan if(npc.CanBuild). Bahkan ketika saya lebih suka npc.CanBuildjalan dari sikap saya saat ini.
Brettetete
3
@ Bretetete - Anda dapat melihat dalam pertanyaan ini mengapa beberapa programmer benar-benar tidak suka memeriksa jenisnya.
Scott Whitlock
0

Saya akan menempatkan semua kemampuan dalam kelas terpisah yang mewarisi dari Kemampuan. Kemudian di kelas tipe unit memiliki daftar kemampuan dan hal-hal lain seperti serangan, pertahanan, kesehatan maksimum dll. Juga memiliki kelas unit yang memiliki kesehatan saat ini, lokasi, dll. Dan memiliki tipe unit sebagai variabel anggota.

Tentukan semua tipe unit dalam file konfigurasi atau database, daripada pengkodean keras.

Kemudian desainer level dapat dengan mudah menyesuaikan kemampuan dan statistik dari tipe unit tanpa mengkompilasi ulang game, dan juga orang dapat menulis mod untuk game.

pengguna3970295
sumber