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

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

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

   image.gif

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

namespaceSK.Framework{
/// <summary>/// 题型/// </summary>publicenumQuestionType    {
/// <summary>/// 判断题/// </summary>JUDGE,
/// <summary>/// 单选题/// </summary>SINGLE_CHOICE,
/// <summary>/// 多选题/// </summary>MULTIPLE_CHOICE,
/// <summary>/// 填空题/// </summary>COMPLETION,
/// <summary>/// 论述题/// </summary>ESSAY,
    }
}

image.gif

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

namespaceSK.Framework{
/// <summary>/// 问题基类/// </summary>publicclassQuestionBase    {
/// <summary>/// 题号/// </summary>publicintSequence;
/// <summary>/// 问题/// </summary>publicstringQuestion;
/// <summary>/// 题型/// </summary>publicQuestionTypeType;
/// <summary>/// 题解/// </summary>publicstringAnalysis;
    }
}

image.gif

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

usingSystem;
namespaceSK.Framework{
/// <summary>/// 判断题/// </summary>    [Serializable]
publicclassJudgeQuestion : QuestionBase    {
/// <summary>/// 积极选项/// </summary>publicstringPositive="正确";
/// <summary>/// 消极选项/// </summary>publicstringNegative="错误";
/// <summary>/// 答案/// </summary>publicboolAnswer;
    }
}

image.gif

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

usingSystem;
usingSystem.Collections.Generic;
namespaceSK.Framework{
/// <summary>/// 填空题/// </summary>    [Serializable]
publicclassCompletionQuestion : QuestionBase    {
/// <summary>/// 答案/// </summary>publicList<string>Answers=newList<string>(0);
    }
}

image.gif

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

usingSystem;
namespaceSK.Framework{
/// <summary>/// 论述题/// </summary>    [Serializable]
publicclassEssayQuestion : QuestionBase    {
/// <summary>/// 答案/// </summary>publicstringAnswer;
    }
}

image.gif

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

namespaceSK.Framework{
/// <summary>/// 选项类型/// </summary>publicenumChoiceType    {
/// <summary>/// 文本/// </summary>Text,
/// <summary>/// 图片/// </summary>Pic,
/// <summary>/// 文本+图片/// </summary>TextAndPic    }
}

image.gif

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

usingSystem;
usingSystem.Collections.Generic;
namespaceSK.Framework{
/// <summary>/// 单项选择题/// </summary>    [Serializable]
publicclassSingleChoiceQuestion : QuestionBase    {
/// <summary>/// 选项类型/// </summary>publicChoiceTypechoiceType;
/// <summary>/// 选项/// </summary>publicList<QuestionChoice>Choices=newList<QuestionChoice>(0);
/// <summary>/// 答案/// </summary>publicintAnswer;
    }
}

image.gif

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

usingSystem;
usingSystem.Collections.Generic;
namespaceSK.Framework{
/// <summary>/// 多项选择题/// </summary>    [Serializable]
publicclassMultipleChoiceQuestion : QuestionBase    {
/// <summary>/// 选项类型/// </summary>publicChoiceTypechoiceType;
/// <summary>/// 选项/// </summary>publicList<QuestionChoice>Choices=newList<QuestionChoice>(0);
/// <summary>/// 答案/// </summary>publicList<int>Answers=newList<int>(0);
    }
}

image.gif

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

namespaceSK.Framework{
/// <summary>/// 英文字母表/// </summary>publicstaticclassAlphabet    {
publicstaticchar[] Values=newchar[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',
        };
    }
}

image.gif

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

usingUnityEngine;
usingSystem.Collections.Generic;
#ifUNITY_EDITORusingUnityEditor;
#endifnamespaceSK.Framework{
    [CreateAssetMenu(fileName="New Questions Profile", menuName="Question Profile")]
