有关属性使用的封装,包括功能,修饰、持久化、表现层绑定等。类图如下:
附加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的模块的。参考以下插件。
https://blog.csdn.net/u011597114/article/details/54291247
其实大部分Localization插件都是使用字符串进行标识文言Item的。
可视化绑定
这部分提供了如何与UI能进行自动化的绑定,动态更新等机制,同时对一些常用框架的支持,比如上接UGUI、下连UniRX、ZECS等。同时也支持创建动态UIItem,比如对一些列表的支持等。当然这里的可视化绑定也适用于3D表现。
需求实例(以下面率土为例,列举常用UI Item)
可以分解出如下类型的属性,以及对应的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结构如下,其中结点的名称如下格式。
调用Bind方法后,就可以实现属性值与表现层自动进行绑定,当然各结点需要绑定对应的UIitem脚本类,才可以进行属性的表现。
属性的持久化支持
正常的持久化,一般常常使用[Serializable]标签进行说明。如果使用这种方法的话,对JSON的支持是个问题。即需要有一些多于的信息被持久化,或者JSON的中属性也使用List的方法,但这样就不是很直观了,当然可以通过Editor扩展工具进行配置。本例中使用的是LitJson,它支持通过JsonData(类似字典结构)持久化为JSON一步步串行的进行持久化工作,中间加入这一层进行转换。
LitJson的官方地址如下,
其中进行类型的变换,比较繁琐,还没有找到比较好的方法。
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。
多态支持
主要是对接口属性的支持,定义之后可以后续组合不同的对象,代码使用方法如下:
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的数据的
也是可以支持UI自动化配置的,如下图所示。当然由于interface的多态性,这里的显示一般使用自定义UIItem,在UI类中再根据不同的实例,进行显示不同的内容,比如,不同的设备或者武器有不同的表现UI等。后续,也可以考虑这一自动化,比如,通过UIitem类Attribute用于类的修饰来完成。(TBD:后续需求再进行追加)
基础模板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://www.cnblogs.com/xiaofeixiang/p/4018066.html
缺点
1. 这样自定义的属性需要通过.Data进行访问数据源,不支持使用Set、Get访问器,C#不支持有类似的扩展,目前是没有找到方法。这时只能做一个属性类到数据类型的一个强制转换的方法。但在赋值时会生成临时对象,造成GC性能影响。
有关定义转换运算符的方法,其中要注意显式还是隐式转换。
代码如下:
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类型的支持。