Unity Odin从入门到精通(七):自定义解析器

注意事项:如下所示:
1.自定义解析器主要用来扩展自定义绘制器
2.解析字符串不仅可以是硬编码,而且还可以是以$开头的成员引用或者以@开头的表达式。

命名值:如下所示:
1.命名值的类型为NamedValue。具有以下特性:
1.1.主要作用:命名值是解析器的重要组成部分,用来表示解析字符串可以访问的上下文值。当解析字符串为表达式时,此时可以使用"$命名值名称"的语法来访问指定名称的命名值;当解析字符串为函数时,此时可以将命名值作为参数传递给函数。
1.2.包含字段:如下所示:
1.2.1.Name:命名值的名称。
1.2.2.Type:命名值的类型。
1.2.3.CurrentValue:命名值的数值。
1.2.4.ValueGetter:命名值的数值委托。
1.3.包含函数:如下所示:
1.3.1.Update:该函数主要用来调用命名值的委托来更新命名值的数值。
2.默认一直存在两种命名值。如下所示:
2.1.InspectorProperty property:表示检视面板属性实例。其中,该命名值可以被解析字符串一直访问。
2.2.TValue value:表示检视面板属性数值。其中,该命名值只能用在具有数值的检视面板属性上,并且TValue会随着检视面板属性对应数值的类型进行动态切换。

值解析器上下文:如下所示:
1.值解析器上下文的类型为ValueResolverContext。具有以下特性:
1.1.主要作用:用来配置值解析器实例所需要的数据信息。
1.2.包含字段:如下所示:
1.2.1.ErrorMessage:该字段表示错误消息。只有当值解析器实例创建失败或者值解析器实例解析异常或者解析字符串不能被解析成目标数据类型的数值时,该字段才会有作用。
1.2.2.ErrorMessageIsDueToException:当ErrorMessage字段数值不为空并且值解析器实例解析异常时,该字段数值为true;否则,该字段数值为false。
1.2.3.FallbackValue:该字段表示备用值。只有当值解析器实例不能将字符串正常解析成目标类型数值时,该字段才会有作用。
1.2.4.HasFallbackValue:该字段表示是否(true表示是,false表示否)存在备用值。
1.2.5.LogExceptions:该字段表示值解析器实例解析异常时,是否(true表示是,false表示否)将异常信息输出到控制台。
1.2.6.NamedValues:该字段表示命名值管理器实例,主要用来管理值解析器实例可用的命名值。
1.2.7.Property:该字段表示值解析器实例关联的检视面板属性实例。
1.3.包含属性:如下所示:
1.3.1.ContextProperty:该属性表示值解析器上下文实例关联的检视面板属性实例。该属性值通常为Property字段值对应的父级检视面板属性实例。
1.3.2.IsResolved:该属性表示值解析器上下文实例是否(true表示是,false表示否)创建成功。
1.3.3.ParentType:该属性表示ContextProperty属性值关联的数值类型名称。等价于:ContextProperty.ValueEntry.TypeOfValue。
1.3.4.ResolvedString:该属性表示解析字符串,用来解析成数值。
1.3.5.ResultType:该属性表示值解析器实例需要解析成的目标数据类型。
1.3.6.SyncRefParametersWithNamedValues:该属性表示值解析器实例是否(true表示是,false表示否)将ref或者out关键字修饰的参数与关联的命名值进行同步。具有以下特性:
1.3.6.1.该属性只能在值解析器上下文实例创建成功之前使用。
1.3.6.2.当该属性值为false时,命名值首先会按照命名值的名称和命名值的适配类型的方式来匹配函数参数(ref或者out参数以及非ref或者out参数);然后将命名值的数值传递给匹配的函数参数;最后对匹配的函数参数进行修改数值时,并不会同步到关联的命名值里面。
1.3.6.3.当该属性值为true时,命名值首先会按照命名值的名称和命名值的适配类型的方式来匹配函数非ref或者out参数;接着命名值会按照命名值的名称和命名值的相同类型的方式来匹配函数ref或者out参数;然后将命名值的数值传递给匹配的函数参数;最后对匹配的函数ref或者out参数进行修改数值时,就会同步到关联的命名值里面。
1.4.包含函数:如下所示:
1.4.1.AddDefaultContextValues:该函数会在创建值解析器实例时自动调用,主要用来将默认命名值(InspectorProperty property和TValue value)添加到NamedValues字段关联的命名值上。
1.4.2.CreateDefault(InspectorProperty, String, T, NamedValue[]):该函数用来创建一个新的值解析器上下文实例。其中,泛型参数表示目标数据类型;第一个参数表示检视面板属性实例;第二个参数表示解析字符串;第三个参数表示值解析器实例创建失败或者值解析器实例解析异常或者解析字符串不能被解析成目标数据类型的数值;第四个参数表示额外的命名值列表。
1.4.3.CreateDefault(InspectorProperty, Type, String, NamedValue[]):该函数用来创建一个新的值解析器上下文实例。其中,第一个参数表示检视面板属性实例;第二个参数表示目标数据类型;第三个参数表示解析字符串;第四个参数表示额外的命名值列表。
1.4.4.MarkResolved:该函数用来标记值解析器上下文实例创建成功。当多次调用且值解析器上下文实例已创建成功时,就会抛出异常信息。
1.4.5.SetParentValue:该函数用来将ContextProperty属性关联的WeakValues集合中指定索引处设置成指定数值。
1.4.6.GetParentValue:该函数用来获取ContextProperty属性关联的WeakValues集合中指定索引处的数值。
1.5.注意事项:如下所示:
1.5.1.值解析器上下文实例通常和值解析器实例配合使用。
1.5.2.由于值解析器上下文实例是一个非常大的结构,所以通过ref或者out传递该结构就不仅可以提高程序的性能,而且还可以提高程序的易用性。

