Unity 使用ScriptableObject创作问答系统数据配置

2022-08-29 16:06:25 浏览数 (1)

Unity中使用ScriptableObject允许我们创建配置文件并作为Unity中的Asset资产使用,无需像Json、XML等文件需要通过IO读取并反序列化成为我们需要的数据结构,其弊端是不可以像Json等配置文件可以在外部进行修改,在此不对其做详细介绍,下面使用ScriptableObject创作一个用于配置问答系统数据的资产,先看效果:

配置文件中包含五种题型:判断、单选、多选、填空、论述,选项可以删减并配置正确答案,首先定义一个描述题型的枚举QuestionType:

代码语言:javascript复制
namespace SK.Framework
{
    /// <summary>
    /// 题型
    /// </summary>
    public enum QuestionType
    {
        /// <summary>
        /// 判断题
        /// </summary>
        JUDGE,
        /// <summary>
        /// 单选题
        /// </summary>
        SINGLE_CHOICE,
        /// <summary>
        /// 多选题
        /// </summary>
        MULTIPLE_CHOICE,
        /// <summary>
        /// 填空题
        /// </summary>
        COMPLETION,
        /// <summary>
        /// 论述题
        /// </summary>
        ESSAY,
    }
}

创建基类QuestionBase,定义公共字段,包括题号、问题、题型、题解:

代码语言:javascript复制
namespace SK.Framework
{
    /// <summary>
    /// 问题基类
    /// </summary>
    public class QuestionBase
    {
        /// <summary>
        /// 题号
        /// </summary>
        public int Sequence;
        /// <summary>
        /// 问题
        /// </summary>
        public string Question;
        /// <summary>
        /// 题型
        /// </summary>
        public QuestionType Type;
        /// <summary>
        /// 题解
        /// </summary>
        public string Analysis;
    }
}

选项因题型不同而异,判断题只包含两个选项:正确与错误,答案为一个bool类型字段:

代码语言:javascript复制
using System;

namespace SK.Framework
{
    /// <summary>
    /// 判断题
    /// </summary>
    [Serializable]
    public class JudgeQuestion : QuestionBase
    {
        /// <summary>
        /// 积极选项
        /// </summary>
        public string Positive = "正确";
        /// <summary>
        /// 消极选项
        /// </summary>
        public string Negative = "错误";
        /// <summary>
        /// 答案
        /// </summary>
        public bool Answer;
    }
}

填空题需要一个string类型的列表存储所有答案:

代码语言:javascript复制
using System;
using System.Collections.Generic;

namespace SK.Framework
{
    /// <summary>
    /// 填空题
    /// </summary>
    [Serializable]
    public class CompletionQuestion : QuestionBase
    {
        /// <summary>
        /// 答案
        /// </summary>
        public List<string> Answers = new List<string>(0);
    }
}

论述题只需要一个string类型字段描述答案:

代码语言:javascript复制
using System;

namespace SK.Framework
{
    /// <summary>
    /// 论述题
    /// </summary>
    [Serializable]
    public class EssayQuestion : QuestionBase
    {
        /// <summary>
        /// 答案
        /// </summary>
        public string Answer;
    }
}

单选和多选题的选项分为三种不同类型:文字描述类型选项、图片类型选项、文字描述 图片类型选项,所以首先定义一个选项类型的枚举:

代码语言:javascript复制
namespace SK.Framework
{
    /// <summary>
    /// 选项类型
    /// </summary>
    public enum ChoiceType 
    {
        /// <summary>
        /// 文本
        /// </summary>
        Text,
        /// <summary>
        /// 图片
        /// </summary>
        Pic,
        /// <summary>
        /// 文本 图片
        /// </summary>
        TextAndPic
    }
}

单选题答案为一个int类型字段:

代码语言:javascript复制
using System;
using System.Collections.Generic;

namespace SK.Framework
{
    /// <summary>
    /// 单项选择题
    /// </summary>
    [Serializable]
    public class SingleChoiceQuestion : QuestionBase
    {
        /// <summary>
        /// 选项类型
        /// </summary>
        public ChoiceType choiceType;
        /// <summary>
        /// 选项
        /// </summary>
        public List<QuestionChoice> Choices = new List<QuestionChoice>(0);
        /// <summary>
        /// 答案
        /// </summary>
        public int Answer;
    }
}

