工具开发 - SettingManager框架实现原理

简介

SettingManager是一个官方提供,用于序列化用户偏好设置的框架。 它提供了一种基于反射的机制,可以将任意的字段序列化到本地,并且可以快捷转换成可供用户在Project Setting 或者 Preference 窗口进行可视化调节的参数 。

两种不同的序列化形式*

使用SettingManager来进行序列化时,本质上就是要将表示窗口状态的类实例序列化到一个本地存储的数据文件。这种数据文件有两种类型的划分。

• json文件,保存在ProjectSettings目录下,通常一个package对应一个,只供当前项目工程使用。

• asset文件,报错在C盘的user目录下,作为全局的设置,所有的工程都可以进行读取和写入。

这两者的序列化形式,都是采用键值对的方式来与类中的字段信息进行唯一的映射。

框架结构分析

SettingManager的代码结构可按照以下的类型进行划分,分别为管理类,序列化载体,属性类 :

管理类

Settings

对一个工具来说 ,全局只应该存在唯一的Settings实例,作为对序列化数据进行读取和写入的统一入口 :

js 复制代码
static class MySettingsManager  
    {  
        internal const string k_PackageName = "com.example.abc";  
  
        static Settings s_Instance;  
  
        internal static Settings instance  
        {  
            get  
            {  
                if (s_Instance == null)  
                    s_Instance = new Settings(k_PackageName);  
  
                return s_Instance;  
            }  
        }  
    }

ISettingsRepository

ISettingsRepository接口描述了序列化的实现细节,包括序列化的方式、路径、增删改查、读取和写入 。 在SettingManager中,已经预设了几个类实现 。

• UserSettingsRepository指定了使用EditorPref的方式进行序列化 ,并且可以跨多个项目共享,以.asset的方式 存储在C盘的user目录下。

• FileSettingsRepository以SettingsDictionary(字典)作为序列化的载体,通过将SettingsDictionary序列化为.Json文件来实现对状态的保存。它有两个派生类:PackageSettingsRepository和ProjectUserSettings,两者的区别在于,前者存储在ProjectSettings/Packages目录下,后者存储在UserSettings/Packages目录下。

IUserSetting

通过操作Settings实例,可以立即将工具的特定字段序列化到本地。但是通常的情况下,我们需要一种延迟的行为,仅在需要时才将其序列化到本地。于是我们可以将特定的字段用IUserSetting进行一层包裹 :

属性类

在SettingManager中,主要提供了这三种标签类,分别是 【SettingProvider】,【UserSetting】,【UserSettingBlock】。这三个标签的作用都是用于标识,以便在打开ProjectSettings或者Preference面板时被框架通过反射机制收集,从而将运行时字段与序列化数据本体进行关联操作。

【SettingProvider】是一个静态回调函数的标签,当我们打开ProjectSettings或者Preference设置面板的时候,系统会收集所有带【SettingProvider】标签的静态函数 ,执行它们从而获取一个SettingProvider实例,对应于面板中的一个设置项。如下代码在面板中新增了一个MySetting的设置项。

js 复制代码
static class MySettingsProvider  
{  
    const string k_PreferencesPath = "Preferences/MySetting";  
     
    [SettingsProvider]  
    static SettingsProvider CreateSettingsProvider()  
    {  
         Debug.LogWarning("创建一个SettingProvider!");  
         var provider = new UserSettingsProvider(k_PreferencesPath,  
                                MySettingsManager.instance,  
                                new [] { typeof(MySettingsProvider).Assembly });  
  
         return provider;  
}

【UserSettings】用于标识哪些字段是要被呈现在设置面板中的,而且在框架的设计上,这些字段必须要用UserSettings进行包裹,从而支持回退等操作,例如:

js 复制代码
class MySettingsExamples : EditorWindow  
{  
    ......部分代码省略......  
    //将字段使用UserSetting进行注册,以便被SettingsProvider收集  
    //MySetting继承自UserSettings  
    [UserSetting("General Settings", "Days Without Incident")]  
    static MySetting<int> s_NumberOfDaysWithoutIncident = new MySetting<int>("general.daysWithoutIncident", 0, SettingsScope.User);  
  
    [UserSetting("General Settings", "Favorite Color")]  
    static MySetting<Color> s_FavoriteColor = new MySetting<Color>("general.favoriteColor", Color.magenta);  
  
    [UserSetting("General Settings", "Vector2 Field")]  
    static MySetting<Vector2> s_Vector2Value = new MySetting<Vector2>("general.vector2Value", new Vector2(2f, 4f));  
  
