【游戏开发题库】使用Unity制作Unity题库,支持题目录入和刷题(面试 | 笔试 | 自制题库 | 从基础到高级)
本文最终效果如下:
文章目录
- 一、前言
- 二、方案设计
- 三、界面设计
- 四、UI素材获取
- 五、Unity客户端部分
- 1、创建Unity工程
- 2、分辨率设置
- 3、制作界面预设
- 3.1、刷题界面:MainPanel.prefab
- 3.2、题目录入界面:QuestionInputPanel.prefab
- 3.3、提示语:FlyTips.prefab
- 4、Http请求封装
- 4.1、封装HttpHelper类
- 4.2、Http Get请求
- 4.3、Http Post请求
- 4.4、MonoBehaviour单例模式
- 4.5、请求题目所有类别
- 4.6、随机获取一个题目
- 4.7、录入新题目
- 5、资源管理器:ResMgr.cs
- 6、UI管理器:UIMgr.cs
- 7、刷题界面:MainPanel.cs
- 8、题目录入界面:QuestionInputPanel.cs
- 9、冒提示语:FlyTips.cs
- 六、Web服务端部分
- 1、服务器思维图
- 2、启动一个Web服务器
- 3、跨域访问问题的解决
- 4、读取所有题目
- 5、请求所有的题目类型
- 6、随机获取一道题目
- 7、录入一道题目
- 8、web_server.py完整代码
- 七、运行测试
- 1、启动服务器
- 2、客户端刷题
- 3、录入题目
- 八、工程源码
- 九、完毕
一、前言
嗨,大家好,我是新发。
我最近在找一些Unity
面试题,然后我看到,有一些网站和小程序的答题需要钱,我比较穷,于是我决定自己做一个题库录入和刷题的程序,自给自足,方便自己整理题目,也顺便教一下大家,看看我是如何使用Unity
制作Unity
题库的。
二、方案设计
我想做的功能很简单,就是客户端录入题目,按题目分类存到服务端,客户端可以选择不同类别的题目进行随机刷题。
画个图:
客户端部分我使用Unity
来做,服务端我准备使用python
来写,使用tornado
的Web
框架,题库数据库就使用简单的json
文本好啦(因为题库的题目数量也不会特别巨量,只是纯粹地把数据落地到磁盘而已,不需要真正的数据库)。
三、界面设计
使用axure
快速原型设计工具先简单设计一下界面,刷题界面如下:
试题录入界面如下:
四、UI素材获取
简单的UI素材资源我是在阿里巴巴矢量图库上找,地址:https://www.iconfont.cn/
比如搜索按钮,
找一个形状合适的,可以进行调色,我一般是调成白色,
因为Unity
中可以设置Color
,这样我们只需要一个白色按钮就可以在Unity
中创建不同颜色的按钮了。
弄点基础的美术资源,
五、Unity客户端部分
1、创建Unity工程
创建一个2D
模板的Unity
工程,工程名我定为UnityQuestionBank
,如下:
注意:
2D
模板会去下载一些2D
的工具,比如2D Sprite
,所以创建工程需要稍微等一下,
2、分辨率设置
我想做成横版的,Game视图分辨率设置为1280 * 720
,
创建一个Canvas
,
Canvas
组件的Render Mode
设置为Screen Space - Camera
,Render Camera
设置为主摄像机,
Canvas Scaler
组件的UI Scale Mode
设置为Scale With Screen Size
,Reference Resolution
设置为1280 * 720
,如下:
3、制作界面预设
根据界面设计,制作界面预设。
3.1、刷题界面:MainPanel.prefab
层级结构如下:
3.2、题目录入界面:QuestionInputPanel.prefab
层级结构如下:
3.3、提示语:FlyTips.prefab
再做一个提示语的预设,
层级结构如下,一个黑色背景图为父节点,文字为子节点,
提示语的背景需要根据文字自适应,
要实现上面的自适应新效果,只需在背景图挂Content Size Fitter
和Horizontal Layout Group
组件,
其中Content Size Fitter
的Horizontal Fit
设置为Preferred Size
,因为我们只需要做横向自适应,
Horizontal Layout Group
组件的Control Child Size
勾选Width
,这样文字子物体的宽度就可以控制背景图的宽度了,把Child Alignment
设置为Middle Center
,这样文字就居中对齐了,再设置一下Padding
的Left
和Right
,让背景图的左右两侧留一些空白,
顺手给提示语预设做个动画,
动画文件记得把Loop Time
勾选去掉,否则它会循环播放,
4、Http请求封装
Unity
提供了一个UnityWebRequest
类,可以很方便地执行Http
请求。
注:关于
UnityWebRequest
的使用教程,我之前写过相关文章:《长江后浪推前浪,UnityWebRequest替代WWW》、
《【游戏开发进阶】新发带你玩转Unity日志打印技巧(彩色日志 | 日志存储与上传 | 日志开关 | 日志双击溯源)》
4.1、封装HttpHelper类
我们封装一个HttpHelper
类,因为请求结果需要想要有异步回调的功能,我们可以使用协程,要执行协程有两种方式,一种是在MonoBehaviour
中调用StartCoroutine
,另一种就是自己通过IEnumerator
迭代器去MoveNext
。这里我选择第一种方法,HttpHelper
继承MonoBehaviour
,调用StartCoroutine
。
// HttpHelper.csusing System.Collections;
using UnityEngine;
using UnityEngine.Networking;
using System;public class HttpHelper : MonoBehaviour
{// Web服务器地址public const string WebUrl = "http://localhost:7891/";/// <summary>/// 请求题目所有类别/// </summary>public void StartGetAllQuestionTypes(Action<string> cb){// StartCoroutine调用Get接口}/// <summary>/// 随机获取一个题目/// </summary>public void StartGetOneQuestion(string questionType, Action<string> cb){// StartCoroutine调用Get接口}/// <summary>/// 试题录入/// </summary>/// <param name="questionType">题目类别</param>/// <param name="question">题目</param>/// <param name="code">代码</param>/// <param name="answer">答案</param>/// <param name="cb">回调</param>public void StartPostAddQuestion(string questionType, string question, string code, string answer, Action<string> cb){// StartCoroutine调用Post接口}
}
接下来我们封装一下Http
的Get
接口和Post
接口。
4.2、Http Get请求
// HttpHelper.cs // Http Get接口
IEnumerator CoroutineHttpGet(string url, Action<string> cb)
{UnityWebRequest req = UnityWebRequest.Get(url);yield return req.SendWebRequest();if (!string.IsNullOrEmpty(req.error)){Debug.Log(req.error);yield break;}cb?.Invoke(req.downloadHandler.text);req.Dispose();
}
4.3、Http Post请求
// HttpHelper.cs // Http Post接口
IEnumerator CoroutineHttpPost(string url, WWWForm form, Action<string> cb)
{UnityWebRequest req = UnityWebRequest.Post(url, form);yield return req.SendWebRequest();if (!string.IsNullOrEmpty(req.error)){Debug.Log(req.error);cb?.Invoke("{'error_code': -1}");yield break;}cb?.Invoke(req.downloadHandler.text);req.Dispose();
}
4.4、MonoBehaviour单例模式
另外,我想让HttpHelper
全局只有一个实例,也就是单例模式,封装一个instance
属性,
// HttpHelper.cs// MonoBehaviour单例模式
private static HttpHelper s_instance;public static HttpHelper instance{get{if (null == s_instance){var go = new GameObject("HttpHelper");s_instance = go.AddComponent<HttpHelper>();}return s_instance;}}
4.5、请求题目所有类别
我们把请求题目所有类别的接口加上Get
调用,
// HttpHelper.cs/// <summary>
/// 获取所有问题的类型
/// </summary>
public void StartGetAllQuestionTypes(Action<string> cb)
{StartCoroutine(CoroutineHttpGet(WebUrl + "get_question_types", cb));
}
4.6、随机获取一个题目
随机获取一个题目,需要告知服务器问题的类别,我们只需在请求链接尾部加上参数即可,例:
http://localhost:7891/get_one_question?question_type=C#基础
不过这里需要小心,因为URL
只能使用英文字母、阿拉伯数字和某些标点符号,所以我们需要先对参数执行URL
编码,Unity
在UnityWebRequest
类中提供了URL
编码的接口给我们:
public static string EscapeURL(string s);
public static string EscapeURL(string s, Encoding e);
对应的,URL
解码接口:
public static string UnEscapeURL(string s);
public static string UnEscapeURL(string s, Encoding e);
最终,随机获取一个题目接口如下:
/// <summary>
/// 随机获取一个题目
/// </summary>
public void StartGetOneQuestion(string questionType, Action<string> cb)
{// 执行URL编码questionType = UnityWebRequest.EscapeURL(questionType);// 执行Get请求StartCoroutine(CoroutineHttpGet(WebUrl + "get_one_question?question_type=" + questionType, cb));
}
4.7、录入新题目
录入新题目,需要上传题目数据给服务器,我们要使用POST
请求,数据要封装在WWWForm
中,如下:
注:塞入
WWWForm
会自动处理成URL
编码,所以不需要我们自己进行URL
编码。
/// <summary>
/// 试题录入
/// </summary>
/// <param name="questionType">题目类别</param>
/// <param name="question">题目</param>
/// <param name="code">代码</param>
/// <param name="answer">答案</param>
/// <param name="cb">回调</param>
public void StartPostAddQuestion(string questionType, string question, string code, string answer, Action<string> cb)
{WWWForm form = new WWWForm();form.AddField("question_type", questionType);form.AddField("question", question);form.AddField("code", code);form.AddField("answer", answer);StartCoroutine(CoroutineHttpPost(WebUrl + "add_one_question", form, cb));
}
5、资源管理器:ResMgr.cs
接下来要做界面交互,在做界面交互之前,需要先能把界面显示出来,这里就涉及到界面资源的加载。
关于资源读取我之前写过相关文章:
《Unity游戏开发——新发教你做游戏(三):3种资源加载方式》
这里我就简单处理,通过Resources.Load
来读取资源。
界面预设文件放在Resources
目录中,如下:
然后我们封装一个资源管理器:ResMgr
,逻辑很简单,通过Resources.Load
加载资源,加载过的资源缓存到容器中,下次再调用则直接从缓存中取,
代码如下:
using UnityEngine;
using System.Collections.Generic;/// <summary>
/// 资源管理器
/// </summary>
public class ResMgr
{public GameObject GetRes(string resPath){if(m_prefabs.ContainsKey(resPath)){return m_prefabs[resPath];}var go = Resources.Load<GameObject>(resPath);m_prefabs[resPath] = go;return go;}private Dictionary<string, GameObject> m_prefabs = new Dictionary<string, GameObject>();private static ResMgr s_instance;public static ResMgr instance{get{if (null == s_instance)s_instance = new ResMgr();return s_instance;}}
}
6、UI管理器:UIMgr.cs
UI
需要实例化,统一挂在Canvas
节点下,所以我们这里再封装一个UI
管理器,
代码如下:
using UnityEngine;/// <summary>
/// UI管理器
/// </summary>
public class UIMgr
{public void Init(){m_canvasTrans = GameObject.Find("Canvas").transform;}public GameObject ShowUi(string resPath){var prefab = ResMgr.instance.GetRes(resPath);if (null == prefab) return null;var uiObj = Object.Instantiate(prefab);uiObj.transform.SetParent(m_canvasTrans, false);return uiObj;}private Transform m_canvasTrans;private static UIMgr s_instance;public static UIMgr instance{get{if (null == s_instance)s_instance = new UIMgr();return s_instance;}}
}
7、刷题界面:MainPanel.cs
创建MainPanel.cs
脚本,定义一些UI
对象成员,
// MainPanel.cs// 题目类别下拉框
public Dropdown questionTypeDropdown;
// 题目文本(含答案)
public Text questionText;
// 题目录入按钮
public Button inputQuestionBtn;
// 看答案按钮
public Button answerBtn;
// 下一题按钮
public Button nextBtn;
把MainPanel.cs
挂到MainPanel.prefab
预设上,赋值对应的UI
对象,
封装一个显示界面的接口,
public static void Show()
{var uiObj = UIMgr.instance.ShowUi("UIPrefabs/MainPanel");var panel = uiObj.GetComponent<MainPanel>();panel.OnShow();
}
在OnShow
中写UI
的交互逻辑,请求题目的所有类别:
questionText.text = "正则请求服务器,请稍等...";
questionTypeDropdown.ClearOptions();
HttpHelper.instance.StartGetAllQuestionTypes((result) =>
{Debug.Log(result);var jd = JsonMapper.ToObject(result);List<string> options = new List<string>();// C#基础排最前面options.Add("C#基础");foreach (var item in jd){var option = item.ToString();if ("C#基础" == option)continue;options.Add(option);}questionTypeDropdown.AddOptions(options);// ...
});
下一题按钮,
// 下一题按钮
nextBtn.onClick.AddListener(() =>
{ReqOneQuestion();
});// .../// <summary>
/// 请求下一题
/// </summary>
private void ReqOneQuestion()
{var questionType = questionTypeDropdown.options[questionTypeDropdown.value].text;HttpHelper.instance.StartGetOneQuestion(questionType, (result) =>{var jd = JsonMapper.ToObject(result);var errorCode = int.Parse(jd["error_code"].ToString());if (0 == errorCode){m_questionData = jd["data"];UpdateQuestionText(false);}else{questionText.text = "";}});
}/// <summary>
/// 更新题目文本(可含答案)
/// </summary>
/// <param name="withAnswer">是否含答案</param>
private void UpdateQuestionText(bool withAnswer)
{if(withAnswer){questionText.text = string.Format("题目:\n{0}\n{1}\n\n解答:\n{2}", m_questionData["question"].ToString(),m_questionData["code"].ToString(), m_questionData["answer"].ToString());}else{questionText.text = string.Format("题目:\n{0}", m_questionData["question"].ToString());}
}
看答案按钮,
// 看答案
answerBtn.onClick.AddListener(() =>
{UpdateQuestionText(true);
});
问题类别切换时,自动请求一道题,
// 问题类别切换
questionTypeDropdown.onValueChanged.AddListener((v) =>
{ReqOneQuestion();
});
题目录入按钮,
// 录入新题
inputQuestionBtn.onClick.AddListener(() =>
{QuestionInputPanel.Show(questionTypeDropdown.options, questionTypeDropdown.value);
});
这里要显示题目录入界面,所有们接下来写QuestionInputPanel
脚本。
8、题目录入界面:QuestionInputPanel.cs
创建QuestionInputPanel.cs
脚本,定义一些UI
对象成员,
// 题目类别下拉框
public Dropdown questionTypeDropdown;
// 题目输入框
public InputField questionInput;
// 代码输入框
public InputField codeInput;
// 答案输入框
public InputField answerInput;
// 提交按钮
public Button okBtn;
// 返回按钮
public Button quitBtn;
把QuestionInputPanel.cs
挂到QuestionInputPanel.prefab
预设上,赋值对应的UI
对象,
封装一个显示界面的接口,
public static void Show(List<Dropdown.OptionData> options, int initQuestionType)
{var uiObj = UIMgr.instance.ShowUi("UIPrefabs/QuestionInputPanel");var panel = uiObj.GetComponent<QuestionInputPanel>();panel.OnShow(options, initQuestionType);
}
在OnShow
中实现界面交互逻辑,代码比较交单,我这里就不赘述了,
void OnShow(List<Dropdown.OptionData> options, int initQuestionType)
{questionTypeDropdown.ClearOptions();questionTypeDropdown.AddOptions(options);questionTypeDropdown.value = initQuestionType;// 提交题目到服务器okBtn.onClick.AddListener(() =>{var questionType = questionTypeDropdown.options[questionTypeDropdown.value].text;var question = questionInput.text;var code = codeInput.text;var answer = answerInput.text;if(string.IsNullOrEmpty(question)){FlyTips.Show("请输入题目");return;}if(string.IsNullOrEmpty(answer)){FlyTips.Show("请输入答案");return;}HttpHelper.instance.StartPostAddQuestion(questionType, question,code, answer, (result) =>{var jd = JsonMapper.ToObject(result);var errorCode = int.Parse(jd["error_code"].ToString());if(0 == errorCode){FlyTips.Show("试题录入成功");questionInput.text = "";codeInput.text = "";answerInput.text = "";}else{FlyTips.Show("服务器出错,录入失败");}});});// 返回按钮quitBtn.onClick.AddListener(() => {Destroy(gameObject);});
}
上面我用到了一个Json
库:LitJson
,可以在GitHub
中下载,地址:https://hub.fastgit.org/LitJSON/litjson
我们下载下来后,把src目录中的LitJson
文件夹整个拷贝到我们Unity
工程中,如下
使用时记得引入命名空间:
using LitJson;
9、冒提示语:FlyTips.cs
创建FlyTips.cs
脚本,实现冒提示语的功能,代码如下,
using UnityEngine;
using UnityEngine.UI;/// <summary>
/// 冒提示语
/// </summary>
public class FlyTips : MonoBehaviour
{public aniTrigger;public Text tipsText;public static void Show(string txt){var uiObj = UIMgr.instance.ShowUi("UIPrefabs/FlyTips");var tips = uiObj.GetComponent<FlyTips>();tips.OnShow(txt);}private void OnShow(string txt){tipsText.text = txt;aniTrigger.aniEvent = (msg) => {if("finish" == msg){Object.Destroy(gameObject);}};}
}
这里我封装了一个动画事件触发器:AnimationTrigger
,
using UnityEngine;
using System;/// <summary>
/// 动画事件触发器
/// </summary>
public class AnimationTrigger : MonoBehaviour
{public Action<string> aniEvent;public void TriggerEvent(string msg){aniEvent?.Invoke(msg);}
}
因为提示语冒完后要销毁(你也可以使用对象池回收起来,下次复用),所以我们需要监听动画的结束,我们先给动画添加帧事件,
帧事件的响应函数选择TriggerEvent
,字符串参数填finish
,
这样帧事件就会触发AnimationTrigger
的TriggerEvent
函数调用,进而调用委托Action<string> aniEvent
,我们只需要在FlyTips
中设置这个委托即可实现动画帧事件触发逻辑了。
以上,基本就是客户端部分的内容了,接下来讲下服务端部分。
六、Web服务端部分
服务端部分是一个Web
服务器,我用python
来写,python
的Web
框架有webpy
、tornado
等等,我推荐使用tornado
,我之前写过一篇关于tornado
搭建Web
服务器的文章:《使用Python Tornado搭建web服务器》,感兴趣的同学可以看看。
tornado
官网:https://www.tornadoweb.org/en/stable/index.html#
tornado
安装:https://pypi.org/project/tornado/
1、服务器思维图
我们创建一个web_server.py
脚本,创建一个question_bank
目录用于存放题库,如下:
question_bank
目录文件如下:
2、启动一个Web服务器
使用tonado
启动一个Web
服务器非常的简单,下面就是一个最简单的Hello World
例子,
import tornado.ioloop
import tornado.webclass MainHandler(tornado.web.RequestHandler):def get(self):self.write("Hello, world")def make_app():return tornado.web.Application([(r"/", MainHandler),])if __name__ == "__main__":app = make_app()app.listen(8888)tornado.ioloop.IOLoop.current().start()
我们通过http://localhost:8888
即可访问Web
服务器了。
3、跨域访问问题的解决
如果你的Web
服务器搭建与你的客户端不同的局域网中,则访问时会出现跨域反问问题,解决办法是定义BaseHandler
,设置header
,然后所有的Handler
类都继承BaseHandler
,例:
import tornado.ioloop
import tornado.web
from tornado.web import RequestHandlerclass BaseHandler(tornado.web.RequestHandler):def set_default_headers(self):self.set_header("Access-Control-Allow-Origin", "*") # 这个地方可以写域名self.set_header("Access-Control-Allow-Headers", "x-requested-with")self.set_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS')def post(self):self.write('some post')def get(self):self.write('some get')def options(self):# no bodyself.set_status(204)self.finish()class MyHandler(BaseHandler):def get(self):self.write('hello world')def post(self):self.write('hello world')
4、读取所有题目
服务器启动时,先读取所有的题目到内存中(因为题目量不大,所以就全部一次性读进来,如果题目海量,则不建议这么做),
all_questions = {}# 读取所有的题目
def read_all_questions():global all_questionsfor root, dir, fs in os.walk('question_bank'):for f in fs:if f.endswith('.json'):fpath = os.path.join(root, f)fr = open(fpath, 'r', encoding='utf-8')txt = fr.read()if '' == txt:txt = '[]'fr.close()f = f.replace('.json', '')all_questions[f] = json.loads(txt)
5、请求所有的题目类型
请求所有题目的类别,需要注意的就是json.dumps
的ensure_ascii
参数要设置为False
,否则传回客户端时中文的编码会有问题,
# 请求所有的题目类型
class get_question_types(BaseHandler):def get(self):question_types = []for k in all_questions.keys():question_types.append(k)self.write(json.dumps(question_types, ensure_ascii=False))
6、随机获取一道题目
这里主要是通过self.get_argument
获取客户端Get
请求的参数,根据题目类别从题库容器中随机抽取一道题,最后以json
的格式返回给客户端,
# 随机获取一道题目
class get_one_question(BaseHandler):def get(self):question_type = self.get_argument('question_type', default = 'C#基础')if not question_type in all_questions.keys():self.write('{ "error_code" : 1 }')returnquestions = all_questions[question_type]if 0 == len(questions):self.write('{ "error_code" : 1 }')returnindex = random.randint(0, len(questions) - 1)question = questions[index]question_txt = json.dumps(question, ensure_ascii=False)self.write('{ "error_code" : 0, "data" : %s }'%question_txt)
7、录入一道题目
这里主要是通过self.get_argument
获取客户端Post
请求的参数,然后更新内存题库数据并回写到本地json
文件中,
# 录入一道题目
class add_one_question(BaseHandler):def post(self):global all_questionsquestion_info = {}question_type = unquote(self.get_argument('question_type'))question_info['question'] = unquote(self.get_argument('question'))question_info['code'] = unquote(self.get_argument('code'))question_info['answer'] = unquote(self.get_argument('answer'))all_questions[question_type].append(question_info)f = open('./question_bank/' + question_type + '.json', 'w', encoding='utf-8')f.write(json.dumps(all_questions[question_type], ensure_ascii=False, sort_keys=False, indent=2))f.close()self.write('{ "error_code" : 0 }')
8、web_server.py完整代码
最终服务器完整代码如下:
import os
import json
import random
from urllib.parse import unquote
import tornado.ioloop
import tornado.web
from tornado.web import RequestHandlerall_questions = {}class BaseHandler(tornado.web.RequestHandler):# 解决跨域访问问题def set_default_headers(self):self.set_header("Access-Control-Allow-Origin", "*") # 这个地方可以写域名self.set_header("Access-Control-Allow-Headers", "x-requested-with")self.set_header('Access-Control-Allow-Methods', 'POST, GET, OPTIONS')def post(self):self.write('some post')def get(self):self.write('some get')def options(self):# no bodyself.set_status(204)self.finish()# 请求所有的题目类型
class get_question_types(BaseHandler):def get(self):question_types = []for k in all_questions.keys():question_types.append(k)self.write(json.dumps(question_types, ensure_ascii=False))# 随机获取一道题目
class get_one_question(BaseHandler):def get(self):question_type = self.get_argument('question_type', default = 'C#基础')if not question_type in all_questions.keys():self.write('{ "error_code" : 1 }')returnquestions = all_questions[question_type]if 0 == len(questions):self.write('{ "error_code" : 1 }')returnindex = random.randint(0, len(questions) - 1)question = questions[index]question_txt = json.dumps(question, ensure_ascii=False)self.write('{ "error_code" : 0, "data" : %s }'%question_txt)# 录入一道题目
class add_one_question(BaseHandler):def post(self):global all_questionsquestion_info = {}question_type = unquote(self.get_argument('question_type'))question_info['question'] = unquote(self.get_argument('question'))question_info['code'] = unquote(self.get_argument('code'))question_info['answer'] = unquote(self.get_argument('answer'))all_questions[question_type].append(question_info)f = open('./question_bank/' + question_type + '.json', 'w', encoding='utf-8')f.write(json.dumps(all_questions[question_type], ensure_ascii=False, sort_keys=False, indent=2))f.close()self.write('{ "error_code" : 0 }')# 创建tornado web服务器对象
def make_app():return tornado.web.Application([(r"/get_question_types", get_question_types),(r"/get_one_question", get_one_question),(r"/add_one_question", add_one_question),])# 读取所有的题目
def read_all_questions():global all_questionsfor root, dir, fs in os.walk('question_bank'):for f in fs:if f.endswith('.json'):fpath = os.path.join(root, f)fr = open(fpath, 'r', encoding='utf-8')txt = fr.read()if '' == txt:txt = '[]'fr.close()f = f.replace('.json', '')all_questions[f] = json.loads(txt)print('read_all_questions ok')if __name__ == "__main__":read_all_questions()app = make_app()app.listen(7891)print('localhost:7891')tornado.ioloop.IOLoop.current().start()
七、运行测试
1、启动服务器
使用python
执行web_server.py
,如下:
2、客户端刷题
运行Unity
客户端,测试刷题,效果如下:
3、录入题目
测试录入题目,
接着我们就可以在题库中刷到新题啦,
录入的题库会存在json
文件中,如下
八、工程源码
本工程源码我已上传到CODE CHINA
,地址:https://codechina.csdn.net/linxinfa/UnityQuestionRank,感兴趣的同学可自行下载学习。
注,我使用的Unity
版本是Unity 2021.1.9f1c1 (64-bit)
,
九、完毕
好了,就写到这里吧,目前题库题量还比较少,有空了我再进行完善,如果有热心的朋友帮我录入题目那就感激不尽啦~
我是林新发:https://blog.csdn.net/linxinfa
原创不易,若转载请注明出处,感谢大家~
喜欢我的可以点赞、关注、收藏,如果有什么技术上的疑问,欢迎留言或私信,拜拜~
【游戏开发题库】使用Unity制作Unity题库,支持题目录入和刷题(面试 | 笔试 | 自制题库 | 从基础到高级)相关推荐
- u3d游戏开发视频潭州_Unity MMORPG游戏开发教程(一)——初识Unity
五邑隐侠,本名关健昌,10年游戏生涯,现隐居五邑.本系列文章以C#为介绍语言,基于Unity2017.4.x. 一.环境搭建 我使用的是Unity+VSCode,用的是mac系统,windows的自己 ...
- 【游戏开发实战】教你在Unity中实现模型消融化为灰烬飘散的效果(ShaderGraph | 消融 | 粒子系统 | 特效)
文章目录 一.前言 二.ShaderGraph环境准备 三.模型准备:原神角色模型 四.实现思路 1.效果一的实现思路 2.效果二的实现思路 五.ShaderGraph具体实现 1.效果一 1.1.创 ...
- 最近打算开始学游戏开发,故此先记录一些Unity的学习网站
1.Unity游戏制作入门教程 (视频) 授课讲师: 雨潭 老师 软件版本: Unity3D 4.2英文版 [点击链接] 教程程度: 初级 所需基础: C语言基础或其他编程基础 地址:http:/ ...
- 游戏开发学习记录01-关于在Unity开发的游戏中部署后端云的选择
目前我还是一名在校学生,而且现在还没有学习数据库方面的知识,所以目前我不具备后端搭建服务器和数据库的知识.所以在之前学习安卓开发过程中,了解到了现在有一种服务-后端云,可以不用费时的去完成后端的开发, ...
- 【Visual C++】游戏开发笔记之九 游戏地图制作(一)平面地图贴图
本系列文章由zhmxy555编写,转载请注明出处. http://blog.csdn.net/zhmxy555/article/details/7364697 作者:毛星云 邮箱: happyl ...
- 【Unity3D】学习笔记(第2记) 2D游戏开发基本技巧之背景制作
最近看了龚老师的u3d视频讲座游戏<Platform>7讲,是关于2D游戏开发的,现将一些个人学习笔记记录于此. 1 背景图导入 首先创建一个Cube,通过缩放调整成和背景图一样的宽高,然 ...
- BAT Java面试笔试33题:JavaList、Java Map等经典面试题!答案汇总!
JavaList面试题汇总 1.List集合:ArrayList.LinkedList.Vector等. 2.Vector是List接口下线程安全的集合. 3.List是有序的. 4.ArrayLis ...
- 前端面试笔试错题指南(二)
嗯,小白的进击之路,继续来补充了... 又看了一些坑,自己第一次疏忽做错的,还是用笔记下来,共同进步 恩,面试系列和排坑会在github更新哦,一起准备秋招的小伙伴路过可以star下,一起进步O(∩_ ...
- 前端面试笔试错题指南(四)
嗯,上次写blog已经是几周前的事情了,其实已经积攒了很多小问题需要记录和分享了.但是在8月底,VK我一次经历了了携程.拼多多.腾讯.网易等多轮面试轰炸,忙得不可开交,有喜有忧的同时,还是赶快记录了不 ...
- 中国国电计算机通信类考试题目,2016国家电网通信类笔试真题——考生回忆版...
2016国家电网通信类笔试真题--考生回忆版 来源: 北极星电气招聘网 阅读:次 发布时间:2016年11月30日 北极星电气招聘网:北极星为广大有意向参加国家电网招聘的考生,实时更新国家电网的招 ...
最新文章
- opencv jni Android 实例笔记
- HDU1862 EXCEL排序【排序】
- Java中可变长参数的使用及注意事项
- 如何使用批处理添加网络打印机
- 一体化服务器和oracle集群,4种Oracle DBaaS部署模式,你在使用哪一种?
- 财务造假10年!贾跃亭被罚2.41亿
- 《图书管理系统》软件需求说明书
- 如何破解无线网密码进行上网
- [网友LionD8的毕业论文]Windows2000 内核级进程隐藏、侦测技术
- 【经典算法实现 44】理解二维FFT快速傅里叶变换 及 IFFT快速傅里叶逆变换(迭代法 和 递归法)
- 跨越技术鸿沟:从TCPIP到NDN
- 批量合并多个PDF文件
- 我说,执着造就了成功
- iOS 中生成随机数的4种方法(rand、random、arc4random、arc4random_uniform)
- dht11温湿度传感器特点及使用介绍
- 全球及中国农业机械LED灯行业竞争力调查及销售规模研究报告2021年版
- 2015百度沸点榜单
- Word插入NoteExpress插件时“运行错误429:ActiveX部件不能创建对象“解决方案
- 历史最全DL相关书籍、课程、视频、论文、数据集、会议、框架和工具整理分享
- 免费杀毒软件推荐下载