多选题答案为一个int类型列表:

代码语言:javascript复制
using System;
using System.Collections.Generic;

namespace SK.Framework
{
    /// <summary>
    /// 多项选择题
    /// </summary>
    [Serializable]
    public class MultipleChoiceQuestion : QuestionBase
    {
        /// <summary>
        /// 选项类型
        /// </summary>
        public ChoiceType choiceType;
        /// <summary>
        /// 选项
        /// </summary>
        public List<QuestionChoice> Choices = new List<QuestionChoice>(0);
        /// <summary>
        /// 答案
        /// </summary>
        public List<int> Answers = new List<int>(0);
    }
}

需要注意各个问题类都需要添加Serializable特性,以确保其可序列化。单选和多选题的答案均为int类型字段,为了使int值对应A、B、C......Z字符,定义一个Alphabet字母表类, 例如Alphabet.Values[0]返回的便是字符A

代码语言:javascript复制
namespace SK.Framework
{
    /// <summary>
    /// 英文字母表
    /// </summary>
    public static class Alphabet
    {
        public static char[] Values = new char[26]
        {
            'A','B','C','D','E','F','G','H','I','G','K','L','M',
            'N','O','P','Q','R','S','T','U','V','W','X','Y','Z',
        };
    }
}

有了上述类后,创建QuestionsProfile类,继承ScriptableObject,使用CreateAssetMenu为创建该类型资产提供了创建菜单,并创建QuestionsProfileInspector继承Editor类进行了编辑器拓展:

代码语言:javascript复制
using UnityEngine;
using System.Collections.Generic;

#if UNITY_EDITOR
using UnityEditor;
#endif

namespace SK.Framework
{
    [CreateAssetMenu(fileName = "New Questions Profile", menuName = "Question Profile")]
    public sealed class QuestionsProfile : ScriptableObject
    {
        /// <summary>
        /// 判断题列表
        /// </summary>
        public List<JudgeQuestion> Judges = new List<JudgeQuestion>(0);
        /// <summary>
        /// 单向选择题列表
        /// </summary>
        public List<SingleChoiceQuestion> SingleChoices = new List<SingleChoiceQuestion>(0);
        /// <summary>
        /// 多项选择题列表
        /// </summary>
        public List<MultipleChoiceQuestion> MultipleChoices = new List<MultipleChoiceQuestion>(0);
        /// <summary>
        /// 填空题列表
        /// </summary>
        public List<CompletionQuestion> Completions = new List<CompletionQuestion>(0);
        /// <summary>
        /// 论述题列表
        /// </summary>
        public List<EssayQuestion> Essays = new List<EssayQuestion>();

        public T Get<T>(QuestionType type, int sequence) where T : QuestionBase
        {
            switch (type)
            {
                case QuestionType.JUDGE: return Judges.Find(m => m.Sequence == sequence) as T;
                case QuestionType.SINGLE_CHOICE: return SingleChoices.Find(m => m.Sequence == sequence) as T;
                case QuestionType.MULTIPLE_CHOICE: return MultipleChoices.Find(m => m.Sequence == sequence) as T;
                case QuestionType.COMPLETION: return Completions.Find(m => m.Sequence == sequence) as T;
                case QuestionType.ESSAY: return Essays.Find(m => m.Sequence == sequence) as T;
                default: return null;
            }
        }
    }

#if UNITY_EDITOR
    [CustomEditor(typeof(QuestionsProfile))]
    public sealed class QuestionsProfileInspector : Editor
    {
        private QuestionsProfile profile;
        private QuestionType currentType;
        private Color btnNormalColor = new Color(0.5f, 0.5f, 0.5f, 1f);
        private readonly GUIContent deleteContent = new GUIContent("-", "delete");
        private Vector2 scroll = Vector2.zero;
        private Dictionary<JudgeQuestion, bool> judgeFoldoutMap;
        private Dictionary<SingleChoiceQuestion, bool> singleChoicesFoldoutMap;
        private Dictionary<MultipleChoiceQuestion, bool> multipleChoicesFoldoutMap;
        private Dictionary<CompletionQuestion, bool> completionFoldoutMap;
        private Dictionary<EssayQuestion, bool> essayFoldoutMap;

