Сразу хочу сказать, что если подобный вопрос уже задавался, прошу указать на него.
Допустим, у меня есть Scriptable Object, который я использую для передачи здоровья игрока из одной системы в другую.
public class HealthSO: ScriptableObject {
[ReadOnly]
public float health;
}
Класс с именем PlayerHealth устанавливает значение в Scriptable Object, чтобы другие системы могли его использовать. Например: полоса здоровья игрока.
Это здорово, потому что я могу свободно подключать разные системы, не ссылаясь на них, но не без проблем, и есть одна, которая меня больше всего беспокоит.
Как мне убедиться, что единственным классом, который может изменить значение здоровья в Scriptable Object, является PlayerHealth?
Или, может быть, это то, о чем мне не следует слишком беспокоиться? Конечно, если над проектом работает только один человек, то беспокоиться не о чем. Но что, если этот подход будет применяться в более крупном проекте?
Спасибо!
Это интересно, потому что этот подход упоминается в разговорах о SO. Например здесь: youtu.be/raQ3iHhE_Kk?t=926 Мы с моей командой думаем об использовании этого в определенной степени. Некоторые системы в нашей игре содержат данные, которыми нужно поделиться с пользовательским интерфейсом/другими системами, и мы попытались придумать способ защитить эти данные.
Ага! это именно то видео, которое вдохновило нас попробовать.





Это может быть немного спорным, но использование ScriptableObject для этого в первую очередь ^^
К сожалению, Unity до сих пор не поддерживает сериализацию полей типа interface. Но в этом случае есть только два разных уровня доступа — чтение и запись.
Таким образом, вы можете сделать что-то вроде
// Just going generic here as latest Unity versions finally support it
// and you have way less re-implementation of the same functionality
public abstract class ReadonlyValueSO<T> : ScriptableObject
{
[SerializeField]
[ReadOnly]
protected T _value;
public T Value
{
get => _value;
}
}
public abstract class WriteableValueSO<T> : ReadonlyValueSO<T>
{
public void Set(T value)
{
_value = value;
}
}
// Some constants could even be ReadonlyValueSO if you never want to write over them anyway
[CreateAssetMenu]
public class HealthSO : WriteableValueSO<float>
{
}
Таким образом, в вашем компоненте установки вы должны использовать записываемый тип и делать, например.
public class SomeSetter : MonoBehaviour
{
[SerializeField] WriteableValueSO<float> health;
private void Update()
{
health.Set(health.Value + .1f * Time.deltaTime);
}
}
в то время как в потребителях вы даете ему только читаемый
public class Consumer : MonoBehaviour
{
[SerializeField] ReadonlyValueSO<float> health;
private void Update()
{
Debug.Log(health.Value);
}
}
Таким образом, у вас есть полный контроль над тем, кто может читать и кто может писать.
Еще одно огромное преимущество: таким образом вам также не нужно опрашивать контрольные значения, как я делал выше. Вы можете просто добавить даже, чтобы оно вызывалось всякий раз, когда установлено значение:
public abstract class ReadonlyValueSO<T> : ScriptableObject
{
[SerializeField]
protected T _value;
public T Value
{
get => _value;
}
public abstract event Action<T> ValueChanged;
}
public abstract class WriteableValueSO<T> : ReadonlyValueSO<T>
{
public void Set(T value)
{
_value = value;
ValueChanged?.Invoke(_value);
}
public override event Action<T> ValueChanged;
}
теперь ваш потребитель может выглядеть, например, как
public class Consumer : MonoBehaviour
{
[SerializeField] ReadonlyValueSO<float> health;
private void Awake()
{
// subscribe to event
health.ValueChanged -= OnHealthChanged;
health.ValueChanged += OnHealthChanged;
// invoke now once with the current value
OnHealthChanged(health.Value);
}
private void OnDestroy()
{
// IMPORTANT: Unsubscribe!
health.ValueChanged -= OnHealthChanged;
}
// Always and only called whenever something sets the value
private void OnHealthChanged(float newHealth)
{
Debug.Log(newHealth);
}
}
Я думаю, что ваш ответ я максимально близок к тому, чего я хочу достичь. Спасибо :) Моя единственная проблема с этим будет заключаться в том, что вы все еще можете использовать тип WriteableValueSO в потребителе и обмануть свой выход из этого.
@t_Gmek, как только у вас есть доступ к исходному коду, всегда есть способы (отражение в крайнем случае) для доступа и управления любым полем или свойством. Нет никакого реального способа обеспечить, какой тип вы используете, нет;)
Я работал над таким проектом, лично я бы не рекомендовал его, он становится очень запутанным с большим количеством переменных и еще хуже, когда вам нужно динамически создавать экземпляры объектов.