Альтернативи багатократному успадкуванню для моєї архітектури (NPC в грі стратегії в реальному часі)?


11

Кодування не так складно на самому справі . Важкою частиною є написання коду, який має сенс, читабельний і зрозумілий. Тому я хочу стати кращим розробником та створити якусь солідну архітектуру.

Тому я хочу створити архітектуру для NPC у відеоіграх. Це стратегія в реальному часі, як Starcraft, Age of Empires, Command & Conquers, і т. Д. І тому я буду мати різні види NPC. NPC може мати один до багатьох здібностям (методи) цим: Build(), Farm()і Attack().

Приклади:
Робітник може Build()і Farm()
Воїн може Attack()
Громадянин може Build(), Farm()а Attack()
Рибалка може Farm()іAttack()

Я сподіваюсь, що досі все зрозуміло.

Тому зараз у мене є свої типи NPC та їхні здібності. Але давайте перейдемо до технічного / програмного аспекту.
Яка б була хороша програмна архітектура для моїх різних видів NPC?

Гаразд, я міг би мати базовий клас. Насправді я думаю, що це хороший спосіб дотримуватися принципу DRY . Тож у мене можуть бути такі методи, як WalkTo(x,y)у моєму базовому класі, оскільки кожен NPC зможе перейти. Але тепер давайте прийдемо до реальної проблеми. Де я реалізую свої здібності? (Пам'ятаєте: Build(), Farm()а Attack())

Оскільки здібності будуть складатися з тієї самої логіки, було б дратувати / порушувати принцип DRY, щоб реалізувати їх для кожного NPC (Worker, Warrior, ..).

Гаразд, я міг би реалізувати свої здібності в базовому класі. Це зажадає який - то логіки , яка перевіряє , якщо NPC може використовувати здатність X. IsBuilder, CanBuild.. Я думаю, ясно , що я хочу висловити.
Але я не дуже добре почуваюся з цією ідеєю. Це звучить як роздутий базовий клас із занадто великою функціональністю.

Я використовую C # як мову програмування. Тож багаторазове успадкування тут не є варіантом. Засоби: мати додаткові базові класи, як, наприклад Fisherman : Farmer, Attacker, не вийде.


3
Я дивився на цей приклад, який здається вирішенням цього питання: en.wikipedia.org/wiki/Composition_over_inheritance
Shawn Mclean

4
Це питання набагато краще підійде на сайті gamedev.stackexchange.com
Філіпп

Відповіді:


14

Композиція та спадкове інтерфейс - це звичайні альтернативи класичному множинному успадкуванню.

Все, що ви описали вище, що починається зі слова "може" - це можливість, яку можна представити інтерфейсом, як у ICanBuild, або ICanFarm. Ви можете успадкувати стільки інтерфейсів, скільки вважаєте потрібним.

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

    #region ICanFarm Implementation
    // Farm
    #endregion
}

Ви можете перейти в "загальні" будівельні та фермерські об'єкти через конструктор вашого класу. Ось тут і входить композиція.


Просто думати вголос: The «відносини» буде (внутрішньо) бути має замість IS (працівник має будівельник замість працівник Builder). Приклад / шаблон, який ви пояснили, має сенс і звучить як приємний розв'язаний спосіб вирішення моєї проблеми. Але мені важко отримати ці відносини.
Brettetete

3
Це є ортогональним для рішення Роберта, але ви повинні надавати перевагу незмінності (навіть більше, ніж зазвичай), коли ви використовуєте складний склад, щоб уникнути створення складних мереж побічних ефектів. В ідеалі ваші реалізації build / farm / атаки беруть дані та виплювають дані, не торкаючись нічого іншого.
Doval

1
Робітник все ще є будівельником, тому що ви успадкували від ICanBuild. Те, що у вас також є інструменти для побудови, є вторинним питанням в ієрархії об'єктів.
Роберт Харві

Має сенс. Я спробую цей принцпіле спробувати. Дякуємо за вашу дружню та описову відповідь. Я дуже ціную це. Я дозволю це питання відкрити на деякий час, хоча :)
Brettetete

4
@Brettetete Це може допомогти мислити реалізацію Build, Farm, Attack, Hunt тощо як навички , якими володіють ваші персонажі. BuildSkill, FarmSkill, AttackSkill тощо. Тоді композиція має набагато більше сенсу.
Ерік Кінг

4

Як згадували інші афіші, архітектура компонентів може бути вирішенням цієї проблеми.

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

У цьому випадку розгорніть функцію оновлення, щоб реалізувати функціонал, який має мати NPC.

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

Ітерація контейнера компонентів та оновлення кожного компонента дозволяє застосовувати конкретну функціональність кожного компонента.

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

  list<component> components;
}

Оскільки кожен тип об'єкта шукається, ви можете використовувати певну форму заводського шаблону для побудови окремих типів, які ви хотіли.

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

Я завжди стикався з проблемами, оскільки компоненти стають все складнішими. Зниження рівня складності компонентів (одна відповідальність) може допомогти полегшити цю проблему.


3

Якщо ви визначаєте такі інтерфейси, як ICanBuildваша ігрова система повинна перевіряти кожен NPC за типом, який, як правило, спохмурнів в OOP. Наприклад: if (npc is ICanBuild).

Вам краще:

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

Потім:

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

... або, звичайно, ви можете використовувати цілу низку різних архітектур, наприклад, якусь доменну мову у вигляді вільного інтерфейсу для визначення різних типів NPC тощо, де ви будуєте типи NPC, комбінуючи різні способи поведінки:

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

var workerFactory = new NPCFactory(workerType);

var worker = workerFactory.Build();

Будівельник NPC може реалізувати таке, DefaultWalkBehaviorщо може бути відмінено у випадку NPC, який летить, але не може ходити тощо.


Ну, лише знову голосно подумавши: Різниці між if(npc is ICanBuild)та і насправді не так вже й багато if(npc.CanBuild). Навіть тоді, коли я віддав перевагу npc.CanBuildшляху від свого теперішнього ставлення.
Brettetete

3
@Brettetete - у цьому питанні ви бачите, чому деякі програмісти дійсно не люблять перевіряти тип.
Скотт Вітлок

0

Я б розмістив усі здібності в окремі класи, які успадковуються від Ability. Тоді в класі типу одиниці є список здібностей та інших речей, таких як атака, захист, максимальне здоров'я тощо. Також є клас одиниць, який має поточне здоров'я, місце розташування тощо та має тип одиниці як змінну члена.

Визначте всі типи одиниць у конфігураційному файлі чи базі даних, а не жорсткому кодуванні.

Тоді дизайнер рівня може легко налаштувати здібності та статистику типів одиниць без перекомпіляції гри, а також люди можуть писати моди для гри.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.