Создание надежной системы предметов


12

Моя цель - создать модульную / как можно более общую систему элементов, которая могла бы обрабатывать такие вещи, как:

  • Обновляемые предметы (+6 Катана)
  • Модификаторы статов (+15 к ловкости)
  • Модификаторы предметов (% X шанс нанести Y урон, шанс заморозить)
  • Перезаряжаемые предметы (Магический посох с 30 использованиями)
  • Набор предметов (4 предмета Х установлены для активации функции Y)
  • Редкость (обычная, уникальная, легендарная)
  • Disenchantable (разбивается на некоторые материалы для крафта)
  • Можно обрабатывать (можно с определенными материалами)
  • Расходуется (5 мин.% Силы атаки X, лечение +15 л. С.)

* Я смог решить функции, которые выделены жирным шрифтом в следующей настройке.

Теперь я попытался добавить много вариантов, чтобы отразить то, что я имею в виду. Я не планирую добавлять все эти функции по необходимости, но я хотел бы иметь возможность реализовать их по своему усмотрению. Они также должны быть совместимы с системой инвентаризации и сериализации данных.

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

  • BaseStat: универсальный класс, который хранит статистику на ходу (также может использоваться для статистики предметов и персонажей)
  • Item: класс, который содержит данные, такие как список, имя, тип элемента и вещи, связанные с пользовательским интерфейсом, actionName, описанием и т. Д.
  • IWeapon: интерфейс для оружия. Каждое оружие будет иметь свой собственный класс с реализованным IWeapon. Это будет иметь Атаку и ссылку на статистику персонажей. Когда оружие экипировано, его данные (характеристика класса предмета) будут введены в стат персонажа (независимо от того, какое у него базовое состояние, оно будет добавлено к классу персонажа в качестве бонуса к стату). Например, мы хотим изготовить меч (думая о производить классы предметов с помощью json), поэтому меч добавит 5 атак к характеристикам персонажа. Таким образом, у нас есть BaseStat as («Attack», 5) (мы также можем использовать enum). Эта характеристика будет добавлена ​​к характеристике «Атака» персонажа в виде BonusStat (это будет другой класс) после оснащения. Таким образом, класс с именем Sword реализует IWeapon будет создан, когда онКласс предметов создан. Таким образом, мы можем ввести характеристики персонажа в этот меч, а при атаке он может получить общую статистику атаки из характеристики персонажа и нанести урон в методе атаки.
  • BonusStat: способ добавления статистики в качестве бонусов, не касаясь BaseStat.
  • IConsumable: та же логика, что и в IWeapon. Добавить прямую статистику довольно легко (+15 л. С.), Но я не уверен насчет добавления временного оружия с этой настройкой (% x к атаке в течение 5 минут).
  • IUpgradeable: это может быть реализовано с этой настройкой. Я считаю UpgradeLevel базовым статом , который увеличивается при улучшении оружия. При обновлении мы можем пересчитать BaseStat оружия в соответствии с его уровнем улучшения.

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

Чтобы вам было легче внести свой вклад, вот несколько вопросов, с которыми вы можете помочь:

  • Должен ли я продолжить эту настройку для реализации других функций? Будет ли это возможно без наследства?
  • Можно ли придумать, как реализовать все эти функции без наследования?
  • О модификаторах предметов, как можно этого достичь? Потому что это очень общий характер.
  • Что можно сделать, чтобы облегчить процесс построения архитектуры такого типа, какие-либо рекомендации?
  • Есть ли источники, которые я могу найти, связанные с этой проблемой?
  • Я действительно стараюсь избегать наследования, но думаете ли вы, что это будет легко решено / достигнуто с помощью наследования при сохранении его достаточной ремонтопригодности?

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


РЕДАКТИРОВАТЬ


Следуя ответу @ 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);
    }
} 

Это необходимые настройки. Теперь мы можем использовать систему (ниже для Unity).

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, а каждый Атрибут (имя не должно вас смущать, это означает, что функция / система элемента. Подобно Upgradeable или disenchantable) будет реализовывать IAttribute. Затем у нас есть метод для обработки сообщения (почему нам нужно сообщение, будет объяснено в следующем примере). Таким образом, в контексте вы можете прикрепить атрибуты к элементу, а остальное сделает за вас. Что очень гибко, потому что вы можете легко добавлять / удалять атрибуты. Таким образом, псевдо-пример был бы Disenchantable. У нас будет класс с именем Disenchantable (IAttribute) и он в методе Disenchant будет запрашивать:

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

и т.п.

