欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

ZProperty属性封装类简介

程序员文章站 2022-05-08 09:22:42
...

有关属性使用的封装,包括功能,修饰、持久化、表现层绑定等。类图如下:

ZProperty属性封装类简介

 

附加Attribute修饰

属性在使用时会带一些附加信息,描述信息也有一定的层次的,包括:名称、简介、详细描述、图片介绍、链接等多种形式。即属性又由其子属性组成。属性的附加信息,可以通过Attribute类【标签】来加以定义。

但属性的这些信息,每个属性不能不同的类实例都自己保存一份,那样开销很大的。所有本例中使用外部保存这些Attribute结点信息。

以下面Attribute为例,支持两种配置名称与描述等修饰方法,第一个是通过Attribute属性,第二个是通过持久化文件 (即JSON),当然后者也需要Attribute属性进行配置,后面的属性Attribute会一一列举出来。

其中描述的Attribute定义如下:

	/// <summary>
	/// Property description attribute, the item is the localization item's ID.
	/// </summary>
	[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property, Inherited = true)]
	public class PropertyDescriptionAttribute : Attribute {
		public string description;
		public PropertyDescriptionAttribute(string description)
		{
			this.description = description;
		}
	}

通过以下代码可以获取到其属性的值

		static public string GetName(){
			var type = typeof(PropertyNameAttribute);
			 
			var attribute = type.GetCustomAttributes(typeof(PropertyNameAttribute), true).FirstOrDefault();
			 
			if (attribute == null)
			{
			 	return null;
			}
			 
			return ((PropertyNameAttribute)attribute).name;
		}

 

用法如下:

		[PropertyDescription("等级", "玩家的等级信息", true)]
		public ZRuntimableProperty<int> rank = new ZRuntimableProperty<int>();

 

有关这些文言Localization的支持问题,以上参数设置对应文言字符串的ID,然后通过其获取到各Localization的文言。是要依赖Localization的模块的。参考以下插件。

 

ZProperty属性封装类简介

https://blog.csdn.net/u011597114/article/details/54291247

其实大部分Localization插件都是使用字符串进行标识文言Item的。

可视化绑定

这部分提供了如何与UI能进行自动化的绑定,动态更新等机制,同时对一些常用框架的支持,比如上接UGUI、下连UniRX、ZECS等。同时也支持创建动态UIItem,比如对一些列表的支持等。当然这里的可视化绑定也适用于3D表现。

需求实例(以下面率土为例,列举常用UI Item)

ZProperty属性封装类简介

可以分解出如下类型的属性,以及对应的UI表现。

等级/稀有度:int/byte类型,使用小星星作为表现

血条/兰条:float类型,进度条表现,也会有数值显示,参考包括最大值、颜色。其实就是做为不同的Prefab但是UI类是一个。

数值属性:比如攻击力防御值等属性。会显示附加值即“()”里的内容。类型的附加值,如果固定可通过Attribute的方式进行定义,当然也可以使用下面介绍的复合类型。

ICON:类型左侧卡片上的攻击距离的表现,由一个图标和数值组成。

复合类型:即左侧卡片做为一个整体属性,其本身又由子属性组成。如下代码所示,武器类包括子属性。

	public class Weapon {

		[PropertyDescription("power", "a power data")]
		public ZProperty<float> power = new ZProperty<float>();
	}

	public class Person {

		public string TestID;
			
		[PropertyDescription("等级", "玩家的等级信息")]
		public ZProperty<int> rank = new ZProperty<int>();

		[PropertyDescription("血量", "玩家的名字")]
		[PropertyUIItemRes("Test/Image")]
		public ZProperty<int> blood = new ZProperty<int>();

		[PropertyDescription("weapon007", "a power weapon")]
		public ZProperty<Weapon> weapon = new ZProperty<Weapon>();
	}

对应的UI结构如下,其中结点的名称如下格式。

ZProperty属性封装类简介

调用Bind方法后,就可以实现属性值与表现层自动进行绑定,当然各结点需要绑定对应的UIitem脚本类,才可以进行属性的表现。

属性的持久化支持

正常的持久化,一般常常使用[Serializable]标签进行说明。如果使用这种方法的话,对JSON的支持是个问题。即需要有一些多于的信息被持久化,或者JSON的中属性也使用List的方法,但这样就不是很直观了,当然可以通过Editor扩展工具进行配置。本例中使用的是LitJson,它支持通过JsonData(类似字典结构)持久化为JSON一步步串行的进行持久化工作,中间加入这一层进行转换。

LitJson的官方地址如下,

https://litjson.net

其中进行类型的变换,比较繁琐,还没有找到比较好的方法。

		private void ConvertToObject<T>(T obj, JsonData data){
			List<IZProperty> props = ZPropertyMgr.GetProperties (obj);

			foreach (var p in props) {
				if (ZPropertyMgr.IsPropertable (p.Value.GetType ())) {
					//p.Value = data[p.PropertyID];
					ConvertToObject<T>((T)(p.Value), data[p.PropertyID]);
				}
				else {
					//ret [p.PropertyID] = p.Value;

					var data1 = data[p.PropertyID];

					if (data1.IsDouble)
						p.Value = (double)data1;
					else if (data1.IsInt)
						p.Value = (int)data1;
					else if (data1.IsBoolean)
						p.Value = (bool)data1;
					else if (data1.IsLong)
						p.Value = (long)data1;
					else if (data1.IsString)
						p.Value = (string)data1;
				}
			}

		}

当然也可以根据需求定义相应的持久化类,比如,使用Http等,或者复合形式,只要实现以下接口就可以

	public interface IZPropertyPrefs
	{
		void Save(object obj, string path);
			
		void Load<T>(T obj, string path);

		void LoadFromStr<T>(T obj, string strData);
	}

