Как создать систему динамических переменных с объектами Scriptable - PullRequest
0 голосов
/ 09 января 2019

В этом выступлении Я научился создавать переменные с объектами, которые можно создавать в сценариях, создавать классы, такие как FloatVariable, DoubleVariable, StringVariable и другие. Но в том же выступлении парень сказал, что использует более динамическую систему переменных, которая не позволяет создавать несколько классов для обработки всех типов переменных.

Используя первую систему, у меня был сценарий C # с именем ImageFillSetter, который с учетом двух переменных с плавающей запятой и сценария Image возвращает разделение двух переменных на переменную fillAmount изображения.

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

Код выглядит так:

[CreateAssetMenu(menuName="Variable/Float")]
public class FloatVariable : ScriptableObject, ISerializationCallbackReceiver
{
    public float initialValue;
    [NonSerialized] 
    public float value;

    public void OnAfterDeserialize()
    {
        value = initialValue;
    }

    public void OnBeforeSerialize() { }
}

Я хочу что-то вроде этого (абсолютно гипотетически, я знаю, что это не работает)

[CreateAssetMenu(menuName="Variable")]
public class Variable : ScriptableObject, ISerializationCallbackReceiver
{
    public var initialValue;
    [NonSerialized] 
    public var value;

    public void OnAfterDeserialize()
    {
        value = initialValue;
    }

    public void OnBeforeSerialize() { }
}

Ответы [ 2 ]

0 голосов
/ 11 января 2019

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

Я думаю, вам лучше сделать переменную FloatVariable независимой от вычислений.

Предположим, что вы рассчитываете на здоровье игрока, и ваше значение заполнения будет рассчитано как currentHealth/maxHealth.

public class PlayerHealth: MonoBehaviour
{
    [SerializeField] private FloatVariable floatReference;
    [SerializeField] private float maxHealth;
    [SerializeField] private float currentHealth;

    void Update()
    {
        this.floatReference.value = currentHealth/maxHealth;
    }
}


public class ImageFillSetter: MonoBehaviour
{
     [SerializeField] private FloatVariable floatReference;
     [SerializeField] private Image imageReference;

     void Update()
    {
        this.imageReference.fill = this.floatReference.value;
    }
}

Или, скажем, здоровье игрока хранится в двойном виде:

public class PlayerHealth: MonoBehaviour
{
    [SerializeField] private FloatVariable floatReference;
    [SerializeField] private double maxHealth;
    [SerializeField] private double currentHealth;

    void Update()
    {
        this.floatReference.value = (float)(currentHealth/maxHealth);
    }
}

Теперь предположим, что вы добавили поле ввода, в которое можно ввести значение заполнения в виде процентной строки (например, «76»):

public class FillInput: MonoBehaviour
{
    [SerializeField] private FloatVariable floatReference;
    [SerializeField] private Input input;

    void Update()
    {
        if(Input.GetKeyDown(KeyCode.Enter))
        {
            this.floatReference.value = float.Parse(input.text)/100f;
        }
    }
}

ImageFillSetter будет «наблюдать» переменную Float, не зная, как рассчитывался этот float.

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

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

public class FillInput: MonoBehaviour
{
    [SerializeField] private FloatVariable floatReference;
    private AsyncOperation loadOperation;

    void LoadLevelAsync(string levelName)
    {
        this.loadOperation = SceneManager.LoadLevelAsync(levelName, LoadSceneMode.Additive);
    }

    void Update()
    {
        this.floatReference.value = this.loadOperation?.progress ?? 0;
    }
}

Это будет работать без каких-либо других изменений, если ваш ImageFillSetter ссылается на тот же FloatVariable.

Думайте о FloatVariable (или любом другом примитиве, например DoubleVariable) как о значении, хранящемся в базе данных. Любой может прочитать значение, и любой может сохранить новое значение. Было бы странно хранить все возможные вычисления для значения в базе данных вместо того, чтобы делать вычисления и просто хранить ответ.

Это не меняет того факта, что вам нужны реализации Scriptable для каждого примитива:

  • FloatVariable
  • DoubleVariable
  • StringVariable
  • BoolVariable
  • и т.д.

но вам понадобится только один из них, как показано в первом разделе ответа derHugo.

0 голосов
/ 09 января 2019

Посмотрите на Дженерики

Имейте один абстрактный класс как

public abstract class ValueAsset<T> : ScriptableObject
{
    public T value;

    // Add your methods

    // Here some more examples also using the T value. They might also be abstract but they don't have to be

    // return a T
    public T GetValue()
    {
        return value;
    }

    // or pass a T
    public void SetValue(T input)
    {
        value = input;
    }
}

Этот класс вы никогда не создадите, но теперь извлекаете из него несколько реализаций, например

[CreateAssetMenu(fileName = "new int", menuName = "ValueAssets/int")]
public class IntValue : ValueAsset<int>
{
    // Maybe constructors here or additional fields and methods
}

[CreateAssetMenu(fileName = "new float", menuName = "ValueAssets/float")]
public class FloatValue : ValueAsset<float>
{
    // Maybe constructors here or additional fields
}

Вы также можете иметь несколько общих значений, таких как

public abstract class OtherExample<TKey, TValue> : ScriptableObject
{
    // Note that this is just an example
    // Dictionary is not serializable
    public Dictionary<TKey, TValue> values = new Dictionary<TKey, TValue>();

    public void AddPair(TKey key, TVakue value)
    {
        values.Add(key, value);
    }
}

И реализовать что-то вроде

public OneImplementation : OtherExample<string, GameObject>
{
    //...
}

Точно так же это можно использовать для справочных значений (компоненты, GameObject и т. Д.)


Таким образом, для IntValue метод GetValue вернет int, а SetValue примет int в качестве параметра. Точно так же они берут и возвращают float в FloatValue.


Делая то же самое с ImageFillSetter<T>, вы можете сделать свой метод abstract и реализовать различные варианты поведения для разных значений T (например, разный анализ и т. Д.)

Примечание: я не знаю почему, но в прошлом я заметил, что

public ValueAsset<T> valueAsset;

не будет сериализовано в инспекторе, даже если позднее будет реализовано, поэтому вместо этого вам нужно будет реализовать поле с правильным типом в реализации. Вы также можете переопределить его во время выполнения, но вы можете пропустить всю часть FetchValue, если она вам не нужна, и в любом случае использовать valueReference - просто добавьте ее для полноты.

public abstract class ImageFillSettet<T> : MonoBehaviour
{
    // Will not appear in the Inspector
    public ValueAsset<T> ValueAsset;

    // Override this in implementation
    protected abstract void FetchValue();

    // Use it for Initializing the value
    private void Awake ()
    {
        FetchValue();
    }

    public abstract void SetFill();   
}

Чем позже

public class ImageFillSetterFloat : ImageFillSetter<float>
{
    // Show in the inspector
    [SerializeField] private FloatValue valueReference;

    // Provide the reference to the base class
    protected override void Fetch value()
    {
        valueAsset = valueReference;
    }

    public override void SetFill()
    {
        // Use valueReference for something
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...