使用嵌套的ScriptableObject及ReorderableList创建习题持久化数据

使用嵌套的ScriptableObject及ReorderableList创建习题持久化数据

效果展示

题集持久化数据:存储题目,可以直接在inspector面板上创建对应的问题子项

问题持久化数据

源码

ScriptableObject类

题集的持久化数据类

[CreateAssetMenu(fileName = "Config/Question/QuestionData",menuName = "题集数据")]
    [Serializable]
    public class ChoiceQuestionData: ScriptableObject
    {
        [SerializeField]
        public List<ChoiceQuestionItem> Questions=new List<ChoiceQuestionItem>();
        
                
        public ChoiceQuestionItem AddQuestion() {
            ChoiceQuestionItem node = ScriptableObject.CreateInstance(typeof(ChoiceQuestionItem)) as ChoiceQuestionItem ;
            Questions.Add(node);
            return node;
        }        
        
        public void Clear() {
            Questions.Clear();
        }
    }

问题的持久化数据

    [Serializable]
    [CreateAssetMenu(fileName = "Config/Question/QuestionItem",menuName = "问题")]
    public class ChoiceQuestionItem : ScriptableObject
    {
        [TextArea]
        public string Question;
        public List<string> Choices;
        public List<int> RightOptions;
    }
[Serializable]
[CreateAssetMenu(fileName = "Config/Question/QuestionItem",menuName = "问题")]
public class ChoiceQuestionItem : ScriptableObject
{
    [TextArea]
    public string Question;
    public List<string> Choices;
    public List<int> RightOptions;
}

题集的Editor脚本

 [CustomEditor(typeof(ChoiceQuestionData))]
    public class ChoiceQuestionDataEditor: UnityEditor.Editor
    {
        private ChoiceQuestionData _choiceQuestionData;
        private ReorderableList  _questionReorderableList;
        private SerializedProperty _questionProperty;
        
        private int _index;
        private string _questionName;
        public void OnEnable()
        {
            _choiceQuestionData = (ChoiceQuestionData)this.target;
            _questionProperty = serializedObject.FindProperty("Questions");
            _questionReorderableList = new ReorderableList(serializedObject, _questionProperty, true,true,true,true);
            
            //添加新元素时的回调函数,自定义新元素的值
            _questionReorderableList.onAddCallback = list =>
            {
                if (list.serializedProperty != null)
                {
                    var item = AddItem();
                    list.serializedProperty.arraySize++;
                    list.index = list.serializedProperty.arraySize - 1;
                    list.serializedProperty.GetArrayElementAtIndex(list.index)
                        .objectReferenceValue = item;
                }
                else
                {
                    ReorderableList.defaultBehaviours.DoAddButton(list);
                }

            };

            //删除元素时的回调函数
            _questionReorderableList.onRemoveCallback = list =>
            {
                RemoveItem(list.index);
                ReorderableList.defaultBehaviours.DoRemoveButton(list);
            };
        }

        public override void OnInspectorGUI()
        {
            //更新序列化对象的表示形式。
            serializedObject.Update();
            
            //绘制列表头部,如标题等
            _questionReorderableList.drawHeaderCallback = DrawHeaderCallback;
            //绘制列表内部元素,缺少时面板不会显示具体元素的可赋值项,只会以Element+序号代替
            _questionReorderableList.drawElementCallback = DrawElement;
            //ReorderableList自动绘制
            _questionReorderableList.DoLayoutList();

            #region 添加问题

            EditorGUILayout.BeginHorizontal();
            _questionName = EditorGUILayout.TextField("问题名称", _questionName);
            if (GUILayout.Button("添加问题", GUILayout.Height(15))) {
                AddItem();
            }
            EditorGUILayout.EndHorizontal();

            #endregion

            #region 删除问题

            EditorGUILayout.BeginHorizontal();
            _index = EditorGUILayout.IntField("索引", _index);
            if (GUILayout.Button("删除", GUILayout.Height(15)))
            {
                if (_index > -1f && _index < _choiceQuestionData.Questions.Count)
                {
                    AssetDatabase.RemoveObjectFromAsset(_choiceQuestionData.Questions[_index]);
                    _choiceQuestionData.Questions.RemoveAt(_index);
                    AssetDatabase.SaveAssets();
                }
            }
            EditorGUILayout.EndHorizontal();

            #endregion

            #region 清空问题

            if (GUILayout.Button("清空", GUILayout.Height(20),GUILayout.Width(100))) { 
                var items = AssetDatabase.LoadAllAssetsAtPath(AssetDatabase.GetAssetPath(_choiceQuestionData))
                    .Where(x=> x is ChoiceQuestionItem);
                
                foreach (var item in items)
                {
                    AssetDatabase.RemoveObjectFromAsset(item);
                }
                this._choiceQuestionData.Clear();
                
                AssetDatabase.SaveAssets();
            }
            #endregion

            //应用序列化对象的更改。
            serializedObject.ApplyModifiedProperties();
        }
        
        /// <summary>
        /// 绘制列表头部
        /// </summary>
        /// <param name="rect"></param>
        void DrawHeaderCallback(Rect rect)
        {
            GUI.Label(rect, "问题:");
            Rect subChildRect = new Rect(rect.x + 80, rect.y, 150, rect.height);
            GUI.Label(subChildRect, "【选项合集】");
        }
        
        /// <summary>
        /// 绘制列表内部元素
        /// </summary>
        void DrawElement(Rect rect, int index, bool isActive, bool isFocused)
        {
            SerializedProperty element = _questionProperty.GetArrayElementAtIndex(index);
            EditorGUI.PropertyField(rect, element);
        }

        
        ChoiceQuestionItem AddItem()
        {
            var question=this._choiceQuestionData.AddQuestion();
            question.name = _questionName??"QuestionItem";
            //嵌套ChoiceQuestionItem的ScriptableObject到ChoiceQuestionData的ScriptableObject对象,方便归一到一个上层文件
            AssetDatabase.AddObjectToAsset(question, target);
            AssetDatabase.SaveAssets();
            return question;
        }        
        
        void RemoveItem(int index)
        {
            AssetDatabase.RemoveObjectFromAsset(this._choiceQuestionData.Questions[index]);
            this._choiceQuestionData.Questions.RemoveAt(index);
            AssetDatabase.SaveAssets();
        }

参考

  1. Unity编辑器拓展之二:ReorderableList可重新排序的列表框(复杂使用)

  2. Unity 编辑器扩展总结 七:数组或list集合的显示方式

posted @ 2023-01-05 17:32  世纪末の魔术师  阅读(353)  评论(0编辑  收藏  举报