        private void OnEnable()
        {
            profile = target as QuestionsProfile;
            judgeFoldoutMap = new Dictionary<JudgeQuestion, bool>();
            singleChoicesFoldoutMap = new Dictionary<SingleChoiceQuestion, bool>();
            multipleChoicesFoldoutMap = new Dictionary<MultipleChoiceQuestion, bool>();
            completionFoldoutMap = new Dictionary<CompletionQuestion, bool>();
            essayFoldoutMap = new Dictionary<EssayQuestion, bool>();
        }

        public override void OnInspectorGUI()
        {
            OnTypeGUI();
            OnMenuGUI();
            OnDetailGUI();

            if (GUI.changed)
            {
                EditorUtility.SetDirty(profile);
                serializedObject.ApplyModifiedProperties();
            }
        }
        private void OnTypeGUI()
        {
            EditorGUILayout.BeginHorizontal();
            {
                GUI.color = currentType == QuestionType.JUDGE ? Color.white : btnNormalColor;
                if (GUILayout.Button("判断题", EditorStyles.miniButtonLeft)) currentType = QuestionType.JUDGE;
                GUI.color = currentType == QuestionType.SINGLE_CHOICE ? Color.white : btnNormalColor;
                if (GUILayout.Button("单选题", EditorStyles.miniButtonMid)) currentType = QuestionType.SINGLE_CHOICE;
                GUI.color = currentType == QuestionType.MULTIPLE_CHOICE ? Color.white : btnNormalColor;
                if (GUILayout.Button("多选题", EditorStyles.miniButtonMid)) currentType = QuestionType.MULTIPLE_CHOICE;
                GUI.color = currentType == QuestionType.COMPLETION ? Color.white : btnNormalColor;
                if (GUILayout.Button("填空题", EditorStyles.miniButtonMid)) currentType = QuestionType.COMPLETION;
                GUI.color = currentType == QuestionType.ESSAY ? Color.white : btnNormalColor;
                if (GUILayout.Button("论述题", EditorStyles.miniButtonRight)) currentType = QuestionType.ESSAY;
                GUI.color = Color.white;
            }
            EditorGUILayout.EndHorizontal();
        }
        private void OnMenuGUI()
        {
            GUILayout.BeginHorizontal();
            if (GUILayout.Button("展开", EditorStyles.miniButtonLeft))
            {
                switch (currentType)
                {
                    case QuestionType.JUDGE: profile.Judges.ForEach(m => judgeFoldoutMap[m] = true); break;
                    case QuestionType.SINGLE_CHOICE: profile.SingleChoices.ForEach(m => singleChoicesFoldoutMap[m] = true); break;
                    case QuestionType.MULTIPLE_CHOICE: profile.MultipleChoices.ForEach(m => multipleChoicesFoldoutMap[m] = true); break;
                    case QuestionType.COMPLETION: profile.Completions.ForEach(m => completionFoldoutMap[m] = true); break;
                    case QuestionType.ESSAY: profile.Essays.ForEach(m => essayFoldoutMap[m] = true); break;
                    default: break;
                }
            }
            if (GUILayout.Button("收缩", EditorStyles.miniButtonMid))
            {
                switch (currentType)
                {
                    case QuestionType.JUDGE: profile.Judges.ForEach(m => judgeFoldoutMap[m] = false); break;
                    case QuestionType.SINGLE_CHOICE: profile.SingleChoices.ForEach(m => singleChoicesFoldoutMap[m] = false); break;
                    case QuestionType.MULTIPLE_CHOICE: profile.MultipleChoices.ForEach(m => multipleChoicesFoldoutMap[m] = false); break;
                    case QuestionType.COMPLETION: profile.Completions.ForEach(m => completionFoldoutMap[m] = false); break;
                    case QuestionType.ESSAY: profile.Essays.ForEach(m => essayFoldoutMap[m] = false); break;
                    default: break;
                }
            }
            if (GUILayout.Button("添加", EditorStyles.miniButtonMid))
            {
                Undo.RecordObject(profile, "Add New");
                switch (currentType)
                {
                    case QuestionType.JUDGE: profile.Judges.Add(new JudgeQuestion()); break;
                    case QuestionType.SINGLE_CHOICE: profile.SingleChoices.Add(new SingleChoiceQuestion()); break;
                    case QuestionType.MULTIPLE_CHOICE: profile.MultipleChoices.Add(new MultipleChoiceQuestion()); break;
                    case QuestionType.COMPLETION: profile.Completions.Add(new CompletionQuestion()); break;
                    case QuestionType.ESSAY: profile.Essays.Add(new EssayQuestion()); break;
                    default: break;
                }
            }
            if (GUILayout.Button("清空", EditorStyles.miniButtonRight))
            {
                Undo.RecordObject(profile, "Clear");
                if (EditorUtility.DisplayDialog("Prompt", "Are you sure clear the questions?", "Yes", "No"))
                {
                    switch (currentType)
                    {
                        case QuestionType.JUDGE: profile.Judges.Clear(); judgeFoldoutMap.Clear(); break;
                        case QuestionType.SINGLE_CHOICE: profile.SingleChoices.Clear(); singleChoicesFoldoutMap.Clear(); break;
                        case QuestionType.MULTIPLE_CHOICE: profile.MultipleChoices.Clear(); multipleChoicesFoldoutMap.Clear(); break;
                        case QuestionType.COMPLETION: profile.Completions.Clear(); completionFoldoutMap.Clear(); break;
                        case QuestionType.ESSAY: profile.Essays.Clear(); essayFoldoutMap.Clear(); break;
                        default: break;
                    }
                }
            }
            GUILayout.EndHorizontal();
        }