publicsealedclassQuestionsProfile : ScriptableObject    {
/// <summary>/// 判断题列表/// </summary>publicList<JudgeQuestion>Judges=newList<JudgeQuestion>(0);
/// <summary>/// 单向选择题列表/// </summary>publicList<SingleChoiceQuestion>SingleChoices=newList<SingleChoiceQuestion>(0);
/// <summary>/// 多项选择题列表/// </summary>publicList<MultipleChoiceQuestion>MultipleChoices=newList<MultipleChoiceQuestion>(0);
/// <summary>/// 填空题列表/// </summary>publicList<CompletionQuestion>Completions=newList<CompletionQuestion>(0);
/// <summary>/// 论述题列表/// </summary>publicList<EssayQuestion>Essays=newList<EssayQuestion>();
publicTGet<T>(QuestionTypetype, intsequence) whereT : QuestionBase        {
switch (type)
            {
caseQuestionType.JUDGE: returnJudges.Find(m=>m.Sequence==sequence) asT;
caseQuestionType.SINGLE_CHOICE: returnSingleChoices.Find(m=>m.Sequence==sequence) asT;
caseQuestionType.MULTIPLE_CHOICE: returnMultipleChoices.Find(m=>m.Sequence==sequence) asT;
caseQuestionType.COMPLETION: returnCompletions.Find(m=>m.Sequence==sequence) asT;
caseQuestionType.ESSAY: returnEssays.Find(m=>m.Sequence==sequence) asT;
default: returnnull;
            }
        }
    }
#ifUNITY_EDITOR    [CustomEditor(typeof(QuestionsProfile))]
publicsealedclassQuestionsProfileInspector : Editor    {
privateQuestionsProfileprofile;
privateQuestionTypecurrentType;
privateColorbtnNormalColor=newColor(0.5f, 0.5f, 0.5f, 1f);
privatereadonlyGUIContentdeleteContent=newGUIContent("-", "delete");
privateVector2scroll=Vector2.zero;
privateDictionary<JudgeQuestion, bool>judgeFoldoutMap;
privateDictionary<SingleChoiceQuestion, bool>singleChoicesFoldoutMap;
privateDictionary<MultipleChoiceQuestion, bool>multipleChoicesFoldoutMap;
privateDictionary<CompletionQuestion, bool>completionFoldoutMap;
privateDictionary<EssayQuestion, bool>essayFoldoutMap;
privatevoidOnEnable()
        {
profile=targetasQuestionsProfile;
judgeFoldoutMap=newDictionary<JudgeQuestion, bool>();
singleChoicesFoldoutMap=newDictionary<SingleChoiceQuestion, bool>();
multipleChoicesFoldoutMap=newDictionary<MultipleChoiceQuestion, bool>();
completionFoldoutMap=newDictionary<CompletionQuestion, bool>();
essayFoldoutMap=newDictionary<EssayQuestion, bool>();
        }
publicoverridevoidOnInspectorGUI()
        {
OnTypeGUI();
OnMenuGUI();
OnDetailGUI();
if (GUI.changed)
            {
EditorUtility.SetDirty(profile);
serializedObject.ApplyModifiedProperties();
            }
        }
privatevoidOnTypeGUI()
        {
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();
        }
privatevoidOnMenuGUI()
        {
GUILayout.BeginHorizontal();
if (GUILayout.Button("展开", EditorStyles.miniButtonLeft))
            {
switch (currentType)
                {
caseQuestionType.JUDGE: profile.Judges.ForEach(m=>judgeFoldoutMap[m] =true); break;
caseQuestionType.SINGLE_CHOICE: profile.SingleChoices.ForEach(m=>singleChoicesFoldoutMap[m] =true); break;
caseQuestionType.MULTIPLE_CHOICE: profile.MultipleChoices.ForEach(m=>multipleChoicesFoldoutMap[m] =true); break;
caseQuestionType.COMPLETION: profile.Completions.ForEach(m=>completionFoldoutMap[m] =true); break;
caseQuestionType.ESSAY: profile.Essays.ForEach(m=>essayFoldoutMap[m] =true); break;
default: break;
                }
            }
if (GUILayout.Button("收缩", EditorStyles.miniButtonMid))
            {
switch (currentType)
                {
caseQuestionType.JUDGE: profile.Judges.ForEach(m=>judgeFoldoutMap[m] =false); break;
caseQuestionType.SINGLE_CHOICE: profile.SingleChoices.ForEach(m=>singleChoicesFoldoutMap[m] =false); break;
caseQuestionType.MULTIPLE_CHOICE: profile.MultipleChoices.ForEach(m=>multipleChoicesFoldoutMap[m] =false); break;
caseQuestionType.COMPLETION: profile.Completions.ForEach(m=>completionFoldoutMap[m] =false); break;
caseQuestionType.ESSAY: profile.Essays.ForEach(m=>essayFoldoutMap[m] =false); break;
default: break;
                }
            }
if (GUILayout.Button("添加", EditorStyles.miniButtonMid))
            {
Undo.RecordObject(profile, "Add New");
switch (currentType)
                {
caseQuestionType.JUDGE: profile.Judges.Add(newJudgeQuestion()); break;
caseQuestionType.SINGLE_CHOICE: profile.SingleChoices.Add(newSingleChoiceQuestion()); break;
caseQuestionType.MULTIPLE_CHOICE: profile.MultipleChoices.Add(newMultipleChoiceQuestion()); break;
caseQuestionType.COMPLETION: profile.Completions.Add(newCompletionQuestion()); break;
caseQuestionType.ESSAY: profile.Essays.Add(newEssayQuestion()); 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)
                    {
caseQuestionType.JUDGE: profile.Judges.Clear(); judgeFoldoutMap.Clear(); break;
caseQuestionType.SINGLE_CHOICE: profile.SingleChoices.Clear(); singleChoicesFoldoutMap.Clear(); break;
caseQuestionType.MULTIPLE_CHOICE: profile.MultipleChoices.Clear(); multipleChoicesFoldoutMap.Clear(); break;
caseQuestionType.COMPLETION: profile.Completions.Clear(); completionFoldoutMap.Clear(); break;
caseQuestionType.ESSAY: profile.Essays.Clear(); essayFoldoutMap.Clear(); break;
default: break;
                    }
                }
            }
GUILayout.EndHorizontal();
        }