    [UserSetting("General Settings", "Editor Flags")]  
    static MySetting<StaticEditorFlags> s_EditorFlags = new MySetting<StaticEditorFlags>("general.editorFlags", StaticEditorFlags.BatchingStatic);  
  
    [UserSetting("General Settings", "FooClassTest")]  
    static MySetting<FooClass> s_FooClass = new MySetting<FooClass>("FClass",new FooClass() );  
    ......代码省略......  
 }

上述都是静态字段,对于实例字段而言,SettingProvider在收集它们之后,需要首先实例化一个类对象,以便在后续对它们进行与非静态字段等价的操作。

【UserSettingBlock】标示的静态回调支持自定义的UI显示,使用通用的编辑器开发语法 。

数据类****

上述的Json文件其实是由SettingDictionary直接序列化而来,每一个需要序列化的字段都被约束成以下的形式:

js 复制代码
[Serializable]  
struct SettingsKeyValuePair  
{  
    public string type;  //字段的AssemblyQualifiedName  
    public string key;   //字段的唯一标识符  
    public string value;  //字段的序列化值  
}

例如,对于一个Verctor2和Color类型的字段而言 ,它们的json形式为 :

js 复制代码
{  
    "m_Dictionary": {  
        "m_DictionaryValues": [  
             {  
                "type": "UnityEngine.Color, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",  
                "key": "MySettingsExamples.m_ColorField",  
                "value": "{\"m_Value\":{\"r\":0.8113207817077637,\"g\":0.04975082352757454,\"b\":0.04975082352757454,\"a\":0.0}}"  
            },  
            {  
                "type": "UnityEngine.Vector2, UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null",  
                "key": "general.vector2Value",  
                "value": "{\"m_Value\":{\"x\":2.4800000190734865,\"y\":4.0}}"  
            }  
         ]  
     }  
}

序列化的限制

目前框架使用EditorJsonUtility.ToJson()的方式将SettingDictionary序列化,但是有个明显的缺点就是,不支持集合类型的嵌套序列化。例如在一个List中嵌套一个数组 :

js 复制代码
[UserSettings]  
List<string[]>  nestedList = new List<string[]> {  
                                new string[2] {"AA","BB","CC"},  
                                new String[1] {"DD"},  
                                new String[3] {"EE","FF","GG"}  
                            }

上述的List即使被标注了[UserSettings],也会被忽略处理。如果要使得这种嵌套能被正确的序列化,就需要额外为数组类型包裹一层 :

js 复制代码
public class ArrWrap<T>  
{  
        public T[] array;         
        public ArrWrap(T[] arr)  
        {  
                array = arr;         
        }  
}  
  
[UserSetting]  
List<string[]>  nestedList = new List<string[]> {  
                               new ArrWrap<string>(new string[2] {"AA","BB"}),  
                               new ArrWrap<string>(new String[1] {"DD"}),  
                               new ArrWrap<string>(new String[3] {"EE","FF","GG"})  
}
相关推荐
AA陈超1 小时前
虚幻引擎5 GAS开发俯视角RPG游戏 P07-18.生成火球术
c++·游戏·ue5·游戏引擎·虚幻
_大学牲2 小时前
Flutter 勇闯2D像素游戏之路(三):人物与地图元素的交互
flutter·游戏·游戏开发
游戏技术分享4 小时前
【鸿蒙游戏技术分享 第71期】资质证明文件是否通过
游戏·华为·harmonyos
reddingtons4 小时前
PS 参考图像:线稿上色太慢?AI 3秒“喂”出精细厚涂
前端·人工智能·游戏·ui·aigc·游戏策划·游戏美术
BT-BOX21 小时前
普中开发板基于51单片机贪吃蛇游戏设计
单片机·游戏·51单片机
90后小陈老师1 天前
Unity教学 项目2 2D闯关游戏
游戏·unity·游戏引擎
毕设源码-邱学长1 天前
【开题答辩全过程】以 基于协同过滤算法的游戏推荐系统的设计与实现为例,包含答辩的问题和答案
游戏
2401_841495642 天前
【LeetCode刷题】跳跃游戏
数据结构·python·算法·leetcode·游戏·贪心算法·数组
呆呆敲代码的小Y2 天前
【Unity实战篇】| 游戏滑动框添加特殊效果,如实时高亮显示、曲线滑动等
游戏·unity·游戏引擎·实战·u3d·免费游戏·unity实战技巧
技术小甜甜2 天前
[Godot] 在 Godot 3.1 中配置 ADB 可执行文件的实用指南
游戏·adb·游戏引擎·godot