346 lines
13 KiB
C#
346 lines
13 KiB
C#
using UnityEngine;
|
||
using UnityEditor;
|
||
using System.IO;
|
||
using System.Collections.Generic;
|
||
using SK.Framework;
|
||
using Newtonsoft.Json;
|
||
|
||
[System.Serializable]
|
||
public class QuestionJson
|
||
{
|
||
public string type;
|
||
public string question;
|
||
public List<string> options;
|
||
public string answer;
|
||
public List<string> answers;
|
||
public string explanation;
|
||
}
|
||
|
||
public static class QuestionProfileBatchImporter
|
||
{
|
||
[MenuItem("Tools/Import Questions/One Profile Per JSON (All Types)")]
|
||
public static void ImportEachJsonAsProfile()
|
||
{
|
||
string folder = EditorUtility.OpenFolderPanel("选择 JSON 文件夹", Application.dataPath, "");
|
||
if (string.IsNullOrEmpty(folder)) return;
|
||
|
||
string[] jsonFiles = Directory.GetFiles(folder, "*.json", SearchOption.TopDirectoryOnly);
|
||
if (jsonFiles.Length == 0)
|
||
{
|
||
EditorUtility.DisplayDialog("提示", "该文件夹中没有 JSON 文件", "OK");
|
||
return;
|
||
}
|
||
|
||
string saveFolder = EditorUtility.SaveFolderPanel("选择保存目录", Application.dataPath, "");
|
||
if (string.IsNullOrEmpty(saveFolder)) return;
|
||
|
||
string relativeSaveFolder = FileUtil.GetProjectRelativePath(saveFolder);
|
||
if (string.IsNullOrEmpty(relativeSaveFolder))
|
||
{
|
||
EditorUtility.DisplayDialog("错误", "选择的保存目录不在当前 Unity 项目内", "OK");
|
||
return;
|
||
}
|
||
|
||
int totalFiles = 0;
|
||
int singleCount = 0;
|
||
int multiCount = 0;
|
||
int judgeCount = 0;
|
||
|
||
foreach (string file in jsonFiles)
|
||
{
|
||
try
|
||
{
|
||
string json = File.ReadAllText(file);
|
||
|
||
// 直接解析为 QuestionJson 列表(数组格式)
|
||
List<QuestionJson> questions = JsonConvert.DeserializeObject<List<QuestionJson>>(json);
|
||
|
||
if (questions == null || questions.Count == 0)
|
||
{
|
||
Debug.LogWarning($"文件没有有效题目: {Path.GetFileName(file)}");
|
||
continue;
|
||
}
|
||
|
||
var profile = ScriptableObject.CreateInstance<QuestionsProfile>();
|
||
profile.SingleChoices = new List<SingleChoiceQuestion>();
|
||
profile.MultipleChoices = new List<MultipleChoiceQuestion>();
|
||
profile.Judges = new List<JudgeQuestion>();
|
||
|
||
int seqSingle = 1;
|
||
int seqMulti = 1;
|
||
int seqJudge = 1;
|
||
|
||
foreach (var q in questions)
|
||
{
|
||
string type = string.IsNullOrEmpty(q.type) ? "single" : q.type.ToLower();
|
||
|
||
if (type == "single")
|
||
{
|
||
var single = new SingleChoiceQuestion
|
||
{
|
||
Sequence = seqSingle++,
|
||
Question = q.question,
|
||
Analysis = q.explanation ?? "",
|
||
choiceType = ChoiceType.Text,
|
||
Choices = new List<QuestionChoice>()
|
||
};
|
||
|
||
if (q.options != null)
|
||
{
|
||
for (int i = 0; i < q.options.Count; i++)
|
||
single.Choices.Add(new QuestionChoice(q.options[i], null));
|
||
}
|
||
|
||
single.Answer = ParseAnswerIndex(q.answer, q.options?.Count ?? 0);
|
||
profile.SingleChoices.Add(single);
|
||
singleCount++;
|
||
}
|
||
else if (type == "multiple")
|
||
{
|
||
var multi = new MultipleChoiceQuestion
|
||
{
|
||
Sequence = seqMulti++,
|
||
Question = q.question,
|
||
Analysis = q.explanation ?? "",
|
||
choiceType = ChoiceType.Text,
|
||
Choices = new List<QuestionChoice>(),
|
||
Answers = new List<int>()
|
||
};
|
||
|
||
if (q.options != null)
|
||
{
|
||
for (int i = 0; i < q.options.Count; i++)
|
||
multi.Choices.Add(new QuestionChoice(q.options[i], null));
|
||
}
|
||
|
||
if (q.answers != null)
|
||
{
|
||
foreach (var ans in q.answers)
|
||
{
|
||
int idx = ParseAnswerIndex(ans, q.options?.Count ?? 0);
|
||
if (idx >= 0) multi.Answers.Add(idx);
|
||
}
|
||
}
|
||
|
||
profile.MultipleChoices.Add(multi);
|
||
multiCount++;
|
||
}
|
||
else if (type == "judgment")
|
||
{
|
||
var judge = new JudgeQuestion
|
||
{
|
||
Sequence = seqJudge++,
|
||
Question = q.question,
|
||
Analysis = q.explanation ?? "",
|
||
Positive = "正确",
|
||
Negative = "错误",
|
||
Answer = ParseJudgeAnswer(q.answer)
|
||
};
|
||
|
||
profile.Judges.Add(judge);
|
||
judgeCount++;
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"未知的题目类型: {q.type},题目: {q.question}");
|
||
}
|
||
}
|
||
|
||
string fileName = Path.GetFileNameWithoutExtension(file);
|
||
string savePath = Path.Combine(relativeSaveFolder, fileName + ".asset");
|
||
|
||
// 确保目录存在
|
||
string directory = Path.GetDirectoryName(savePath);
|
||
if (!Directory.Exists(directory))
|
||
Directory.CreateDirectory(directory);
|
||
|
||
AssetDatabase.CreateAsset(profile, savePath);
|
||
totalFiles++;
|
||
|
||
Debug.Log($"✅ 成功创建: {savePath} \n(单选题: {profile.SingleChoices.Count}, 多选题: {profile.MultipleChoices.Count}, 判断题: {profile.Judges.Count})");
|
||
}
|
||
catch (System.Exception e)
|
||
{
|
||
Debug.LogError($"❌ 处理文件失败: {Path.GetFileName(file)}\n错误: {e.Message}");
|
||
}
|
||
}
|
||
|
||
AssetDatabase.SaveAssets();
|
||
AssetDatabase.Refresh();
|
||
|
||
EditorUtility.DisplayDialog("导入完成",
|
||
$"处理了 {totalFiles}/{jsonFiles.Length} 个 JSON 文件\n" +
|
||
$"单选题:{singleCount} 道\n" +
|
||
$"多选题:{multiCount} 道\n" +
|
||
$"判断题:{judgeCount} 道\n" +
|
||
$"保存目录:{relativeSaveFolder}",
|
||
"OK");
|
||
}
|
||
|
||
[MenuItem("Tools/Import Questions/Single JSON File")]
|
||
public static void ImportSingleJsonFile()
|
||
{
|
||
string filePath = EditorUtility.OpenFilePanel("选择 JSON 文件", Application.dataPath, "json");
|
||
if (string.IsNullOrEmpty(filePath)) return;
|
||
|
||
string saveFolder = EditorUtility.SaveFolderPanel("选择保存目录", Application.dataPath, "");
|
||
if (string.IsNullOrEmpty(saveFolder)) return;
|
||
|
||
string relativeSaveFolder = FileUtil.GetProjectRelativePath(saveFolder);
|
||
if (string.IsNullOrEmpty(relativeSaveFolder))
|
||
{
|
||
EditorUtility.DisplayDialog("错误", "选择的保存目录不在当前 Unity 项目内", "OK");
|
||
return;
|
||
}
|
||
|
||
try
|
||
{
|
||
string json = File.ReadAllText(filePath);
|
||
List<QuestionJson> questions = JsonConvert.DeserializeObject<List<QuestionJson>>(json);
|
||
|
||
if (questions == null || questions.Count == 0)
|
||
{
|
||
EditorUtility.DisplayDialog("错误", "JSON 文件中没有有效的题目数据", "OK");
|
||
return;
|
||
}
|
||
|
||
var profile = ScriptableObject.CreateInstance<QuestionsProfile>();
|
||
profile.SingleChoices = new List<SingleChoiceQuestion>();
|
||
profile.MultipleChoices = new List<MultipleChoiceQuestion>();
|
||
profile.Judges = new List<JudgeQuestion>();
|
||
|
||
int seqSingle = 1;
|
||
int seqMulti = 1;
|
||
int seqJudge = 1;
|
||
int singleCount = 0;
|
||
int multiCount = 0;
|
||
int judgeCount = 0;
|
||
|
||
foreach (var q in questions)
|
||
{
|
||
string type = string.IsNullOrEmpty(q.type) ? "single" : q.type.ToLower();
|
||
|
||
if (type == "single")
|
||
{
|
||
var single = new SingleChoiceQuestion
|
||
{
|
||
Sequence = seqSingle++,
|
||
Question = q.question,
|
||
Analysis = q.explanation ?? "",
|
||
choiceType = ChoiceType.Text,
|
||
Choices = new List<QuestionChoice>()
|
||
};
|
||
|
||
if (q.options != null)
|
||
{
|
||
for (int i = 0; i < q.options.Count; i++)
|
||
single.Choices.Add(new QuestionChoice(q.options[i], null));
|
||
}
|
||
|
||
single.Answer = ParseAnswerIndex(q.answer, q.options?.Count ?? 0);
|
||
profile.SingleChoices.Add(single);
|
||
singleCount++;
|
||
}
|
||
else if (type == "multiple")
|
||
{
|
||
var multi = new MultipleChoiceQuestion
|
||
{
|
||
Sequence = seqMulti++,
|
||
Question = q.question,
|
||
Analysis = q.explanation ?? "",
|
||
choiceType = ChoiceType.Text,
|
||
Choices = new List<QuestionChoice>(),
|
||
Answers = new List<int>()
|
||
};
|
||
|
||
if (q.options != null)
|
||
{
|
||
for (int i = 0; i < q.options.Count; i++)
|
||
multi.Choices.Add(new QuestionChoice(q.options[i], null));
|
||
}
|
||
|
||
if (q.answers != null)
|
||
{
|
||
foreach (var ans in q.answers)
|
||
{
|
||
int idx = ParseAnswerIndex(ans, q.options?.Count ?? 0);
|
||
if (idx >= 0) multi.Answers.Add(idx);
|
||
}
|
||
}
|
||
|
||
profile.MultipleChoices.Add(multi);
|
||
multiCount++;
|
||
}
|
||
else if (type == "judgment")
|
||
{
|
||
var judge = new JudgeQuestion
|
||
{
|
||
Sequence = seqJudge++,
|
||
Question = q.question,
|
||
Analysis = q.explanation ?? "",
|
||
Positive = "正确",
|
||
Negative = "错误",
|
||
Answer = ParseJudgeAnswer(q.answer)
|
||
};
|
||
|
||
profile.Judges.Add(judge);
|
||
judgeCount++;
|
||
}
|
||
}
|
||
|
||
string fileName = Path.GetFileNameWithoutExtension(filePath);
|
||
string savePath = Path.Combine(relativeSaveFolder, fileName + ".asset");
|
||
|
||
AssetDatabase.CreateAsset(profile, savePath);
|
||
AssetDatabase.SaveAssets();
|
||
AssetDatabase.Refresh();
|
||
|
||
EditorUtility.DisplayDialog("导入完成",
|
||
$"成功导入: {fileName}\n" +
|
||
$"单选题: {singleCount} 道\n" +
|
||
$"多选题: {multiCount} 道\n" +
|
||
$"判断题: {judgeCount} 道",
|
||
"OK");
|
||
|
||
Debug.Log($"✅ 资源已创建: {savePath}");
|
||
}
|
||
catch (System.Exception e)
|
||
{
|
||
EditorUtility.DisplayDialog("错误", $"导入失败: {e.Message}", "OK");
|
||
Debug.LogError($"❌ 导入失败: {e}");
|
||
}
|
||
}
|
||
|
||
private static int ParseAnswerIndex(string answer, int optionCount)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(answer)) return -1;
|
||
answer = answer.Trim();
|
||
|
||
char c = char.ToUpperInvariant(answer[0]);
|
||
if (c >= 'A' && c < 'A' + optionCount)
|
||
return c - 'A';
|
||
|
||
if (int.TryParse(answer, out int num))
|
||
{
|
||
int idx = num - 1;
|
||
if (idx >= 0 && idx < optionCount) return idx;
|
||
if (num >= 0 && num < optionCount) return num;
|
||
}
|
||
|
||
return -1;
|
||
}
|
||
|
||
private static bool ParseJudgeAnswer(string answer)
|
||
{
|
||
if (string.IsNullOrWhiteSpace(answer)) return false;
|
||
|
||
answer = answer.Trim().ToLower();
|
||
|
||
if (answer == "true" || answer == "t" || answer == "正确" || answer == "对" || answer == "√" || answer == "1")
|
||
return true;
|
||
else if (answer == "false" || answer == "f" || answer == "错误" || answer == "错" || answer == "×" || answer == "0")
|
||
return false;
|
||
|
||
return false;
|
||
}
|
||
} |