前言:
去年还是小菜鸡的我写过在Unity中单选题和多选题的开发。现在进步一点点,这次可以直接编辑表格,在表格中增删改查数据即可,无需再对代码进行更改!
下载链接在文章末尾,需要的可以直接划到最后!
废话不多说,开始~
首先需要配置三个文件
- 读取表格的程序集:EPPlus
- 处理Json数据的程序集:Newtonsoft.Json
- 表格文件:question.xlsx
大概流程如下
创建StreamingAssets文件
-
首先我们在工程文件Assets文件下创建一个StreamingAssets(这里我们默认使用此路径为加载路径)
-
我们在此文件夹下创建一个表格。这里我使用的是.xlsx
下面是表结构,大家如果要修改的话,记得同时修改Question.cs哦!
创建Plugins文件
-
在Assets文件下创建一个名为Plugins文件夹
-
将刚才上面说了两个程序集EPPlus和Newtonsoft.Json放入此文件下
做完上面那些就可以导入我给大伙准备的脚本了
这里一共有三个脚本:
- ExcelMgr.cs
- Question.cs
- UIQuesPanel.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using OfficeOpenXml;
using System.IO;
using System.Data;
using System;
using Newtonsoft.Json.Linq;
using DialogEntity;
namespace vvb_ExcelMgr
{
public class ExcelMgr : MonoBehaviour
{
static ExcelPackage package;//表文件缓存
public static ExcelWorkbook dialogWorkbook;//表工作簿
public List<Question> questionList = new List<Question>();//题目缓存列表
public string quesPackPath="question";//题目文件地址,文件后缀默认为".xlsx"
private void Awake()
{
ReadExcel(quesPackPath, () =>
{
UIQuesPanel.quesList = GetQuesList(package);
questionList = UIQuesPanel.quesList;//这里为了在inspecter中能看到读取到达数据
});
}
/// <summary>
/// 打开表缓存
/// </summary>
public static ExcelWorkbook ReadExcel(string excelPath, Action action)
{
//Debug.Log(Application.streamingAssetsPath);
excelPath = Application.streamingAssetsPath + "/"+excelPath+".xlsx";
try
{
using (package = new ExcelPackage(new FileStream(excelPath, FileMode.Open)))
{
action?.Invoke();
return package.Workbook;
}
}
catch (NullReferenceException e)
{
Debug.LogError("空指针异常:" + e);
return null;
}
catch (IOException e)
{
Debug.LogError("文件打开异常:" + e);
return null;
}
catch (Exception e)
{
Debug.LogError("其他异常:" + e);
return null;
}
}
public List<Question> GetQuesList(ExcelPackage excelPackage)
{
List<Question> quesList = new List<Question>();
int dataStartRow = 0;
if (excelPackage.Workbook.Worksheets.Count < 0)
{
Debug.LogError("空表");
return null;
}
ExcelWorksheet sheet = excelPackage.Workbook.Worksheets[1];
for (int startRow = sheet.Dimension.Start.Row, endRow = sheet.Dimension.End.Row; startRow <= endRow; startRow++)
{
if (sheet.GetValue(startRow, 1).ToString().Equals("quesId"))//此行开始才是咱们真正需要的数据,当然在开发过程中这里是不需要的
{
dataStartRow = startRow;
break;
}
//Debug.Log(sheet.GetValue(startRow, 1).ToString());
//Debug.Log(sheet.GetValue(2, 1).ToString());
}
for (int startRow = dataStartRow+1, endRow = sheet.Dimension.End.Row; startRow <= endRow; startRow++)
{
JObject question = new JObject();//每一行实例化一个对象,用来存储到题目列表中
for (int startColumn = sheet.Dimension.Start.Column, endColumn = sheet.Dimension.End.Column; startColumn <= endColumn; startColumn++)
{
//这里要注意做一步特殊处理,因为当读取到选项内容那一行时,类型为数组,所以我们需要特殊处理一下
if (startColumn == 3)//在表中我们将选项内容放在C列,也就是第三项。或者各位也可以使用其他的方式判定
{
JArray options = new JArray();
string[] opsStr = sheet.GetValue(startRow, startColumn).ToString().Split(';');//这里先获取此处的字符串,然后再使用我们自定的符号切割以获得选项数组
options.Add(opsStr);
question.Add(sheet.GetValue(dataStartRow, startColumn).ToString(), options);
}
else
{
question.Add(sheet.GetValue(dataStartRow, startColumn).ToString(), sheet.GetValue(startRow, startColumn).ToString());
}
}
quesList.Add(JsonUtility.FromJson<Question>(question.ToString()));//转成Question对象添加入列表中
Debug.Log(question);
}
return quesList;
}
}
}
using System;
[Serializable]
public class Question
{
public int quesId;//当前题号
public string tittleStr;//题目内容
public string[] optionsStr;//选项内容
public int quesType;//题目类型
public string ans;//正确答案
public string analysis;//解析
public int nextQuesId;//下一题号
}
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class UIQuesPanel : MonoBehaviour
{
//为了方便,所有变量均拖拽赋值。
public static List<Question> quesList = new List<Question>();
public Text tittleTxt;//题目内容
public RectTransform optionsRoot;//选项生成根节点
public List<Toggle> optionTogList;//选项
public ToggleGroup toggleGroup;//根节点togglegroup组件
public GameObject togPrefab;//选项预制体
public Text analysisTxt;//解析文本
public Button lastBtn;//上一题按钮
public Button nextBtn;//下一题按钮
public Button confirmBtn;//确认作答按钮
public int curQuesIndex = -1;//当前题索引
private void Start()
{
_UIQuesPanelInit();
}
/// <summary>
/// 上一题
/// </summary>
public void OnLastBtnClick()
{
curQuesIndex--;
if (curQuesIndex <0)
{
Debug.LogWarning("前面没有了!");
curQuesIndex = 0;
return;
}
_UpdateQues();
}
/// <summary>
/// 下一题
/// </summary>
public void OnNextBtnClick()//这里加载下一题的方式也可以通过表中的字段nextQuesId,上一题同理(表中暂未设计)。
{
curQuesIndex++;
if (curQuesIndex>quesList.Count-1)
{
Debug.LogWarning("后面没有了!");
curQuesIndex = quesList.Count-1;
return;
}
_UpdateQues();
}
/// <summary>
/// 刷新题目
/// </summary>
private void _UpdateQues()
{
analysisTxt.gameObject.SetActive(false);
tittleTxt.text = (curQuesIndex + 1).ToString() + "." + quesList[curQuesIndex].tittleStr;
int optionsCount = optionsRoot.childCount;
optionTogList.Clear();
while (optionsCount > 0)
{
optionsCount--;
Destroy(optionsRoot.GetChild(optionsCount).gameObject);
}
for (int i = 0; i < quesList[curQuesIndex].optionsStr.Length; i++)//根据选项个数创建相应的toggle数量
{
optionTogList.Add(Instantiate(togPrefab, optionsRoot).GetComponent<Toggle>());
optionTogList[i].isOn = false;
if (quesList[curQuesIndex].quesType == 0)
{
optionTogList[i].group = toggleGroup;
}
optionTogList[i].GetComponentInChildren<Text>().text = quesList[curQuesIndex].optionsStr[i];
}
}
/// <summary>
/// 确认作答
/// </summary>
public void OnConfirmBtnClick()
{
if (curQuesIndex<0||curQuesIndex>quesList.Count-1)
{
Debug.LogError("数组越界");
return;
}
string selected=null;//用户选择
analysisTxt.text = selected;
analysisTxt.gameObject.SetActive(true);
switch (quesList[curQuesIndex].quesType)
{
case 0://单选
for (int i = 0; i < optionTogList.Count; i++)
{
if (optionTogList[i].isOn)
{
selected = optionTogList[i].GetComponentInChildren<Text>().text[0].ToString();//默认第一个字符代表此选项,并与答案进行对比。这里正误判定也可以自己定义
Debug.Log("选择的答案为:" + selected);
break;
}
}
break;
case 1://多选
for (int i = 0; i < optionTogList.Count; i++)
{
if (optionTogList[i].isOn)
{
selected += optionTogList[i].GetComponentInChildren<Text>().text[0].ToString();
}
}
break;
default:
break;
}
if (string.IsNullOrEmpty(selected))//没作答情况。具体可以自定义,比如你弹出一个面板提示什么的,强制答完才行。
{
selected = "未作答";
Debug.LogError("请先选择选项!");
}
if (selected.Equals(quesList[curQuesIndex].ans))//与正确答案做比较
{
Debug.Log("正确");
analysisTxt.text = "<color=green>回答正确</color> ";
}
else
{
Debug.Log("错误");
analysisTxt.text = "<color=red>回答错误</color> ";
}
analysisTxt.text += "你的答案:" + selected + " 正确答案:" + quesList[curQuesIndex].ans+"\n解析:" + quesList[curQuesIndex].analysis;
}
/// <summary>
/// 面板初始化
/// </summary>
private void _UIQuesPanelInit()
{
//这里大伙儿可以自己默认调用一次“下一题”按钮的事件来默认更新第一道题。我就不写了
analysisTxt.gameObject.SetActive(false);
if (optionsRoot.gameObject.GetComponent<ToggleGroup>())
{
toggleGroup = optionsRoot.gameObject.GetComponent<ToggleGroup>();
}
else
{
toggleGroup = optionsRoot.gameObject.AddComponent<ToggleGroup>();
}
toggleGroup.allowSwitchOff = true;
}
}
具体的过程我就不详述了,大伙儿看代码自行体会!
有何疑问欢迎留言讨论!需要源码的可以评论区留言或者私聊。
下面是源工程的下载地址:
链接:https://pan.baidu.com/s/1hrmtq9PGJlDg4Ya_YO6FeQ
提取码:grnh
================================ 2023/04/18新增 ===============================
我已经将资源上传至CSDN了,各位需要的可以自行下载,放心进,我设置了0积分,纯免费下载。
创作不易,我就一个要求,如果对你有帮助的话,请给我点个赞,谢谢。下载地址