本文最终效果如下:

文章目录

  • 一、前言
  • 二、方案设计
  • 三、界面设计
  • 四、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来写,使用tornadoWeb框架,题库数据库就使用简单的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 - CameraRender Camera设置为主摄像机,
Canvas Scaler组件的UI Scale Mode设置为Scale With Screen SizeReference Resolution设置为1280 * 720,如下:

3、制作界面预设

根据界面设计,制作界面预设。

3.1、刷题界面:MainPanel.prefab


层级结构如下:

3.2、题目录入界面:QuestionInputPanel.prefab


层级结构如下:

3.3、提示语:FlyTips.prefab

再做一个提示语的预设,

层级结构如下,一个黑色背景图为父节点,文字为子节点,

提示语的背景需要根据文字自适应,

要实现上面的自适应新效果,只需在背景图挂Content Size FitterHorizontal Layout Group组件,
其中Content Size FitterHorizontal Fit设置为Preferred Size,因为我们只需要做横向自适应,

Horizontal Layout Group组件的Control Child Size勾选Width,这样文字子物体的宽度就可以控制背景图的宽度了,把Child Alignment设置为Middle Center,这样文字就居中对齐了,再设置一下PaddingLeftRight,让背景图的左右两侧留一些空白,

顺手给提示语预设做个动画,

动画文件记得把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接口}
}

接下来我们封装一下HttpGet接口和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编码,UnityUnityWebRequest类中提供了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

这样帧事件就会触发AnimationTriggerTriggerEvent函数调用,进而调用委托Action<string> aniEvent,我们只需要在FlyTips中设置这个委托即可实现动画帧事件触发逻辑了。

以上,基本就是客户端部分的内容了,接下来讲下服务端部分。

六、Web服务端部分

服务端部分是一个Web服务器,我用python来写,pythonWeb框架有webpytornado等等,我推荐使用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.dumpsensure_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题库,支持题目录入和刷题(面试 | 笔试 | 自制题库 | 从基础到高级)相关推荐

  1. u3d游戏开发视频潭州_Unity MMORPG游戏开发教程(一)——初识Unity

    五邑隐侠,本名关健昌,10年游戏生涯,现隐居五邑.本系列文章以C#为介绍语言,基于Unity2017.4.x. 一.环境搭建 我使用的是Unity+VSCode,用的是mac系统,windows的自己 ...

  2. 【游戏开发实战】教你在Unity中实现模型消融化为灰烬飘散的效果(ShaderGraph | 消融 | 粒子系统 | 特效)

    文章目录 一.前言 二.ShaderGraph环境准备 三.模型准备:原神角色模型 四.实现思路 1.效果一的实现思路 2.效果二的实现思路 五.ShaderGraph具体实现 1.效果一 1.1.创 ...

  3. 最近打算开始学游戏开发,故此先记录一些Unity的学习网站

    1.Unity游戏制作入门教程   (视频) 授课讲师: 雨潭 老师 软件版本: Unity3D 4.2英文版 [点击链接] 教程程度: 初级 所需基础: C语言基础或其他编程基础 地址:http:/ ...

  4. 游戏开发学习记录01-关于在Unity开发的游戏中部署后端云的选择

    目前我还是一名在校学生,而且现在还没有学习数据库方面的知识,所以目前我不具备后端搭建服务器和数据库的知识.所以在之前学习安卓开发过程中,了解到了现在有一种服务-后端云,可以不用费时的去完成后端的开发, ...

  5. 【Visual C++】游戏开发笔记之九 游戏地图制作(一)平面地图贴图

    本系列文章由zhmxy555编写,转载请注明出处. http://blog.csdn.net/zhmxy555/article/details/7364697 作者:毛星云    邮箱: happyl ...

  6. 【Unity3D】学习笔记(第2记) 2D游戏开发基本技巧之背景制作

    最近看了龚老师的u3d视频讲座游戏<Platform>7讲,是关于2D游戏开发的,现将一些个人学习笔记记录于此. 1 背景图导入 首先创建一个Cube,通过缩放调整成和背景图一样的宽高,然 ...

  7. BAT Java面试笔试33题:JavaList、Java Map等经典面试题!答案汇总!

    JavaList面试题汇总 1.List集合:ArrayList.LinkedList.Vector等. 2.Vector是List接口下线程安全的集合. 3.List是有序的. 4.ArrayLis ...

  8. 前端面试笔试错题指南(二)

    嗯,小白的进击之路,继续来补充了... 又看了一些坑,自己第一次疏忽做错的,还是用笔记下来,共同进步 恩,面试系列和排坑会在github更新哦,一起准备秋招的小伙伴路过可以star下,一起进步O(∩_ ...

  9. 前端面试笔试错题指南(四)

    嗯,上次写blog已经是几周前的事情了,其实已经积攒了很多小问题需要记录和分享了.但是在8月底,VK我一次经历了了携程.拼多多.腾讯.网易等多轮面试轰炸,忙得不可开交,有喜有忧的同时,还是赶快记录了不 ...

  10. 中国国电计算机通信类考试题目,2016国家电网通信类笔试真题——考生回忆版...

    2016国家电网通信类笔试真题--考生回忆版 来源: 北极星电气招聘网 阅读:次   发布时间:2016年11月30日 北极星电气招聘网:北极星为广大有意向参加国家电网招聘的考生,实时更新国家电网的招 ...

最新文章

  1. opencv jni Android 实例笔记
  2. HDU1862 EXCEL排序【排序】
  3. Java中可变长参数的使用及注意事项
  4. 如何使用批处理添加网络打印机
  5. 一体化服务器和oracle集群,4种Oracle DBaaS部署模式,你在使用哪一种?
  6. 财务造假10年!贾跃亭被罚2.41亿
  7. 《图书管理系统》软件需求说明书
  8. 如何破解无线网密码进行上网
  9. [网友LionD8的毕业论文]Windows2000 内核级进程隐藏、侦测技术
  10. 【经典算法实现 44】理解二维FFT快速傅里叶变换 及 IFFT快速傅里叶逆变换(迭代法 和 递归法)
  11. 跨越技术鸿沟:从TCPIP到NDN
  12. 批量合并多个PDF文件
  13. 我说,执着造就了成功
  14. iOS 中生成随机数的4种方法(rand、random、arc4random、arc4random_uniform)
  15. dht11温湿度传感器特点及使用介绍
  16. 全球及中国农业机械LED灯行业竞争力调查及销售规模研究报告2021年版
  17. 2015百度沸点榜单
  18. Word插入NoteExpress插件时“运行错误429:ActiveX部件不能创建对象“解决方案
  19. 历史最全DL相关书籍、课程、视频、论文、数据集、会议、框架和工具整理分享
  20. 免费杀毒软件推荐下载

热门文章

  1. 这款手绘风格的在线制图软件超棒
  2. Linux中设置Java程序开机自动运行
  3. 基于flask的可视化动漫分析网站
  4. 世界电子书展:号称最大的电子书库
  5. 最简单的易班打卡脚本
  6. Android静默安装实现方案,仿360手机助手秒装和智能安装功能
  7. FPS游戏:实现GDI方框透视
  8. 下载并安装 J2SDK以及运行第一个java程序
  9. 深度学习笔记(一)——M-P模型(神经元模型)
  10. 桂林理工大学 就业指导 2021 创业项目计划书样本