赞
踩
正常开发流程中,通常会遇到很多的题目开发,常见的有选择题、填空题、问答题等。当然这些功能的开发很简单,但是,如果没有一套通用的框架来进行管理,而是每一个或每一种题目都写上一套代码,那么后期的迭代和维护都会收到很多的影响,为了解决这个问题,闲暇之余,做了一个简单的题目框架,能够支持和拓展以上说到的所有题型。
首先我定义了一个接口IQuestion,后期所有的题目都继承自这个接口,接口内容如下
public interface IQuestion { /// <summary> /// quesItem列表 /// </summary> List<IQuesItem> questionItemList { get; set; } /// <summary> /// quesItem的数量 /// </summary> int quesItemCount { get; } /// <summary> /// 增加一个QuesItem /// </summary> /// <param name="item"></param> void AddQuesItem(IQuesItem item); /// <summary> /// 移除一个QuesItem /// </summary> /// <param name="item"></param> void RemoveQuesItem(IQuesItem item); /// <summary> /// 清空所有QuesItem /// </summary> /// <param name="item"></param> void ClearQuesItem(); /// <summary> /// 提交 /// </summary> /// <returns></returns> bool Submit(); /// <summary> /// 是否回答正确 /// </summary> /// <returns></returns> bool IsRight(); /// <summary> /// 根据索引获取QuesItem /// </summary> /// <param name="id"></param> /// <returns></returns> IQuesItem GetQuesItem(int id); }
那么在上面的IQuestion接口中引入了一个新接口IQuesItem。
什么是IQuesItem?
其实简而言之就是,拿选择题举例子,每一个选项A/B/C/D都是一个QuesItem,如果是填空题,那么每一个需要输入的空格就是一个QuesItem。
为什么引入IQuesItem这个接口?
因为考虑到后期每一个操作单元的展示、效果、操作方式都有可能不同,那么这个时候,所有的这些可能情况,非常适合做一个多态处理,那么后期每一个操作单元都是一个独立的个体,提高操作单元的灵活性
/// <summary> /// 操作单元 /// </summary> public interface IQuesItem { /// <summary> /// 目标结果 /// </summary> object targetAnswer { get;} /// <summary> /// 实际当前结果 /// </summary> object curAnswer { get; } /// <summary> /// 实际当前结果改变 /// </summary> /// <param name="value">改变之后</param> void OnChanged(object value); /// <summary> /// 是否回答正确 /// </summary> /// <returns></returns> bool IsRight(); /// <summary> /// 回答正确之后的反应 /// </summary> void OnRight(); /// <summary> /// 回答错误之后的反应 /// </summary> void OnError(); }
其实写到这里基础结构就已经确定了,后期题目对象继承IQuestion,所有的操作单元继承IQuesItem,然后分别在具体对象中进行多态。
但是到这时我们发现,由于接口无法实现具体的方法,为了提高开发速度,我在实际对象和题目接口之间又做了一次封装。
public abstract class Question : MonoBehaviour,IQuestion { private IQuestion iquestion=>this; List<IQuesItem> IQuestion.questionItemList { get; set; } public int quesItemCount => iquestion?.questionItemList == null ? 0 : iquestion.questionItemList.Count; protected virtual void Awake() { IQuesItem[] items = transform.GetComponentsInChildren<IQuesItem>(); if (items!=null&&items.Length>0) for (int i = 0; i < items.Length; i++)AddQuesItem(items[i]); } public IQuesItem GetQuesItem(int id) { if (id>=quesItemCount) return null; return iquestion.questionItemList[id]; } /// <summary> /// 提交答案 /// </summary> /// <returns>返回正误</returns> public virtual void OnSubmit() { bool isRight = iquestion.Submit(); SubmitResult(isRight); } protected abstract void SubmitResult(bool isRight); /// <summary> /// 添加一个QuesItem /// </summary> /// <param name="item"></param> public void AddQuesItem(IQuesItem item) { if (iquestion.questionItemList==null)iquestion.questionItemList=new List<IQuesItem>(); iquestion.questionItemList.Add(item); } /// <summary> /// 移除一个QuesItem /// </summary> /// <param name="item"></param> public void RemoveQuesItem(IQuesItem item) { if (quesItemCount==0) return; iquestion.questionItemList.Remove(item); } /// <summary> /// 清空所有QuesItem /// </summary> public void ClearQuesItem() { if (iquestion.questionItemList!=null)iquestion.questionItemList.Clear(); } /// <summary> /// 当前回答是否正确 /// </summary> /// <returns>返回正误</returns> public bool IsRight() { if (quesItemCount==0) return false; bool isRight = true; for (int i = 0; i < quesItemCount; i++) { if (!GetQuesItem(i).IsRight()) { isRight = false; break; } } return isRight; } #region 私有 bool IQuestion.Submit() { if (quesItemCount == 0) return false; IQuesItem curItem = null; bool isRight = true; for (int i = 0; i < quesItemCount; i++) { curItem = GetQuesItem(i); if (curItem.IsRight())curItem.OnRight(); else { isRight = false; curItem.OnError(); } } return isRight; } #endregion }
那么可以看到,Question对象继承自MonoBehaviour,和IQuestion接口,首先继承自MonoBehaviour是为了简化初始数据的配置,继承IQuestion接口则是为了实现题目的基础功能,并且,Question是一个抽象对象,目的是为了防止程序开发中直接使用Question并修改内部数据和逻辑,保证这个对象的统一性。
所以,之后所有的题目对象我们都可以继承自Question而不是IQuestion接口
写到其实就已经完事了,但是不让看看效果总感觉少点什么。
莫方! 下面就做一个简单的小测试,就拿选择题开刀吧。
那么我把选择题对象命名为ChooseQues,当然要继承Question了
/// <summary> /// 选择题 /// </summary> public class ChooseQues : Question { [SerializeField] private Text tipText; [SerializeField] private Button submitBtn; private void Start() { submitBtn.onClick.AddListener(OnSubmit); } protected override void SubmitResult(bool isRight) { tipText.text = isRight ? "回答正确" : "回答错误"; } }
怎么样?选择题对象只需要写这么一丢丢代码就能够实现,提交和提示功能了。。。。。
是不是还少点啥?
没错了,还少每个操作单元(选项)对象了,那么我把每个操作单元叫做ToggleItem,因为每个选项我都是用Toggle做的,当然你也可以用其他方式。
public class ToggleItem : MonoBehaviour,IQuesItem { [SerializeField] private bool answer; public object targetAnswer => answer; public object curAnswer => toggle.isOn; private Toggle toggle; private Text effectText; private void Start() { toggle = GetComponent<Toggle>(); toggle.onValueChanged.AddListener(isSelect=>OnChanged(isSelect)); effectText = transform.Find("EffectText")?.GetComponent<Text>(); } public void OnChanged(object value) { Debug.Log(transform.name+": 变为:"+(bool)value); } public bool IsRight() { return (bool) curAnswer == (bool) targetAnswer; } public void OnRight() { if (effectText)effectText.text = "选择正确"; } public void OnError() { if (effectText)effectText.text = "选择错误"; } }
这次真的写完了哈。。
可以看到这个对象有一个字段targetAnswer 对了 这个就是目标答案(可以再面板进行配置),接口中是object类型的,因为后期他的数据类型可能不固定,例如这里使用bool值作为目标答案即可。
还有一个字段叫curAnswer,这个字段是当前实际录入的答案,那么显然只需要我们在IsRight()中判断他们两个是否结果协同就Ok了。
effectText 是做了一个简单的效果(文字提示,这个操作块,是否正确?)当然,你也可以在里面做非常酷炫的效果,直接写在OnRight()【回答正确之后的效果】和OnError()【回答错误之后的效果】中就好了。
效果图如下:
最后附上源码链接,源码中还实现了填空题和九宫格拼图的题目类型。
源码
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。