值解析器:如下所示:
1.值解析器的基础类型为ValueResolver。具有以下特性:
1.1.主要作用:用来将字符串解析成指定类型的数值。
1.2.包含字段:如下所示:
1.2.1.Context:该字段表示值解析器上下文实例。
1.3.包含属性:如下所示:
1.3.1.HasError:该属性表示此刻是否存在错误消息。
1.3.2.ErrorMessage:该属性表示获取此刻的错误消息。如果此刻不存在错误消息的话,那么该属性就返回null。
1.3.3.ValueType:该属性表示值解析器实例需要解析成的目标数据类型。
1.4.包含函数:如下所示:
1.4.1.DrawError:当此刻存在错误消息时,就绘制一个错误消息提示框;否则,就不做任何处理。
1.4.2.DrawErrors(ValueResolver, ValueResolver, ValueResolver, ValueResolver, ValueResolver, ValueResolver, ValueResolver, ValueResolver, ValueResolver[]):该函数首先通过参数来接收若干个值解析器实例;然后调用每个值解析器实例的DrawError函数。
1.4.3.GetCombinedErrors(ValueResolver, ValueResolver, ValueResolver, ValueResolver, ValueResolver, ValueResolver, ValueResolver, ValueResolver, ValueResolver[]):该函数首先通过参数来接收若干个值解析器实例;接着判定每个值解析器实例的ErrorMessage属性是否为空;然后将不为空的ErrorMessage属性值添加到缓冲区中;最后当缓冲区长度为空时就返回null,否则就返回缓冲区组合的字符串。
1.4.4.Get(Type, InspectorProperty, String, NamedValue[]):以弱类型方式来创建一个新的值解析器实例。其中,第一个参数表示目标数据类型;第二个参数表示检视面板属性实例;第三个参数表示解析字符串;第四个参数表示额外的命名值列表。
1.4.5.Get<TResult>(InspectorProperty, String, NamedValue[]):以强类型方式来创建一个新的值解析器实例。其中,泛型参数表示目标数据类型;第一个参数表示检视面板属性实例;第二个参数表示解析字符串;第三个参数表示额外的命名值列表。
1.4.6.Get(Type, InspectorProperty, String, Object, NamedValue[]):以弱类型方式来创建一个新的值解析器实例。其中,第一个参数表示目标数据类型;第二个参数表示检视面板属性实例;第三个参数表示解析字符串;第四个参数表示值解析器实例创建失败或者值解析器实例解析异常或者解析字符串不能被解析成目标数据类型的数值,就返回该参数代表的默认值;第五个参数表示额外的命名值列表。
1.4.7.Get<TResult>(InspectorProperty, String, TResult, NamedValue[]):以强类型方式来创建一个新的值解析器实例。其中,泛型参数表示目标数据类型;第一个参数表示检视面板属性实例;第二个参数表示解析字符串;第三个参数表示值解析器实例创建失败或者值解析器实例解析异常或者解析字符串不能被解析成目标数据类型的数值,就返回该参数代表的默认值;第四个参数表示额外的命名值列表。
1.4.8.GetFromContextWeak(ref ValueResolverContext):以弱类型方式来创建一个新的值解析器实例。其中,第一个参数表示值解析器上下文实例。
1.4.9.GetFromContext<TResult>(ref ValueResolverContext):以强类型方式来创建一个新的值解析器实例。其中,泛型参数表示目标数据类型;第一个参数表示值解析器上下文实例。
1.4.10.GetForString(InspectorProperty, String, NamedValue[]):创建一个目标类型为字符串的值解析器实例。其中,第一个参数表示检视面板属性实例;第二个参数既表示解析字符串也表示解析失败时返回的默认值;第三个参数表示额外的命名值列表。
1.4.11.GetWeakValue(Int32):以弱类型方式从值解析器实例中获取一个值。其中,第一个参数表示选择索引,默认值为0。
2.值解析器的派生类型为ValueResolver<TResult>。具有以下特性:
2.1.主要作用:用来扩展ValueResolver。
2.2.包含字段:如下所示:
2.2.1.Func:在调用GetValue函数时,就会主动调用该字段代表的委托函数,进而获取目标类型的最终数值。
2.3.包含属性:如下所示:
2.3.1.ValueType:该属性表示值解析器实例需要解析成的目标数据类型。
2.4.包含函数:如下所示:
2.4.1.GetWeakValue(Int32):以弱类型方式从值解析器实例中获取一个值。其中,第一个参数表示选择索引,此处貌似废弃不用,选择索引永远固定为0。
2.4.2.GetValue(Int32):以强类型方式从值解析器实例中获取一个值。其中,第一个参数表示选择索引,默认值为0。
3.自定义值解析器流程如下所示:
3.1.首先创建一个继承自Attribute类型并且应用范围为字段或者属性的定制特性类型;然后将该定制特性类型应用到想要进行自定义绘制的字段或者属性上面。参考代码如下所示:

public class SampleValueResolver : MonoBehaviour
{
	// 在该字段上应用定制特性类型,并且交给该定制特性类型关联的定制特性绘制器类型来进行自定义绘制
    [DisplayFormattedDate("@$hour + \":\" + $minute + \":\" + $second")]
    public string datedString;
}

public class DisplayFormattedDateAttribute : Attribute
{
    public string FormattedDate;

    public DisplayFormattedDateAttribute(string formattedDate)
    {
        this.FormattedDate = formattedDate;
    }
}

3.2.首先在编辑器环境下创建一个继承自OdinAttributeDrawer类型的定制特性绘制器类型;然后在泛型参数中指定该定制特性绘制器类型关联的定制特性类型;最后在该定制特性绘制器类型中定义一个值解析器实例字段。参考代码如下所示:

public class DisplayFormattedDateAttributeDrawer : OdinAttributeDrawer<DisplayFormattedDateAttribute>
{
    private ValueResolver<string> formattedDateResolver;
}

3.3.首先重写定制特性绘制器类型中的Initialize函数;然后在该函数里面通过调用值解析器类型的GetForString函数以及将Property实例、解析字符串和命名值列表一起作为参数来创建一个值解析器实例。参考代码如下所示:

protected override void Initialize()
{
	this.formattedDateResolver = ValueResolver.GetForString(this.Property, this.Attribute.FormattedDate, new NamedValue[]
	{
		new NamedValue("hour", typeof(int)),
		new NamedValue("minute", typeof(int)),
		new NamedValue("second", typeof(int)),
	});
}