privatevoidOnDetailGUI()
        {
scroll=GUILayout.BeginScrollView(scroll);
switch (currentType)
            {
#region判断题caseQuestionType.JUDGE:
for (inti=0; i<profile.Judges.Count; i++)
                    {
varcurrent=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));
varnewValue=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));
varnewQ=EditorGUILayout.TextArea(current.Question, newGUIStyle(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));
varnewP=EditorGUILayout.TextField(current.Positive);
if (newP!=current.Positive)
                            {
Undo.RecordObject(profile, "Positive");
current.Positive=newP;
                            }
varnewAnswer=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));
varnewN=EditorGUILayout.TextField(current.Negative);
if (newN!=current.Positive)
                            {
Undo.RecordObject(profile, "Negative");
current.Negative=newN;
                            }
varnewAns=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));
varnewA=EditorGUILayout.TextArea(current.Analysis, newGUIStyle(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单选题caseQuestionType.SINGLE_CHOICE:
for (inti=0; i<profile.SingleChoices.Count; i++)
                    {
varcurrent=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));
varnewS=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));
varnewQ=EditorGUILayout.TextArea(current.Question, newGUIStyle(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(newQuestionChoice("选项描述", null));
                            }
GUILayout.FlexibleSpace();
current.choiceType= (ChoiceType)EditorGUILayout.EnumPopup(current.choiceType, GUILayout.Width(80f));
GUILayout.Label("(类型)");
GUILayout.EndHorizontal();
GUILayout.EndHorizontal();
GUILayout.BeginVertical();
for (intk=0; k<current.Choices.Count; k++)
                            {
GUILayout.BeginHorizontal();
GUILayout.Space(50);
GUILayout.Label($"{Alphabet.Values[k]}.", GUILayout.Width(20));
switch (current.choiceType)
                                {
caseChoiceType.Text:
current.Choices[k].text=GUILayout.TextField(current.Choices[k].text);
break;
caseChoiceType.Pic:
current.Choices[k].pic=EditorGUILayout.ObjectField(current.Choices[k].pic, typeof(Sprite), false) asSprite;
break;
caseChoiceType.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)) asSprite;
break;
                                }
varnewValue=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));
varnewA=EditorGUILayout.TextArea(current.Analysis, newGUIStyle(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多选题caseQuestionType.MULTIPLE_CHOICE:
for (inti=0; i<profile.MultipleChoices.Count; i++)
                    {
varcurrent=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));
varnewS=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));
varnewQ=EditorGUILayout.TextArea(current.Question, newGUIStyle(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(newQuestionChoice("选项描述", null));
                            }
GUILayout.FlexibleSpace();
current.choiceType= (ChoiceType)EditorGUILayout.EnumPopup(current.choiceType, GUILayout.Width(80f));
GUILayout.Label("(类型)");
GUILayout.EndHorizontal();
GUILayout.EndHorizontal();
GUILayout.BeginVertical();
for (intk=0; k<current.Choices.Count; k++)
                            {
GUILayout.BeginHorizontal();
GUILayout.Space(50);
GUILayout.Label($"{Alphabet.Values[k]}.", GUILayout.Width(20));
switch (current.choiceType)
                                {
caseChoiceType.Text:
current.Choices[k].text=GUILayout.TextField(current.Choices[k].text);
break;
caseChoiceType.Pic:
current.Choices[k].pic=EditorGUILayout.ObjectField(current.Choices[k].pic, typeof(Sprite), false) asSprite;
break;
caseChoiceType.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)) asSprite;
break;
                                }