        private void OnDetailGUI()
        {
            scroll = GUILayout.BeginScrollView(scroll);
            switch (currentType)
            {
                #region 判断题
                case QuestionType.JUDGE:
                    for (int i = 0; i < profile.Judges.Count; i  )
                    {
                        var current = profile.Judges[i];
                        if (!judgeFoldoutMap.ContainsKey(current)) judgeFoldoutMap.Add(current, true);

                        GUILayout.BeginHorizontal("IN Title");
                        judgeFoldoutMap[current] = EditorGUILayout.Foldout(judgeFoldoutMap[current], $"第 {current.Sequence} 题", true);
                        if (GUILayout.Button("×", GUILayout.Width(20f)))
                        {
                            profile.Judges.Remove(current);
                            judgeFoldoutMap.Remove(current);
                            break;
                        }
                        GUILayout.EndHorizontal();
                        if (judgeFoldoutMap[current])
                        {
                            GUILayout.BeginHorizontal();
                            GUILayout.Label("题号:", GUILayout.Width(40));
                            var newValue = EditorGUILayout.IntField(current.Sequence, GUILayout.Width(30));
                            if (current.Sequence != newValue)
                            {
                                Undo.RecordObject(profile, "Judge Sequence");
                                current.Sequence = newValue;
                            }
                            GUILayout.EndHorizontal();

                            GUILayout.BeginHorizontal();
                            GUILayout.Label("问题:", GUILayout.Width(40));
                            var newQ = EditorGUILayout.TextArea(current.Question, new GUIStyle(GUI.skin.textArea) { stretchWidth = false }, GUILayout.ExpandWidth(true));
                            if (newQ != current.Question)
                            {
                                Undo.RecordObject(profile, "Judge Question");
                                current.Question = newQ;
                            }
                            GUILayout.EndHorizontal();

                            GUILayout.BeginHorizontal();
                            GUILayout.Label("正确:", GUILayout.Width(40));
                            var newP = EditorGUILayout.TextField(current.Positive);
                            if (newP != current.Positive)
                            {
                                Undo.RecordObject(profile, "Positive");
                                current.Positive = newP;
                            }
                            var newAnswer = EditorGUILayout.Toggle(current.Answer == true, GUILayout.Width(15));
                            if (newAnswer != (current.Answer == true))
                            {
                                Undo.RecordObject(profile, "Judge Answer");
                                current.Answer = true;
                            }
                            GUILayout.EndHorizontal();

                            GUILayout.BeginHorizontal();
                            GUILayout.Label("错误:", GUILayout.Width(40));
                            var newN = EditorGUILayout.TextField(current.Negative);
                            if (newN != current.Positive)
                            {
                                Undo.RecordObject(profile, "Negative");
                                current.Negative = newN;
                            }
                            var newAns = EditorGUILayout.Toggle(current.Answer == false, GUILayout.Width(15));
                            if (newAns != (current.Answer == false))
                            {
                                Undo.RecordObject(profile, "Judge Answer");
                                current.Answer = false;
                            }
                            GUILayout.EndHorizontal();

                            GUILayout.BeginHorizontal();
                            GUILayout.Label("题解:", GUILayout.Width(40));
                            var newA = EditorGUILayout.TextArea(current.Analysis, new GUIStyle(GUI.skin.textArea) { stretchWidth = false }, GUILayout.ExpandWidth(true));
                            if (newA != current.Analysis)
                            {
                                Undo.RecordObject(profile, "Judge Analysis");
                                current.Analysis = newA;
                            }
                            GUILayout.EndHorizontal();
                        }
                    }
                    break;
                #endregion
                #region 单选题
                case QuestionType.SINGLE_CHOICE:
                    for (int i = 0; i < profile.SingleChoices.Count; i  )
                    {
                        var current = profile.SingleChoices[i];
                        if (!singleChoicesFoldoutMap.ContainsKey(current)) singleChoicesFoldoutMap.Add(current, true);

                        GUILayout.BeginHorizontal("IN Title");
                        singleChoicesFoldoutMap[current] = EditorGUILayout.Foldout(singleChoicesFoldoutMap[current], $"第 {current.Sequence} 题", true);
                        if (GUILayout.Button("×", GUILayout.Width(20f)))
                        {
                            profile.SingleChoices.Remove(current);
                            singleChoicesFoldoutMap.Remove(current);
                            break;
                        }
                        GUILayout.EndHorizontal();

                        if (singleChoicesFoldoutMap[current])
                        {
                            GUILayout.BeginHorizontal();
                            GUILayout.Label("题号:", GUILayout.Width(40));
                            var newS = EditorGUILayout.IntField(current.Sequence, GUILayout.Width(30));
                            if (current.Sequence != newS)
                            {
                                Undo.RecordObject(profile, "SingleChoices Sequence");
                                current.Sequence = newS;
                            }
                            GUILayout.EndHorizontal();

                            GUILayout.BeginHorizontal();
                            GUILayout.Label("问题:", GUILayout.Width(40));
                            var newQ = EditorGUILayout.TextArea(current.Question, new GUIStyle(GUI.skin.textArea) { stretchWidth = false }, GUILayout.ExpandWidth(true));
                            if (newQ != current.Question)
                            {
                                Undo.RecordObject(profile, "SingleChoices Question");
                                current.Question = newQ;
                            }
                            GUILayout.EndHorizontal();

                            GUILayout.BeginHorizontal();
                            GUILayout.Label("选项:", GUILayout.Width(40));
                            GUILayout.BeginHorizontal();
                            if (GUILayout.Button("+", GUILayout.Width(25)))
                            {
                                Undo.RecordObject(profile, "SingleChoices Add");
                                current.Choices.Add(new QuestionChoice("选项描述", null));
                            }
                            GUILayout.FlexibleSpace();
                            current.choiceType = (ChoiceType)EditorGUILayout.EnumPopup(current.choiceType, GUILayout.Width(80f));
                            GUILayout.Label("(类型)");
                            GUILayout.EndHorizontal();
                            GUILayout.EndHorizontal();

                            GUILayout.BeginVertical();
                            for (int k = 0; k < current.Choices.Count; k  )
                            {
                                GUILayout.BeginHorizontal();
                                GUILayout.Space(50);
                                GUILayout.Label($"{Alphabet.Values[k]}.", GUILayout.Width(20));
                                switch (current.choiceType)
                                {
                                    case ChoiceType.Text:
                                        current.Choices[k].text = GUILayout.TextField(current.Choices[k].text);
                                        break;
                                    case ChoiceType.Pic:
                                        current.Choices[k].pic = EditorGUILayout.ObjectField(current.Choices[k].pic, typeof(Sprite), false) as Sprite;
                                        break;
                                    case ChoiceType.TextAndPic:
                                        current.Choices[k].text = GUILayout.TextField(current.Choices[k].text);
                                        current.Choices[k].pic = EditorGUILayout.ObjectField(current.Choices[k].pic, typeof(Sprite), false, GUILayout.Width(110f)) as Sprite;
                                        break;
                                }
                                var newValue = EditorGUILayout.Toggle(current.Answer == k, GUILayout.Width(15));
                                if (newValue)
                                {
                                    Undo.RecordObject(profile, "SingleChoices Answer");
                                    current.Answer = k;
                                }
                                if (GUILayout.Button(deleteContent, "MiniButton", GUILayout.Width(18)))
                                {
                                    Undo.RecordObject(profile, "Delete SingleChoice");
                                    current.Choices.RemoveAt(k);
                                    break;
                                }
                                GUILayout.EndHorizontal();
                            }
                            GUILayout.EndVertical();

                            GUILayout.BeginHorizontal();
                            GUILayout.Label("题解:", GUILayout.Width(40));
                            var newA = EditorGUILayout.TextArea(current.Analysis, new GUIStyle(GUI.skin.textArea) { stretchWidth = false }, GUILayout.ExpandWidth(true));
                            if (newA != current.Analysis)
                            {
                                Undo.RecordObject(profile, "SingleChoices Analysis");
                                current.Analysis = newA;
                            }
                            GUILayout.EndHorizontal();
                        }
                    }
                    break;
                #endregion
                #region 多选题
                case QuestionType.MULTIPLE_CHOICE:
                    for (int i = 0; i < profile.MultipleChoices.Count; i  )
                    {
                        var current = profile.MultipleChoices[i];
                        if (!multipleChoicesFoldoutMap.ContainsKey(current)) multipleChoicesFoldoutMap.Add(current, true);

                        GUILayout.BeginHorizontal("IN Title");
                        multipleChoicesFoldoutMap[current] = EditorGUILayout.Foldout(multipleChoicesFoldoutMap[current], $"第 {current.Sequence} 题", true);
                        if (GUILayout.Button("×", GUILayout.Width(20f)))
                        {
                            profile.MultipleChoices.Remove(current);
                            multipleChoicesFoldoutMap.Remove(current);
                            break;
                        }
                        GUILayout.EndHorizontal();

                        if (multipleChoicesFoldoutMap[current])
                        {
                            GUILayout.BeginHorizontal();
                            GUILayout.Label("题号:", GUILayout.Width(40));
                            var newS = EditorGUILayout.IntField(current.Sequence, GUILayout.Width(30));
                            if (newS != current.Sequence)
                            {
                                Undo.RecordObject(profile, "MultipleChoices Sequence");
                                current.Sequence = newS;
                            }
                            GUILayout.EndHorizontal();

                            GUILayout.BeginHorizontal();
                            GUILayout.Label("问题:", GUILayout.Width(40));
                            var newQ = EditorGUILayout.TextArea(current.Question, new GUIStyle(GUI.skin.textArea) { stretchWidth = false }, GUILayout.ExpandWidth(true));
                            if (newQ != current.Question)
                            {
                                Undo.RecordObject(profile, "MultipleChoices Question");
                                current.Question = newQ;
                            }
                            GUILayout.EndHorizontal();

                            GUILayout.BeginHorizontal();
                            GUILayout.Label("选项:", GUILayout.Width(40));
                            GUILayout.BeginHorizontal();
                            if (GUILayout.Button("+", GUILayout.Width(25)))
                            {
                                Undo.RecordObject(profile, "SingleChoices Add");
                                current.Choices.Add(new QuestionChoice("选项描述", null));
                            }
                            GUILayout.FlexibleSpace();
                            current.choiceType = (ChoiceType)EditorGUILayout.EnumPopup(current.choiceType, GUILayout.Width(80f));
                            GUILayout.Label("(类型)");
                            GUILayout.EndHorizontal();
                            GUILayout.EndHorizontal();

                            GUILayout.BeginVertical();
                            for (int k = 0; k < current.Choices.Count; k  )
                            {
                                GUILayout.BeginHorizontal();
                                GUILayout.Space(50);
                                GUILayout.Label($"{Alphabet.Values[k]}.", GUILayout.Width(20));
                                switch (current.choiceType)
                                {
                                    case ChoiceType.Text:
                                        current.Choices[k].text = GUILayout.TextField(current.Choices[k].text);
                                        break;
                                    case ChoiceType.Pic:
                                        current.Choices[k].pic = EditorGUILayout.ObjectField(current.Choices[k].pic, typeof(Sprite), false) as Sprite;
                                        break;
                                    case ChoiceType.TextAndPic:
                                        current.Choices[k].text = GUILayout.TextField(current.Choices[k].text);
                                        current.Choices[k].pic = EditorGUILayout.ObjectField(current.Choices[k].pic, typeof(Sprite), false, GUILayout.Width(110f)) as Sprite;
                                        break;
                                }
                                var newValue = EditorGUILayout.Toggle(current.Answers.Contains(k), GUILayout.Width(15));
                                if (newValue != current.Answers.Contains(k))
                                {
                                    Undo.RecordObject(profile, "MultipleChoices Answers");
                                    if (newValue)
                                        current.Answers.Add(k);
                                    else
                                        current.Answers.Remove(k);
                                }
                                if (GUILayout.Button(deleteContent, "MiniButton", GUILayout.Width(18)))
                                {
                                    Undo.RecordObject(profile, "Delete MultipleChoice");
                                    current.Choices.RemoveAt(k);
                                    break;
                                }
                                GUILayout.EndHorizontal();
                            }
                            GUILayout.EndVertical();

                            GUILayout.BeginHorizontal();
                            GUILayout.Label("题解:", GUILayout.Width(40));
                            var newA = EditorGUILayout.TextArea(current.Analysis, new GUIStyle(GUI.skin.textArea) { stretchWidth = false }, GUILayout.ExpandWidth(true));
                            if (newA != current.Analysis)
                            {
                                Undo.RecordObject(profile, "MultipleChoices Analysis");
                                current.Analysis = newA;
                            }
                            GUILayout.EndHorizontal();
                        }
                    }
                    break;
                #endregion
                #region 填空题
                case QuestionType.COMPLETION:
                    for (int i = 0; i < profile.Completions.Count; i  )
                    {
                        var current = profile.Completions[i];
                        if (!completionFoldoutMap.ContainsKey(current)) completionFoldoutMap.Add(current, true);

                        GUILayout.BeginHorizontal("IN Title");
                        completionFoldoutMap[current] = EditorGUILayout.Foldout(completionFoldoutMap[current], $"第 {current.Sequence} 题", true);
                        if (GUILayout.Button("×", GUILayout.Width(20f)))
                        {
                            profile.Completions.Remove(current);
                            completionFoldoutMap.Remove(current);
                            break;
                        }
                        GUILayout.EndHorizontal();

                        if (completionFoldoutMap[current])
                        {
                            GUILayout.BeginHorizontal();
                            GUILayout.Label("题号:", GUILayout.Width(40));
                            var newS = EditorGUILayout.IntField(current.Sequence, GUILayout.Width(30));
                            if (newS != current.Sequence)
                            {
                                Undo.RecordObject(profile, "Completion Sequence");
                                current.Sequence = newS;
                            }
                            GUILayout.EndHorizontal();

                            GUILayout.BeginHorizontal();
                            GUILayout.Label("问题:", GUILayout.Width(40));
                            var newQ = EditorGUILayout.TextArea(current.Question, new GUIStyle(GUI.skin.textArea) { stretchWidth = false }, GUILayout.ExpandWidth(true));
                            if (newQ != current.Question)
                            {
                                Undo.RecordObject(profile, "Completion Question");
                                current.Question = newQ;
                            }
                            GUILayout.EndHorizontal();

                            GUILayout.BeginHorizontal();
                            GUILayout.Label("答案:", GUILayout.Width(40));
                            if (GUILayout.Button("+", GUILayout.Width(25)))
                            {
                                Undo.RecordObject(profile, "CompletionAnswers Add");
                                current.Answers.Add(null);
                            }
                            GUILayout.EndHorizontal();

                            GUILayout.BeginVertical();
                            for (int n = 0; n < current.Answers.Count; n  )
                            {
                                GUILayout.BeginHorizontal();
                                GUILayout.Space(50);
                                GUILayout.Label($"({n   1}).", GUILayout.Width(30));
                                var newC = EditorGUILayout.TextField(current.Answers[n]);
                                if (current.Answers[n] != newC)
                                {
                                    Undo.RecordObject(profile, "CompletionAnswer");
                                    current.Answers[n] = newC;
                                }
                                if (GUILayout.Button(deleteContent, "MiniButton", GUILayout.Width(18)))
                                {
                                    Undo.RecordObject(profile, "CompletionAnswers Remove");
                                    current.Answers.RemoveAt(n);
                                    break;
                                }
                                GUILayout.EndHorizontal();
                            }
                            GUILayout.EndVertical();

                            GUILayout.BeginHorizontal();
                            GUILayout.Label("题解:", GUILayout.Width(40));
                            var newA = EditorGUILayout.TextArea(current.Analysis, new GUIStyle(GUI.skin.textArea) { stretchWidth = false }, GUILayout.ExpandWidth(true));
                            if (newA != current.Analysis)
                            {
                                Undo.RecordObject(profile, "Completion Analysis");
                                current.Analysis = newA;
                            }
                            GUILayout.EndHorizontal();
                        }
                    }
                    break;
                #endregion
                #region 论述题
                case QuestionType.ESSAY:
                    for (int i = 0; i < profile.Essays.Count; i  )
                    {
                        var current = profile.Essays[i];
                        if (!essayFoldoutMap.ContainsKey(current)) essayFoldoutMap.Add(current, true);

                        GUILayout.BeginHorizontal("IN Title");
                        essayFoldoutMap[current] = EditorGUILayout.Foldout(essayFoldoutMap[current], $"第 {current.Sequence} 题", true);
                        if (GUILayout.Button("×", GUILayout.Width(20f)))
                        {
                            profile.Essays.Remove(current);
                            essayFoldoutMap.Remove(current);
                            break;
                        }
                        GUILayout.EndHorizontal();

                        if (essayFoldoutMap[current])
                        {
                            GUILayout.BeginHorizontal();
                            GUILayout.Label("题号:", GUILayout.Width(40));
                            var newS = EditorGUILayout.IntField(current.Sequence, GUILayout.Width(30));
                            if (newS != current.Sequence)
                            {
                                Undo.RecordObject(profile, "Essay Sequence");
                                current.Sequence = newS;
                            }
                            GUILayout.EndHorizontal();

                            GUILayout.BeginHorizontal();
                            GUILayout.Label("问题:", GUILayout.Width(40));
                            var newQ = EditorGUILayout.TextArea(current.Question, new GUIStyle(GUI.skin.textArea) { stretchWidth = false }, GUILayout.ExpandWidth(true));
                            if (newQ != current.Question)
                            {
                                Undo.RecordObject(profile, "Essay Question");
                                current.Question = newQ;
                            }
                            GUILayout.EndHorizontal();

                            GUILayout.BeginHorizontal();
                            GUILayout.Label("答案:", GUILayout.Width(40));
                            var newA = EditorGUILayout.TextArea(current.Answer, new GUIStyle(GUI.skin.textArea) { stretchWidth = false }, GUILayout.ExpandWidth(true));
                            if (newA != current.Answer)
                            {
                                Undo.RecordObject(profile, "Essay Answer");
                                current.Answer = newA;
                            }
                            GUILayout.EndHorizontal();

                            GUILayout.BeginHorizontal();
                            GUILayout.Label("题解:", GUILayout.Width(40));
                            var newV = EditorGUILayout.TextArea(current.Analysis, new GUIStyle(GUI.skin.textArea) { stretchWidth = false }, GUILayout.ExpandWidth(true));
                            if (newV != current.Analysis)
                            {
                                Undo.RecordObject(profile, "Essay Analysis");
                                current.Analysis = newV;
                            }
                            GUILayout.EndHorizontal();
                        }
                    }
                    break;
                #endregion
                default:
                    GUILayout.Label("Unknown Question Type.");
                    break;
            }
            GUILayout.EndScrollView();
        }
    }
#endif
}

0 人点赞