3.4.首先重写定制特性绘制器类型中的DrawPropertyLayout函数;接着在该函数里面通过值解析器实例调用HasError属性来判定此刻是否存在错误消息,如果存在错误消息的话就通过值解析器实例调用DrawError函数来绘制错误消息框;然后获取值解析器实例关联的值解析器上下文实例中的命名值列表并设置相关命名值的数值;最后调用值解析器实例的GetValue函数来获取最终的目标数值并以文本标签的形式显示在检视面板属性的上方。参考代码如下所示:

protected override void DrawPropertyLayout(GUIContent label)
{
	if (this.formattedDateResolver.HasError)
	{
		this.formattedDateResolver.DrawError();
	}
	else
	{
		var time = DateTime.Now;
		this.formattedDateResolver.Context.NamedValues.Set("hour", time.Hour);
		this.formattedDateResolver.Context.NamedValues.Set("minute", time.Minute);
		this.formattedDateResolver.Context.NamedValues.Set("second", time.Second);
            
		GUILayout.Label(this.formattedDateResolver.GetValue());
	}
        
	this.CallNextDrawer(label);
}

行为解析器上下文:如下所示:
1.行为解析器上下文的类型为ActionResolverContext。具有以下特性:
1.1.主要作用:用来配置行为解析器实例所需要的数据信息。
1.2.包含字段:如下所示:
1.2.1.ErrorMessage:该字段表示错误消息。只有当行为解析器实例创建失败或者解析字符串不能被解析成行为或者行为解析器实例调用行为抛出异常时,该字段才会有作用。
1.2.2.ErrorMessageIsDueToException:当ErrorMessage字段数值不为空并且行为解析器实例调用行为抛出异常时,该字段数值为true;否则,该字段数值为false。
1.2.3.LogExceptions:该字段表示行为解析器实例调用行为期间,是否(true表示是,false表示否)将引起的异常信息输出到控制台。
1.2.4.NamedValues:该字段表示命名值管理器实例,主要用来管理行为解析器实例可用的命名值。
1.2.5.Property:该字段表示行为解析器实例关联的检视面板属性实例。
1.3.包含属性:如下所示:
1.3.1.ContextProperty:该属性表示行为解析器上下文实例关联的检视面板属性实例。该属性值通常为Property字段值对应的父级检视面板属性实例。
1.3.2.IsResolved:该属性表示行为解析器上下文实例是否(true表示是,false表示否)创建成功。
1.3.3.ParentType:该属性表示ContextProperty属性值关联的数值类型名称。等价于:ContextProperty.ValueEntry.TypeOfValue。
1.3.4.ResolvedString:该属性表示解析字符串,用来解析成行为。
1.3.5.SyncRefParametersWithNamedValues:该属性表示行为解析器实例是否(true表示是,false表示否)将ref或者out关键字修饰的参数与关联的命名值进行同步。具有以下特性:
1.3.5.1.该属性只能在行为解析器上下文实例创建成功之前使用。
1.3.5.2.当该属性值为false时,命名值首先会按照命名值的名称和命名值的适配类型的方式来匹配函数参数(ref或者out参数以及非ref或者out参数);然后将命名值的数值传递给匹配的函数参数;最后对匹配的函数参数进行修改数值时,并不会同步到关联的命名值里面。
1.3.5.3.当该属性值为true时,命名值首先会按照命名值的名称和命名值的适配类型的方式来匹配函数非ref或者out参数;接着命名值会按照命名值的名称和命名值的相同类型的方式来匹配函数ref或者out参数;然后将命名值的数值传递给匹配的函数参数;最后对匹配的函数ref或者out参数进行修改数值时,就会同步到关联的命名值里面。
1.4.包含函数:如下所示:
1.4.1.AddDefaultContextValues:该函数会在创建行为解析器实例时自动调用,主要用来将默认命名值(InspectorProperty property和TValue value)添加到NamedValues字段关联的命名值上。
1.4.2.CreateDefault(InspectorProperty, String, NamedValue[]):该函数用来创建一个新的行为解析器上下文实例。其中,第一个参数表示检视面板属性实例;第二个参数表示解析字符串;第三个参数表示额外的命名值列表。
1.4.3.MarkResolved:该函数用来标记行为解析器上下文实例创建成功。当多次调用且行为解析器上下文实例已创建成功时,就会抛出异常信息。
1.4.4.SetParentValue:该函数用来将ContextProperty属性关联的WeakValues集合中指定索引处设置成指定数值。
1.4.5.GetParentValue:该函数用来获取ContextProperty属性关联的WeakValues集合中指定索引处的数值。
1.5.注意事项:如下所示:
1.5.1.行为解析器上下文实例通常和行为解析器实例配合使用。
1.5.2.由于行为解析器上下文实例是一个非常大的结构,所以通过ref或者out传递该结构就不仅可以提高程序的性能,而且还可以提高程序的易用性。