varnewValue=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);
elsecurrent.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));
varnewA=EditorGUILayout.TextArea(current.Analysis, newGUIStyle(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填空题caseQuestionType.COMPLETION:
for (inti=0; i<profile.Completions.Count; i++)
                    {
varcurrent=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));
varnewS=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));
varnewQ=EditorGUILayout.TextArea(current.Question, newGUIStyle(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 (intn=0; n<current.Answers.Count; n++)
                            {
GUILayout.BeginHorizontal();
GUILayout.Space(50);
GUILayout.Label($"({n + 1}).", GUILayout.Width(30));
varnewC=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));
varnewA=EditorGUILayout.TextArea(current.Analysis, newGUIStyle(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论述题caseQuestionType.ESSAY:
for (inti=0; i<profile.Essays.Count; i++)
                    {
varcurrent=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));
varnewS=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));
varnewQ=EditorGUILayout.TextArea(current.Question, newGUIStyle(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));
varnewA=EditorGUILayout.TextArea(current.Answer, newGUIStyle(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));
varnewV=EditorGUILayout.TextArea(current.Analysis, newGUIStyle(GUI.skin.textArea) { stretchWidth=false }, GUILayout.ExpandWidth(true));
if (newV!=current.Analysis)
                            {
Undo.RecordObject(profile, "Essay Analysis");
current.Analysis=newV;
                            }
GUILayout.EndHorizontal();
                        }
                    }
break;
#endregiondefault:
GUILayout.Label("Unknown Question Type.");
break;
            }
GUILayout.EndScrollView();
        }
    }
#endif}

image.gif

image.gif

目录
相关文章
|
4月前
|
API 开发工具 vr&ar
PicoVR Unity SDK⭐️一、SDK下载、项目设置与程序初始配置
PicoVR Unity SDK⭐️一、SDK下载、项目设置与程序初始配置
|
3月前
|
vr&ar 图形学 API
Unity与VR控制器交互全解:从基础配置到力反馈应用,多角度提升虚拟现实游戏的真实感与沉浸体验大揭秘
【8月更文挑战第31天】虚拟现实(VR)技术迅猛发展,Unity作为主流游戏开发引擎,支持多种VR硬件并提供丰富的API,尤其在VR控制器交互设计上具备高度灵活性。本文详细介绍了如何在Unity中配置VR支持、设置控制器、实现按钮交互及力反馈,结合碰撞检测和物理引擎提升真实感,助力开发者创造沉浸式体验。
198 0
|
6月前
|
存储 JSON 关系型数据库
【unity实战】制作unity数据保存和加载系统——大型游戏存储的最优解
【unity实战】制作unity数据保存和加载系统——大型游戏存储的最优解
196 2
|
7月前
|
JSON Java 数据处理
Unity 数据读取|(二)多种方式读取文本文件
Unity 数据读取|(二)多种方式读取文本文件
|
6月前
|
存储 JSON 图形学
【unity实战】制作unity数据保存和加载系统——小型游戏存储的最优解
【unity实战】制作unity数据保存和加载系统——小型游戏存储的最优解
203 0
|
6月前
|
图形学
【unity小技巧】unity读excel配置表操作,excel转txt文本,并读取txt文本内容,实例说明
【unity小技巧】unity读excel配置表操作,excel转txt文本,并读取txt文本内容,实例说明
235 0
|
7月前
|
数据采集 编解码 图形学
Android平台Unity下如何通过WebCamTexture采集摄像头数据并推送至RTMP服务器或轻量级RTSP服务
Android平台Unity下如何通过WebCamTexture采集摄像头数据并推送至RTMP服务器或轻量级RTSP服务
356 0
|
7月前
|
存储 安全 API
Unity 数据读取|(六)PlayerPrefs解析
Unity 数据读取|(六)PlayerPrefs解析
|
7月前
|
XML 存储 安全
Unity 数据读取|(五)XML文件解析(XmlDocument,XmlTextReader)
Unity 数据读取|(五)XML文件解析(XmlDocument,XmlTextReader)
|
7月前
|
存储 JSON C#
Unity 数据读取|(四)Json文件解析(Newtonsoft.Json ,Litjson,JsonUtility,SimpleJSON)
Unity 数据读取|(四)Json文件解析(Newtonsoft.Json ,Litjson,JsonUtility,SimpleJSON)