Створення надійної системи предметів


12

Моя мета - створити модульну / максимально універсальну систему предметів, яка могла б обробляти такі речі, як:

  • Елементи, що можна оновити (+6 Катани)
  • Модифікатори стату (+15 спритність)
  • Модифікатори предмета (% X шанс завдати шкоди Y, шанс заморозити)
  • Акумуляторні товари (Чарівний персонал з 30 звичаями)
  • Встановити елементи (обладнайте 4 частини X набору для активації функції Y)
  • Рідкість (загальна, унікальна, легендарна)
  • Disenchavable (врізається в деякі майстерні матеріали)
  • Craftable (можна виготовити з певними матеріалами)
  • Витратний (5 хв.% Атакова потужність, оздоровлення +15 к.с.)

* Мені вдалося вирішити функції, які сміливі в наступних налаштуваннях.

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

Я планую взагалі не використовувати спадщину, а навпаки, підхід, орієнтований на суб’єкти / дані. Спочатку я думав про систему, яка має:

  • BaseStat: загальний клас, який містить статистику в ході (може бути використаний і для елементів, і для символів)
  • Item: клас, який містить дані, такі як список, ім'я, тип предмета та речі, пов’язані з ui, actionName, описом тощо.
  • IWeapon: інтерфейс для зброї. Кожна зброя матиме свій клас із впровадженим IWeapon. Це матиме Attack та посилання на статистику персонажів. Коли зброя оснащена, її дані (item item 'stat) будуть введені в символьні stat (що б у BaseStat не було, воно буде додано до класу символів як бонус Stat). Наприклад, ми хочемо створити меч (думаючи створити класи предметів з json), тому меч додасть 5 атак до статистики символів. Отже, у нас є BaseStat as ("Attack", 5) (ми можемо використовувати і перерахунок). Ця статистика буде додана до статусу "Атака" персонажа як BonusStat (що був би іншим класом) після його оснащення. Отже, клас з ім'ям Sword реалізує IWeapon , коли він буде створений 'Клас предметів створено. Таким чином, ми можемо вводити статистику символів у цей меч, і при атаці він може витягувати загальну статистику атаки зі статусу символів та завдавати шкоди методом Attack.
  • BonusStat: це спосіб додавання статистики як бонусів, не торкаючись BaseStat.
  • IConsumable: та ж логіка, що і у IWeapon. Додавати пряму статистику досить просто (+15 к.с.), але я не впевнений у додаванні тимчасової зброї з цією установкою (% x для атаки протягом 5 хв).
  • IUpgradeable: це може бути реалізовано за допомогою цієї установки. Я думаю, що UpgradeLevel є базовим статом , який збільшується при оновленні зброї. Після оновлення ми можемо перерахувати BaseStat зброї відповідно до рівня оновлення.

До цього моменту я бачу, що система є досить хорошою. Щодо інших функцій, я думаю, що нам потрібно щось інше, тому що, наприклад, я не можу реалізувати функцію Craftable в цьому, оскільки мій BaseStat не міг би впоратися з цією функцією, і саме тут я застряг. Я можу додати всі інгредієнти як Стати, але це не мало б сенсу.

Щоб полегшити вам внесок у це, ось кілька питань, з якими ви можете допомогти:

  • Чи варто продовжувати цю налаштування для впровадження інших функцій? Чи можливо це було б без спадщини?
  • Чи є якийсь спосіб, який ви можете придумати, реалізувати всі ці функції без успадкування?
  • Щодо модифікаторів предметів, як можна цього досягти? Тому що це дуже родова природа.
  • Що можна зробити, щоб полегшити процес побудови такої архітектури, будь-які рекомендації?
  • Чи є джерела, які я можу викопати, пов'язані з цією проблемою?
  • Я дійсно намагаюся уникати спадкування, але ви вважаєте, що це було б вирішено / досягнуто спадщиною з легкістю, зберігаючи це досить ретельно?

Не соромтесь відповісти лише на одне запитання, оскільки я тримав питання дуже широкі, щоб я міг отримати знання з різних аспектів / людей.


EDIT


Після відповіді @ jjimenezg93 я створив в C # дуже базову систему для тестування, вона працює! Перевірте, чи можете ви додати що-небудь до нього:

public interface IItem
{
    List<IAttribute> Components { get; set; }

    void ReceiveMessage<T>(T message);
}

public interface IAttribute
{
    IItem source { get; set; }
    void ReceiveMessage<T>(T message);
}

Поки IItem та IAttribute є базовими інтерфейсами. Не було потреби (що я можу придумати) мати базовий інтерфейс / атрибут для повідомлення, тому ми безпосередньо створимо клас тестового повідомлення. Тепер для тестових занять:


public class TestItem : IItem
{
    private List<IAttribute> _components = new List<IAttribute>();
    public List<IAttribute> Components
    {
        get
        {
            return _components;
        }

        set
        {
            _components = value;
        }
    }

    public void ReceiveMessage<T>(T message)
    {
        foreach (IAttribute attribute in Components)
        {
            attribute.ReceiveMessage(message);
        }
    }
}

public class TestAttribute : IAttribute
{
    string _infoRequiredFromMessage;

    public TestAttribute(IItem source)
    {
        _source = source;
    }

    private IItem _source;
    public IItem source
    {
        get
        {
            return _source;
        }

        set
        {
            _source = value;
        }
    }

    public void ReceiveMessage<T>(T message)
    {
        TestMessage convertedMessage = message as TestMessage;
        if (convertedMessage != null)
        {
            convertedMessage.Execute();
            _infoRequiredFromMessage = convertedMessage._particularInformationThatNeedsToBePassed;
            Debug.Log("Message passed : " + _infoRequiredFromMessage);

        }
    }
} 

public class TestMessage
{
    private string _messageString;
    private int _messageInt;
    public string _particularInformationThatNeedsToBePassed;
    public TestMessage(string messageString, int messageInt, string particularInformationThatNeedsToBePassed)
    {
        _messageString = messageString;
        _messageInt = messageInt;
        _particularInformationThatNeedsToBePassed = particularInformationThatNeedsToBePassed;
    }
    //messages should not have methods, so this is here for fun and testing.
    public void Execute()
    {
        Debug.Log("Desired Execution Method: \nThis is test message : " + _messageString + "\nThis is test int : " + _messageInt);
    }
} 

Це необхідні установки. Тепер ми можемо скористатися системою (Далі - для Єдності).

public class TestManager : MonoBehaviour
{

    // Use this for initialization
    void Start()
    {
        TestItem testItem = new TestItem();
        TestAttribute testAttribute = new TestAttribute(testItem);
        testItem.Components.Add(testAttribute);
        TestMessage testMessage = new TestMessage("my test message", 1, "VERYIMPORTANTINFO");
        testItem.ReceiveMessage(testMessage);
    }

}

Приєднайте цей скрипт TestManager до компонента в сцені, і ви зможете побачити в налагодженні, що повідомлення успішно передається.


Для того, щоб пояснити все: Кожен елемент гри буде реалізовувати інтерфейс IItem та кожен атрибут (ім'я не повинно вас бентежити, це означає, що функція / система елемента. Як і оновлений, або неможливий) буде реалізовувати IAttribute. Тоді у нас є метод обробки повідомлення (для чого нам потрібне повідомлення, буде пояснено в подальшому прикладі). Тож у контексті ви можете приєднати атрибути до елемента та дозволити іншим зробити за вас. Що дуже гнучко, тому що ви можете легко додавати / видаляти атрибути. Таким чином, псевдо-приклад був би незмінним. У нас буде клас під назвою Disenchantable (IAttribute), і він у методі Disenchant запитає:

  • Перелічі інгредієнтів (коли елемент не заперечується, який предмет слід надати гравцеві) Примітка: IItem слід розширити, щоб мати ItemType, ItemID і т.д.
  • int resultModifier (якщо ви впроваджуєте якусь функцію посилення розчарування, ви можете передати тут int, щоб збільшити інгредієнти, отримані при знеціненні)
  • int failChance (якщо процес зневаги має шанс відмови)

тощо.

Ці відомості будуть надані класом під назвою DisenchantManager, він отримає елемент і сформує це повідомлення відповідно до пункту (інгредієнти елемента при відключенні) та прогресії гравця (resultModifier and failChance). Для того, щоб передати це повідомлення, ми створимо клас DisenchantMessage, який буде виконувати функції цього повідомлення. Тож DisenchantManager заповнить DisenchantMessage і надішле його на предмет. Елемент отримає повідомлення та передасть його всім доданим атрибутам. Оскільки метод ReceiveMessage класу Disenchantable буде шукати DisenchantMessage, тільки атрибут Disenchantable отримає це повідомлення і діятиме на нього. Сподіваюся, це очищає речі настільки ж, як і для мене :).



@DMGregory Привіт! Дякуємо за посилання Хоча це здається дуже винахідливим, на жаль, мені потрібна реальна розмова, щоб зрозуміти концепцію. І я намагаюся з’ясувати фактичну розмову, яка, на жаль, є лише вмістом GDCVault (495 доларів за рік - божевільне!). (Ви можете знайти тут розмову, якщо у вас є членство в GDCVault -, -
Vandarthul

Як саме, якщо ваша концепція "BaseStat" не дозволить виправити зброю?
Attackfarm

Це насправді не виключає, але не дуже вписується в контекст моєї думки. Можна додати "Wood", 2 та "Iron", 5 як BaseStat до рецепту майстерності, який давав би меч. Я думаю, що зміна назви BaseStat на BaseAttribute буде служити краще в цьому контексті. Але все ж система не зможе служити своєму призначенню. Подумайте про витратний предмет, що має 5 хв -% 50 сили атаки. Як я міг би передати це як BaseStat? "Perc_AttackPower", 50 це потрібно вирішити як "якщо це Perc, обробити ціле число у відсотках" і не вистачає інформації хвилин. О сподіваюся, ви отримаєте те, що я маю на увазі.
Вандартул

@Attackfarm, по-друге, цю концепцію "BaseStat" можна розширити, якщо список списків Ints замість лише одного int. Отже, для споживчого бафу я можу поставити "Attack", 50, 5, 1 і IConsumable шукає 3 цілих числа, 1. - значення, 2. - хвилини, 3. - якщо це відсоток чи ні. Але він відчуває себе сильним, коли інші системи потрапляють і змушені пояснювати себе лише в int.
Вандартул

Відповіді:


6

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

Я поясню далі:

Перш за все, ви створюєте інтерфейс IItemта інтерфейс IComponent. Будь-який предмет, який ви хочете зберігати, повинен успадкувати IItem, і будь-який компонент, на який ви хочете вплинути на ваші товари, повинен успадкувати IComponent.

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

Наприклад, одне повідомлення - це тип пошкодження, і воно інформує як зловмисника, так і атакованого, тож ви знаєте, скільки сильно вдарили, і, можливо, стягуватимете свою фурі-бар, виходячи з цього збитку. Або ІІ противника може вирішити бігти, якщо він завдає удару і завдає менше 2 к.с. Це нерозумні приклади, але використовуючи систему, подібну до цієї, яку я згадую, вам не потрібно буде робити нічого, крім створення повідомлення та відповідних обробків, щоб додати більшість подібних механізмів.

У мене є реалізація для ECS з повідомленнями тут , але це використовується для об'єктів замість елементів, і він використовує C ++. У будь-якому випадку, я думаю, що це може допомогти, якщо ви поглянете на component.h, entity.hі messages.h. Є багато чого, що потрібно вдосконалити, але це працювало для мене в тій простій університетській роботі.

Сподіваюся, це допомагає.


Привіт @ jjimenezg93, дякую за відповідь. Отже, для того, щоб пояснити простий приклад того, що ви пояснили: Ми хочемо, щоб меч був: - Disenchantable [Компонент] - Stat Modifier [Компонент] - Модернізується [Компонент] У мене є список дій, який містить усі речі, наприклад - DISENCHANT - MODIFY_STAT - UPGRADE Кожен раз, коли елемент отримує це повідомлення, проходить всі його компоненти та надсилає це повідомлення, кожен компонент буде знати, що робити з даним повідомленням. Теоретично це здається дивним! Я не перевірив твій приклад, але буду, велике спасибі!
Вандартул

@Vandarthul Так, це в основному ідея. Таким чином, предмет не буде знати нічого про його компоненти, тому взагалі немає з’єднання, і, в той же час, він буде мати всю необхідну вам функціональність, яку також можна поділити між різними типами елементів. Я сподіваюся, що він відповідає вашим потребам!
jjimenezg93
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.