行为解析器:如下所示:
1.行为解析器的类型为ActionResolver。具有以下特性:
1.1.主要作用:用来将字符串解析成行为。
1.2.包含字段:如下所示:
1.2.1.Context:该字段表示行为解析器上下文实例。
1.2.2.Action:该字段表示解析字符串解析成的行为。注意:不要手动调用该字段,而是通过调用DoAction函数来间接调用该字段。
1.3.包含属性:如下所示:
1.3.1.HasError:该属性表示此刻是否存在错误消息。
1.3.2.ErrorMessage:该属性表示获取此刻的错误消息。如果此刻不存在错误消息的话,那么该属性就返回null。
1.4.包含函数:如下所示:
1.4.1.DrawError:当此刻存在错误消息时,就绘制一个错误消息提示框;否则,就不做任何处理。
1.4.2.DrawErrors(ActionResolver, ActionResolver, ActionResolver, ActionResolver, ActionResolver, ActionResolver, ActionResolver, ActionResolver, ActionResolver[]):该函数首先通过参数来接收若干个行为解析器实例;然后调用每个行为解析器实例的DrawError函数。
1.4.3.GetCombinedErrors(ActionResolver, ActionResolver, ActionResolver, ActionResolver, ActionResolver, ActionResolver, ActionResolver, ActionResolver, ActionResolver[]):该函数首先通过参数来接收若干个行为解析器实例;接着判定每个行为解析器实例的ErrorMessage属性是否为空;然后将不为空的ErrorMessage属性值添加到缓冲区中;最后当缓冲区长度为空时就返回null,否则就返回缓冲区组合的字符串。
1.4.4.Get(InspectorProperty, String, NamedValue[]):该函数用来创建一个新的行为解析器实例。其中,第一个参数表示检视面板属性实例;第二个参数表示解析字符串;第三个参数表示额外的命名值列表。
1.4.5.GetFromContext(ref ActionResolverContext):该函数用来创建一个新的行为解析器实例。其中,第一个参数表示行为解析器上下文实例。
1.4.6.DoActionForAllSelectionIndices:该函数首先会获取Context字段关联的父级检视面板属性对应的类型列表;然后依次遍历该类型列表并调用DoAction函数。
1.4.7.DoAction(Int32):该函数首先会接收Context字段关联的父级检视面板属性对应的类型列表索引值,默认值为0;然后通过调用Action字段代表的行为来执行具体的操作流程。
2.自定义行为解析器流程如下所示:
2.1.首先创建一个继承自Attribute类型并且应用范围为字段或者属性的定制特性类型;然后将该定制特性类型应用到想要进行自定义绘制的字段或者属性上面。参考代码如下所示:

public class SampleActionResolver : MonoBehaviour
{
	// 在该字段上应用定制特性类型,并且交给该定制特性类型关联的定制特性绘制器类型来进行自定义绘制
    [ActionButton("DoAction")]
    public string methodReferenceActions;
    // 解析字符串解析成的行为,对应ActionResolver类型中的Action字段,参数为传递的命名值
    private void DoAction(ref int hour,  out int minute, ref int second)
    {
        var time = DateTime.Now;
        hour = time.Hour;
        minute = time.Minute;
        second = time.Second;
        Debug.Log("DoAction ====> hour:" + hour + ",minute:" + minute + ",second:" + second);
    }
}

public class ActionButtonAttribute : Attribute
{
	// 解析字符串
    public string Action;

    public ActionButtonAttribute(string action)
    {
        this.Action = action;
    }
}

2.2.首先在编辑器环境下创建一个继承自OdinAttributeDrawer类型的定制特性绘制器类型;然后在泛型参数中指定该定制特性绘制器类型关联的定制特性类型;最后在该定制特性绘制器类型中定义一个行为解析器实例字段。参考代码如下所示:

public class ActionButtonAttributeDrawer : OdinAttributeDrawer<ActionButtonAttribute>
{
	// 行为解析器实例
    private ActionResolver actionResolver;
}

2.3.首先重写定制特性绘制器类型中的Initialize函数;接着在该函数里面创建一个行为解析器上下文实例;然后将Property实例、解析字符串、命名值列表、是否同步参数关联命名值等数据信息赋值给行为解析器上下文实例的相关字段。最后在该函数里面通过调用行为解析器类型的GetFromContext函数以及将行文解析器上下文实例作为参数来创建一个行为解析器实例。参考代码如下所示:

protected override void Initialize()
{
	var context = new ActionResolverContext();
	context.Property = this.Property;
	context.ResolvedString = this.Attribute.Action;
	context.NamedValues = new NamedValues();
	context.NamedValues.Add("hour", typeof(int), 1);
	context.NamedValues.Add("minute", typeof(int), 2);
	context.NamedValues.Add("second", typeof(int), 3);
	context.SyncRefParametersWithNamedValues = true;
	this.actionResolver = ActionResolver.GetFromContext(ref context);
}

2.4.首先重写定制特性绘制器类型中的DrawPropertyLayout函数;然后在该函数里面通过行为解析器实例调用HasError属性来判定此刻是否存在错误消息,如果存在错误消息的话就通过行为解析器实例调用DrawError函数来绘制错误消息框;最后调用行为解析器实例的DoActionForAllSelectionIndices函数来执行Action字段代表的行为。参考代码如下所示:

protected override void DrawPropertyLayout(GUIContent label)
{
	if (this.actionResolver.HasError)
	{
		this.actionResolver.DrawError();
	}
	else
	{
		if (GUILayout.Button("Perform Action"))
		{
			this.actionResolver.DoActionForAllSelectionIndices();
			var hour = this.actionResolver.Context.NamedValues.GetValue("hour");
			var minute = this.actionResolver.Context.NamedValues.GetValue("minute");
			var second = this.actionResolver.Context.NamedValues.GetValue("second");
			Debug.Log("DrawPropertyLayout ====> hour:" + hour + ",minute:" + minute + ",second:" + second);
		}
	}
        
	this.CallNextDrawer(label);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值