Эта информация будет предоставлена ​​классом DisenchantManager, он получит предмет и сформирует это сообщение в соответствии с предметом (ингредиенты предмета при распускании) и прогрессом игрока (resultModifier и failChance). Чтобы передать это сообщение, мы создадим класс DisenchantMessage, который будет действовать как тело для этого сообщения. Таким образом, DisenchantManager заполнит сообщение DisenchantMessage и отправит его элементу. Предмет получит сообщение и передаст его всем прикрепленным атрибутам. Поскольку метод ReceiveMessage класса Disenchantable будет искать DisenchantMessage, только атрибут Disenchantable получит это сообщение и будет воздействовать на него. Надеюсь, это прояснит вещи так же, как и для меня :).


1
Вы можете найти некоторое полезное вдохновение в системах модификаторов, используемых в «Чести»
DMGregory

@DMGregory Привет! Спасибо за ссылку. Хотя это кажется очень изобретательным, к сожалению, мне нужен фактический разговор, чтобы понять концепцию. И я пытаюсь выяснить реальный разговор, который, к сожалению, только для членов GDCVault (495 $ в год - это безумие!). (Вы можете найти доклад здесь, если у вас есть членство в GDCVault -, -
Vandarthul

Как именно ваша концепция «BaseStat» исключает возможность изготовления оружия?
Attackfarm

Это не исключает, но не вписывается в мой контекст. Можно добавить «Древесину», 2 и «Железо», 5 в качестве BaseStat к рецепту крафта, который даст меч. Я думаю, что изменение имени BaseStat на BaseAttribute будет лучше в этом контексте. Но все же система не сможет служить своей цели. Подумайте о расходуемом предмете с силой атаки 5 минут - 50%. Как бы это передать как BaseStat? «Perc_AttackPower», 50 это нужно разрешить как «если это Perc, обрабатывать целое число как процент» и пропуская информацию минут. О, надеюсь, вы понимаете, о чем я.
Вандартул

@ Attckfarm, если подумать, эту концепцию «BaseStat» можно расширить, добавив список целых вместо одного целого. поэтому для расходуемого баффа я могу указать «Attack», 50, 5, 1, а IConsumable будет искать 3 целых числа, 1. - значение, 2. - минуты, 3. - если это процент или нет. Но он чувствует себя двигателем, поскольку другие системы входят и вынуждены объясняться только в int.
Вандартул

Ответы:


6

Я думаю, что вы можете достичь того, что вы хотите с точки зрения масштабируемости и удобства обслуживания, используя Entity-Component System с базовым наследованием и систему обмена сообщениями. Конечно, имейте в виду, что эта система является самой модульной / настраиваемой / масштабируемой, которую я могу себе представить, но, вероятно, она будет работать хуже, чем ваше текущее решение.

Я объясню дальше:

Прежде всего, вы создаете интерфейс IItemи интерфейс IComponent. Любой элемент, который вы хотите сохранить, должен наследоваться IItem, а любой компонент, на который вы хотите повлиять, должен наследовать IComponent.

IItemбудет иметь массив компонентов и метод для обработки IMessage. Этот метод обработки просто отправляет любое полученное сообщение всем сохраненным компонентам. Затем компоненты, которые заинтересованы в данном сообщении, будут действовать соответствующим образом, а остальные будут его игнорировать.

Например, одно сообщение имеет тип повреждения, и оно информирует как атакующего, так и атакованного, так что вы знаете, сколько вы ударили, и, возможно, начисляете свою ярость на основе этого урона. Или ИИ противника может принять решение бежать, если он ударяет вас и наносит менее 2HP урона. Это глупые примеры, но используя систему, подобную той, о которой я упоминаю, вам не нужно ничего делать, кроме создания сообщения и соответствующих обработок, чтобы добавить большую часть этого вида механики.

У меня есть реализация для ECS с обменом сообщениями здесь , но это используется для сущностей вместо элементов, и это использует C ++. Во всяком случае, я думаю, это может помочь, если вы посмотрите component.h, entity.hи messages.h. Есть много вещей, которые нужно улучшить, но это помогло мне в этой простой работе в университете.

Надеюсь, поможет.


Привет @ jjimenezg93, спасибо за ваш ответ. Итак, чтобы проиллюстрировать простым примером того, что вы объяснили: нам нужен меч, который: - Disenchantable [Компонент] - Модификатор Статуса [Компонент] - Обновляемый [Компонент] У меня есть список действий, который содержит все такие вещи, как - DISENCHANT - MODIFY_STAT - UPGRADE Всякий раз, когда элемент получает это сообщение, проходит через все его компоненты и отправляет это сообщение, каждый компонент будет знать, что делать с данным сообщением. В теории это кажется потрясающим! Я не проверял ваш пример, но я буду, большое спасибо!
Вандартул

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