在this talk中,我学习了如何使用可编写脚本的对象创建变量,如何创建FloatVariable,DoubleVariable,StringVariable等类。但是,在同一个谈话中,那个家伙说他使用了一个更具动态性的变量系统,从而阻止了创建多个类来处理所有变量类型。
在第一个系统中,我有一个名为ImageFillSetter的C#脚本,该脚本提供了两个float变量和一个Image脚本,它将两个变量的除法返回到图像的fillAmount变量。
但是,当我得到一个Double变量,并且想要使用该值设置进度条时,我需要创建另一个名为ImageFillSetterDouble的脚本,并放入这些变量。如果我需要使用Integers创建一个?每次创建这样的脚本时,都需要创建两个副本来处理其他数字变量类型? 使用此动态变量系统,应该可以解决此问题,但是我不知道如何启动/创建此系统。
代码如下:
[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() { }
}
答案 0 :(得分:0)
看看Generics
有一个像这样的抽象类
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
}
}
答案 1 :(得分:0)
我知道有一个可以接受的答案,但是我觉得链接视频中描述的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将“观察” FloatVariable,而无需知道该浮点数是如何计算的。
这样,您只需拥有一个可用于任何图像和任何数据源的ImageFillSetter,同时具有一种或多种更改填充的方式,而无需对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)视为存储在数据库中的值。任何人都可以读取该值,任何人都可以保存一个新值。将所有可能的值计算结果存储在数据库中,而不是进行计算并仅存储答案,这很奇怪。
这不会改变您需要为每个原语编写可脚本实现的事实:
但是您只需要一个,如derHugo答案的第一部分所示。