需求描述
- 需要在运行时修改脚本的属性值
- 某个对象只在特定场景或时机表现出特殊属性,其余时表现正常属性(属性值修改不能直接修改玩家的默认配置)
分析
本质就是希望对象在不同条件下应用不同的属性,以表现出不同的特性(如玩家的移动速度在某些场景变慢/变快)。
但凡是有参数依赖的配置,都应该用配置的方式去做依赖注入,不应该使用反射破坏性的修改,更不应该在运行时使用反射。
反射是邪恶的,但在架构完整,流程完善的情况下,无法短时间低成本的建立一个新的配置注入的机制,反射能快速完成需求并很好的与代码结构解耦。
我不愿意这样做,但确实能提供一种极细粒度,极高灵活性的问题解决方案。
解决方案
- 一个参数注入器
public class ComponentInjector : MonoBehaviour
{
// 目标物体 这里限定为脚本
public MonoBehaviour TargetObj;
// 参数配置列表
[SerializeField]
private List<PropertyConfig> _propertyConfig;
protected virtual void Start()
{
// 示例 在需要的时刻调用Inject注入参数
if (TargetObj)
{
Inject();
}
}
public virtual void Inject()
{
// 遍历参数配置
foreach (var VARIABLE in _propertyConfig)
{
// 拿到配置的值和希望注入的类型
var result = VARIABLE.GetActiveConfig(out Type type);
if (type != null)
{
// 反射拿到目标物体的字段信息
// GetField(字段名,标识位)
// 字段名指示了需要获取的指定脚本的字段名称
// 标志位指定了一些过滤信息
// 注意 GetField 方法只能拿到没有Get/Set方法的字段
FieldInfo field = TargetObj.GetType().GetField(VARIABLE.PropertyName,BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance);
// 判断指定名称的字段合法且类型与反射配置一致
if (field != null)
{
if (field.FieldType == type)
{
// 注入反射配置
field.SetValue(TargetObj, result);
}
else
{
Debug.LogError($"Inject Error: {TargetObj.name} {VARIABLE.PropertyName} type is not match");
}
}
}
}
}
}
- 反射参数配置
[Serializable]
public class PropertyConfig
{
public string PropertyName;
public PropertyType PropertyType;
public bool isString => PropertyType == PropertyType.String;
public bool isFloat => PropertyType == PropertyType.Float;
public bool isInt => PropertyType == PropertyType.Int;
public bool isCurve => PropertyType == PropertyType.Curve;
[NaughtyAttributes.ShowIf(nameof(isString))]
[AllowNesting]
public string StringProperty;
[NaughtyAttributes.ShowIf(nameof(isFloat))]
[AllowNesting]
public float FloatProperty;
[NaughtyAttributes.ShowIf(nameof(isInt))]
[AllowNesting]
public int IntProperty;
[NaughtyAttributes.ShowIf(nameof(isCurve))]
[AllowNesting]
public AnimationCurve CurveProperty;
public object GetActiveConfig(out Type type)
{
switch (PropertyType)
{
case PropertyType.String:
{
type = typeof(string);
return StringProperty;
}
case PropertyType.Float:
{
type = typeof(float);
return FloatProperty;
}
case PropertyType.Int:
{
type = typeof(int);
return IntProperty;
}
case PropertyType.Curve:
{
type = typeof(AnimationCurve);
return CurveProperty;
}
}
type = null;
return null;
}
}
public enum PropertyType
{
String,
Float,
Int,
Curve
}