其中Json持久后的例子如下

 

{

"Person.weapon":{

"Weapon.power":88.2

},

"Person.rank":190,

"Person.blood":98

}

对于double类型,在LitJson中是不能使用整数的,比如上面写为88,就会出现转换错误。这个在LitJson中有说明。

 

数组(队列)支持

下面看一下,对于数组的支持

		[PropertyDescription("testlist", "a test list")]
                [PropertyUIItemRes("Test/Image")]
		public ZPropertyList<int> testList = new ZPropertyList<int>();PropertyUIItemRes("Test/Image")]
		public ZPropertyList<int> testList = new ZPropertyList<int>();

它其实理解为List<ZProperty<int>>,即每个元素也是一个属性,其自身也是一个属性,所有子属性继承父属性的值,比如其PropertyID是一样的。这样在做针对数组属性的表现层时需要了解其PropertyID的含义。如下图所示,有两项      

        person.testList.Add (900);

        person.testList.Add (100);

可以看到对应的UI出现两项。内容如何可以使用Layout等功能,这个不是本框架需要支持的,目前List属性只支持自定义UIItem。

ZProperty属性封装类简介

 

多态支持

主要是对接口属性的支持,定义之后可以后续组合不同的对象,代码使用方法如下:

	public interface ITestInterface
	{
		void TestFunc();
	}

	public class TestObj : ITestInterface{
		void ITestInterface.TestFunc(){
			Debug.Log ("Test Func");
		}
	}
		[PropertyDescription("ITestInterface", "a test interface")]
		public ZPropertyList<ITestInterface> testInterface = new ZPropertyList<ITestInterface>();

以上为定义。以下为进行赋值,也需要使用CreateObject方法进行创建。

		var testObj = ZPropertyMgr.CreateObject<TestObj> ();
		testObj.testData.Value = 201;
		person.testInterface.Value = testObj;

这样生成Json也会包含testobj的数据的

ZProperty属性封装类简介

也是可以支持UI自动化配置的,如下图所示。当然由于interface的多态性,这里的显示一般使用自定义UIItem,在UI类中再根据不同的实例,进行显示不同的内容,比如,不同的设备或者武器有不同的表现UI等。后续,也可以考虑这一自动化,比如,通过UIitem类Attribute用于类的修饰来完成。(TBD:后续需求再进行追加)

ZProperty属性封装类简介

基础模板View表现层

泛型的支持,提供一套基础属性模板封装?

ZECS框架支持

如何在Editor扩展中进行显示在属性面板?自定义接口的话,不好在属性面板中获取了。这里需要考虑对ZECS框架的支持。

using System;
using UnityEditor;

namespace Entitas.VisualDebugging.Unity.Editor {

    public class IntTypeDrawer : ITypeDrawer {

        public bool HandlesType(Type type) {
            return type == typeof(int);
        }

        public object DrawAndGetNewValue(Type memberType, string memberName, object value, object target) {
            return EditorGUILayout.IntField(memberName, (int)value);
        }
    }
}

可以通过以上的代码为每种类型自定义一个属性面板类。详细的代码,请参考:

https://github.com/bennychao/ZECS/tree/b90d5a1e761c18445d75cc8c349441f1fb485b06/TestZECS/Assets/Libs/ZECSer/Src/TypeDrawer

https://www.cnblogs.com/xiaofeixiang/p/4018066.html

 

缺点

1. 这样自定义的属性需要通过.Data进行访问数据源,不支持使用Set、Get访问器,C#不支持有类似的扩展,目前是没有找到方法。这时只能做一个属性类到数据类型的一个强制转换的方法。但在赋值时会生成临时对象,造成GC性能影响。

有关定义转换运算符的方法,其中要注意显式还是隐式转换。

https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/statements-expressions-operators/using-conversion-operators

代码如下:

 
	public class ZProperty<T>
	{
		private T data;
		public ZProperty (T data)
		{
		}

		public static implicit operator T(ZProperty<T> d)  // implicit digit to byte conversion operator
		{
			return d.data;  // implicit conversion
		}

		public static implicit operator ZProperty<T>(T d)  // implicit digit to byte conversion operator
		{
			return new ZProperty<T>(d);  // implicit conversion
		}
	}

可以看出是支持模板的。但对于第二个转换,即从Data类型转换为Property封装类,会生成临时的对象。这个还是通过Pool进行优化掉。

2. 使用反射进行属性的绑定与遍历,对性能有一定的影响,所有要保证不是在Update中调用。

使用Demo代码

		person = ZPropertyMgr.CreateObject<Person> ();
		person.blood.Value = 100;
		person.rank.Value = 2;

		Weapon sword = person.weapon.Value;
		sword.power.Value = 991.0f;

		ZUICommonTools.BindObject (person, ZUI.transform);

		Debug.Log ("perion's l " + person.weapon.Value.power.Value);

		//ZPropertyPrefs.Save((object)sword, "");
		ZPropertyPrefs.LoadFromStr(person, strData.text);

 

Attribute列表:

 

PropertyUIItemTypeAttribute:定义绑定UIItem的类型,主要用于在UI中需要动态创建的Item,即预先没有在UI中定义GameObject的数据属性项。如果,预先已经定义了(通过名称进行匹配到了UIItem对象)那么以预定义为准。参数Type为UIitem类型,Parent为父结点GameObject的ID,默认为当前UI GameObject结点。

BUG列表:

1. 对于List如果设置UIItem,并当没有定义GameObject结点时,创建了结点后,再绑定子结点就会出现问题,因为UIItem也会被继承到子结点上。

2. 可以直接支持一个支持Enum的Property,会自动变换为Enum与Int类型的支持。

 

相关标签: Property 反射