Как изменить значение в куче клонов префаба?

Делаю игру на подобие Craft the World. Есть карта ресурсов. Каждый уникальный ресурс имеет префаб. При заполнении карты я просто создаю клоны префаба нужного ресурса. У каждого префаба есть скрипт Resource. В этом скрипте есть много значений, таких как: cost, name, description, hardness, isTaked и т.д. Также есть значение isKnown. То есть, знает ли игрок что-то об этом ресурсе. Игрок, добывший неизвестный ресурс, должен закинуть его в лабораторию для его изучения. После этого он уже знает о нём всё (isKnown = true). Ему доступны уже другие значения (name, description и т.д.). Но значение я поменял только в текущем клоне и это я понимаю.

Чтобы поменять это самое значение (isKnown) в других клонах данного ресурса на карте мне нужно будет перелопатить 2000+ объектов и в нужных заменить значение с isKnown = false на isKnown = true. Как по мне это далеко не самое правильное решение. Возможно, я изначально подошел к этому вопросу неправильно. Как мне изменять значение одного типа ресурса по ходу игры и при этом другие клоны этого же типа ресурсы знали об этом изменении? Я думал по поводу делегатов и ивентов. Но я не знаю как правильно реализовать через них.


Ответы (2 шт):

Автор решения: Sergey Skvortsov

Простой и рабочий вариант, - использовать статику

Заведите статичное (общее для всего класса) поле static public bool isKnown {get; set}

Более правильное решение, которое обеспечит больше гибкости в будущем и некоторую страховку от ошибок, - хранить информацию о изученности отдельно от ресурсов, где-нибудь в статичном классе GameContext

Если решите идти по этому пути, то класс предстоит написать самому, но вот несколько советов:

  1. Стоит запретить опускать флаг изученности, то есть у вас будет

а) Публичный метод, что бы узнать об изученности ресурса

б) Публичный метод, что бы сделать не изученный ресурс изученным

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

    static private bool _value = false; 

    static public bool Value
    {
        get => _value;
        set
        {
            if (!_value)
            {
                _value = value;
            }
            else
            {
               //действие при попытке отменить изученность, например, можно кинуть исключение
            }
        }
    }

  1. Стоит завести Enum по типам ресурсов и везде оперировать им, тогда в статичном контексте у вас будет два всего публичных метода, даже для кучи ресурсов:

а) Публичный метод, что бы узнать об изученности ресурса по enumу

б) Публичный метод, что бы сделать не изученный ресурс изученным по enumу

Примечание: используйте статику с умом. Её использование увеличивает связанность кода и затрудняет тестирование

→ Ссылка
Автор решения: Yaroslav

Сам вопрос "Как изменить одно и то-же поле на одно и то-же значение у множества объектов" намекает на то, что это поле описывает не конкретную единицу а сразу все множество и должно быть в единственном месте.

Вам нужен объект описывающий каждый тип ресурса. Но изменяемые и не изменяемые значения всегда хранят в разных моделях. Для константных данных прекрасно подойдет ScriptableObject.

[CreateAssetMenu(fileName = "ResourceConstance", menuName = "Resource/Constance")]
public class ResourceConstance : ScriptableObject
{
    public ResourceType Type;
    public string Name;
    public string Description;
    public int BaseCost;
    public int BaseHardness;
}

Полная информация о ресурсах состоит и констант и значений.

[Serializable]
public class ResourceValues
{
    public ResourceType Type;
    public bool IsExplored;
    public int BonusLevel;
    public float BoostFactor;

    public ResourceValues (ResourceType type)
    {
        Type = type;
    }
}

public struct ResourceData 
{
    public readonly ResourceConstance Constance;
    public ResourceValues Values;

    public ResourceData (ResourceConstance constance, ResourceValues values)
    {
        Constance = constance;
        Values = values;
    }
}

Ну и массив ресурсов тоже ScriptableObject

[CreateAssetMenu(fileName = "ResourceCollection", menuName = "Resource/Collection")]
public class ResourceDataCollection : ScriptableObject
{
    [SerializeField] private ResourceConstance[] _constances;
}

ResourceCollection можно использовать не только как контейнер, но и фасад общей точки доступа данных по ресурсов.

То-есть есть метод Initialize который кто-то в начале сцены один раз запускает и он либо грузит данные ли создает свежие.

public class ResourceDataCollection : ScriptableObject
{
    [SerializeField] private string _key = "ResourcesData";
    [SerializeField] private ResourceConstance[] _constances;

    public Dictionary<ResourceType, ResourceData> Resources;

    public void Initialize ()
    {
        if (PlayerPrefs.HasKey(_key))
            Load();
        else
            Default();
    }

    public void Save ()
    {
        ResourceValues[] values = Resources.Select(r => r.Value.Values).ToArray();
        ValuesSaveCollection save = new ValuesSaveCollection(values);
        string json = JsonUtility.ToJson(values);
        PlayerPrefs.SetString(_key, json);
        PlayerPrefs.Save();
    }

    private void Load ()
    {
        Resources = new Dictionary<ResourceType, ResourceData>();
        string json = PlayerPrefs.GetString(_key);
        ValuesSaveCollection save = JsonUtility.FromJson<ValuesSaveCollection>(json);
        // сопастовляем каждый values.Type с элементами _constances и собираем Resources
        // для ресурсов из _constances которые не были использованы создаем свежие записи
    }

    [ContextMenu("ClearData")]
    private void Default ()
    {
        Resources = new Dictionary<ResourceType, ResourceData>();
        foreach (var constance in _constances)
            Resources.Add(constance.Type, new ResourceData(constance, new ResourceValues(constance.Type)));
        Save();
    }

    [Serializable]
    private struct ValuesSaveCollection
    {
        public ResourceValues[] Values;

        public ValuesSaveCollection (ResourceValues[] values)
        {
            Values = values;
        }
    }
}

Можно сделать более закрытую и защищенную (скорее нужно) с методами изменения, автоматически сохраняющие их, добавить эвенты оповещения и т.д.

→ Ссылка