本文介绍在Editor下使用单例ScriptableObject

ScriptableSingleton GUI官方文档click here!

Unity FilePathAttribute官方文档click here!

前言

相信大家都用过ScriptableObject(以下简称so),这里不过多介绍它了,不过关于ScriptableSingleton,可能就用的不多了,上面有官方文档链接。ScriptableSingleton就是一个单例的so。最近在整理项目中开发的工具,准备集合成一个方便移植的工具库,为了更抽象、可移植性更强,有些工具就得抽象出一个配置文件出来,关于这类配置文件,各个项目可能又不一样,所以这类文件就不适合放在工具库内部。放在工程内、工具库以外是比较合适的做法。那么问题在于,工具库里的工具如何拿到这个配置文件呢?(当然如果配置文件不使用so,也无所谓啦)

正文

可行的方案思考出来两种:

1、定义一个FilePathAttribute(并不是指Unity2020提供的那个),在工具外部定义一个FilePathAttribute的字段指定路径,工具内部通过这个Attribute拿到对应的配置文件路径。
2、使用ScriptableSingleton

第一种方案这里就不演示了,这个方案其实是从GF框架从学习来的,可以参见GF框架的ResourceEditorConfigPathAttribute

第二种方案是Unity提供的方案,如何使用具体参考上面的官方文档。这里介绍下使用时候的坑!!!

以下测试环境Unity2019.4.2f1。

1、新建一个单例so

1
2
3
4
5
6
7
using UnityEditor;

public class TestScriptableObjectSingleton : ScriptableSingleton<TestScriptableObjectSingleton>
{
public string UserName;
public int Age;
}

2、新建一个测试读写上面so的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
using UnityEditor;

namespace Editor
{
public class TestLoadSo
{
[MenuItem("Tools/Read")]
public static void Read()
{
UnityEngine.Debug.LogError(TestScriptableObjectSingleton.instance.UserName);
UnityEngine.Debug.LogError(TestScriptableObjectSingleton.instance.Age);
}

[MenuItem("Tools/Write")]
public static void Write()
{
TestScriptableObjectSingleton.instance.UserName = "user_name";
TestScriptableObjectSingleton.instance.Age = 18;
}
}
}

3、测试gif图

从图上看很正常对吧?写入的数据也能正常的读出来。。。但是,重启Unity后,再执行以下Tool/Read,数据没了,因为Unity内部创建的单例so是不会帮我们保存的。

在访问instance的时候,如果instance为空,会调用一下CreateAndLoad。

然后会通过FilePathAttribute找到该类型定义的文件路径,然而,该FilePathAttribute是internal的,从目前了解来看,直到2020版本,FilePathAttribute才公开。

既然无法使用FilePathAttribute,如果我们自己创建一个该类型的so,那么只会报错存在多个,因为它内部压根就无法找到自定义的那个so,更别提instance能够引用到它了。

解决方案

解决方案就是自己写,毕竟项目现在上不了2020。。。

其实啰嗦这么半天,Editor下单例的so写起来就那么点代码,只不过啰嗦下Unity的坑吧。。。

直接上代码了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
using UnityEditor;
using UnityEngine;

public class ScriptableObjectSingleton<T> : ScriptableObject where T : ScriptableObject
{
private static T s_Instance;
public static T Instance
{
get
{
if (s_Instance == null)
{
string[] findAssets = AssetDatabase.FindAssets($"t:{typeof(T).Name}");
if (findAssets == null || findAssets.Length == 0)
{
Debug.LogError($"Please create ScriptableObject typeof {typeof(T)} first...");
}
else if (findAssets.Length > 1)
{
Debug.LogError($"ScriptableObject typeof {typeof(T)} exist multiple,please check they...");
}
else
{
s_Instance = AssetDatabase.LoadAssetAtPath<T>(AssetDatabase.GUIDToAssetPath(findAssets[0]));
}
}
return s_Instance;
}
}
}
1
2
3
4
5
6
7
8
using UnityEditor;
using UnityEngine;
[CreateAssetMenu(menuName = "ScriptableObjectSingletonTest")]
public class ScriptableObjectSingletonTest : ScriptableObjectSingleton<ScriptableObjectSingletonTest>
{
public string UserName;
public int Age;
}

本篇文章,如果有不懂,欢迎留言~click here!

以上知识分享,如有错误,欢迎指出,共同学习,共同进步。如果有不懂,欢迎留言评论!