A

Alexander D Huang’s Homepage

Alexander D Huang's Blog

UGF 源码阅读笔记:(1)安装

UnityGameFramework(UGF) 是由 Ellan Jiang 开发的一个 Unity 游戏开发框架。我决定采用它作为我最近为公司开发的一款 3D 扫雷游戏的开发框架,为此,我觉得有必要仔细阅读它的源码,并做好笔记。另外,我还建了一个仓库去写一些测试代码。 官网已经有教程教用户如何安装框架了。它推荐的安装方式是安装 Unity 插件包,其中核心部分的代码都打包成 DLL 形式了。虽然这种方法方便了用户使用,但我的目的是阅读和调试代码,我得拿到所有的代码。 下面是我的安装方法: 下载某一个版本(如我当前使用的是 v2019.11.09)的 UnityGameFramework,并将它拷贝到新建的 Unity 工程的 Assets 目录之中,如: 删除 UnityGameFramework/Libraries 文件夹下的 GameFramework.dll 和 GameFramework.xml 文件。 下载某一个版本的 GameFramework,并将它的源码拷贝到 Unity 工程的 Assets 目录之中。其存放位置任意,如我就将它放进了 UnityGameFramework 目录之中: 然后在 GameFramework 文件夹下新建一个 GameFramework.asmdef 文件: { "name": "GameFramework", "references": [], "includePlatforms": [], "excludePlatforms": [], "allowUnsafeCode" : true } 然后让 UnityGameFramework.Runtime.asmdef 依赖 GameFramework.asmdef,让 UnityGameFramework.Editor.asmdef 同时依赖 GameFramework.asmdef 和 UnityGameFramework.Runtime.asmdef 即可。 { "name": "UnityGameFramework.Runtime", "references": [ "GameFramework" ], "includePlatforms": [], "excludePlatforms": [] } { "name": "UnityGameFramework.Editor", "references": [ "UnityGameFramework.Runtime", "GameFramework" ], "includePlatforms": [ "Editor" ], "excludePlatforms": [] } 如此,整个框架就安装好了。接下来我们可以新建一个空场景 LaunchScene.unity 作为我们的游戏的启动场景,然后把框架提供的 GameFramework.prefab 拖入到场景中: 现在点击运行按钮就可以让框架代码跑起来了。

2019/11/15
articleCard.readMore

How to Implement a Tag Archive Page in Jekyll

Jekyll uses Liquid, which was created by Shopify and written in Ruby, as its template language. For making a tag archive page, we have to do a little bit of dirty work because of the lack of power of Liquid. But first, let me describe what a tag page we want to make here. On the top of the tag archive page, there is a list of labels, for example, Ignoring the style, we can see that each label has a tag name and the count of tags. Besides, each label has a link to the section of the related posts list. These labels are first sorted by the counts and then by the alphabetic. Talk is cheap. Show me the code. Linus OK, I show you the code now: {% assign tags_max = 0 %} {% for tag in site.tags %} {% if tag[1].size > tags_max %} {% assign tags_max = tag[1].size %} {% endif %} {% endfor %} {% assign tag_names_array = "" %} {% assign tag_counts = "" %} {% assign first_array_element = true %} {% for i in (1..tags_max) reversed %} {% assign tag_names = "" %} {% assign first_tag = true %} {% for tag in site.tags %} {% if tag[1].size == i %} {% if first_tag %} {% assign first_tag = false %} {% else %} {% assign tag_names = tag_names | append: "," %} {% endif %} {% assign tag_names = tag_names | append: tag[0] %} {% endif %} {% endfor %} {% if tag_names != "" %} {% assign tag_names = tag_names | split: "," | sort | join: "," %} {% if first_array_element %} {% assign first_array_element = false %} {% else %} {% assign tag_names_array = tag_names_array | append: "|" %} {% assign tag_counts = tag_counts | append: "|" %} {% endif %} {% assign tag_names_array = tag_names_array | append: tag_names %} {% assign tag_counts = tag_counts | append: i %} {% endif %} {% endfor %} {% assign tag_names_array = tag_names_array | split: "|" %} {% assign tag_counts = tag_counts | split: "|" %} <ul class="taxonomy-index"> {% for tag_names in tag_names_array %} {% assign tag_names_list = tag_names | split: "," %} {% assign tag_count = tag_counts[forloop.index0] %} {% for tag_name in tag_names_list %} <li> <a href="#{{ tag_name | slugify }}"> <strong>{{ tag_name }}</strong> <span class="taxonomy-count">{{ tag_count }}</span> </a> </li> {% endfor %} {% endfor %} </ul> It looks dirty, so let us walk through the code for better understanding. {% assign tags_max = 0 %} {% for tag in site.tags %} {% if tag[1].size > tags_max %} {% assign tags_max = tag[1].size %} {% endif %} {% endfor %} This segment is for calculating the maximum counts of tags. {% site.tags %} is a hash of posts indexed by the tag, for example, { 'tech' => [<Post A>, <Post B>], 'ruby' => [<Post B>] } Then we define two strings tag_names_array and tag_counts. What we want to have are two arrays, but by the lack of syntax for directly creating arrays in Liquid, we play a trick here. We use a long string to collect tag names; each element is delimited by a vertical line |, and each tag name in each element is delimited by a comma ,. For example, "tech,ruby|jekyll|html,css,javascript" Similarly, we also use a string to collect tag counts; each count is delimited by a vertical line |. Next, we define an auxiliary Boolean value first_array_element. If the first element is appended to the array, it will be set to false. It is used to check whether a delimiter | should be appended to the array. Next, we iterate from tags_max to 1, and inside this loop, we define two variables tag_names and first_tag. Their roles are similar with tag_names_array and tag_counts. Then we create an inner loop to find all tags whose count is matched with i: {% for tag in site.tags %} {% if tag[1].size == i %} {% if first_tag %} {% assign first_tag = false %} {% else %} {% assign tag_names = tag_names | append: "," %} {% endif %} {% assign tag_names = tag_names | append: tag[0] %} {% endif %} {% endfor %} After escaping this loop, if tag_names is not an empty string, that means we have collected tags whose counts are equal to i. So we append tag_names to tag_names_array, and at the same time, append i to tag_counts. {% if tag_names != "" %} {% assign tag_names = tag_names | split: "," | sort | join: "," %} {% if first_array_element %} {% assign first_array_element = false %} {% else %} {% assign tag_names_array = tag_names_array | append: "|" %} {% assign tag_counts = tag_counts | append: "|" %} {% endif %} {% assign tag_names_array = tag_names_array | append: tag_names %} {% assign tag_counts = tag_counts | append: i %} {% endif %} Now we can make two real arrays by calling split: {% assign tag_names_array = tag_names_array | split: "|" %} {% assign tag_counts = tag_counts | split: "|" %} Until now, all the things we do are prepare works. Let’s do a real job: showing the list of labels. <ul class="taxonomy-index"> {% for tag_names in tag_names_array %} {% assign tag_names_list = tag_names | split: "," %} {% assign tag_count = tag_counts[forloop.index0] %} {% for tag_name in tag_names_list %} <li> <a href="#{{ tag_name | slugify }}"> <strong>{{ tag_name }}</strong> <span class="taxonomy-count">{{ tag_count }}</span> </a> </li> {% endfor %} {% endfor %} </ul> At last, we need to show the post entries for each tag: {% for tag_names in tag_names_array %} {% assign tag_names_list = tag_names | split: "," %} {% for tag_name in tag_names_list %} <section id="{{ tag_name | slugify | downcase }}" class="taxonomy-section"> <h2 class="taxonomy-title">{{ tag_name }}</h2> {% for tag in site.tags %} {% if tag[0] == tag_name %} <div> {% for entry in tag.last %} {% comment %} Show the entry of each post in the style you like. {% endcomment %} {% endfor %} </div> {% endif %} {% endfor %} </section> {% endfor %} {% endfor %} Since we have finished a tag archive page, I think a category archive page is also easy to make by little modifications.

2019/11/12
articleCard.readMore

基于贝叶斯平均对豆瓣图书的搜索结果进行排序

想必有人会对豆瓣图书搜索结果的排序感到困惑吧?举个例子,假设我们搜索“JavaScript”,我们会发现排在第 3 位的是《JavaScript DOM编程艺术 (第2版)》,豆瓣评分为 8.7,有 1505 人评价;但是排在第 4 位的《JavaScript语言精粹》的豆瓣评分更高,为 9.1,而且评价人数更多,有 1792 人。更糟糕的情况是,我们想要找的高分图书往往出现在好几页之后。因此,我开发了一个 Web 应用,它基于贝叶斯平均对搜索结果进行排序。 为什么用贝叶斯平均? 如果我只想对搜索到的图书按评分排序,为什么不直接用豆瓣的评分,而用贝叶斯平均分? 原因之一是,我实在不知道豆瓣评分是怎么计算的!在此,我只能假设它就是一个简单的算术平均。现在我们假设某本书 A 有一个人评价,评分为 9.5;而某本书 B 有 100 个人评价,算术平均分为 8.9。哪一本书应当排在前面?哪一本书更值一读?算术平均不能回答这些问题。 什么是贝叶斯平均分?它的公式为 \bar{x} = \frac{Cm + \sum_{i = 1}^{n} x_i}{C + n} 其中 $x_i$ 为某一投票项的某人给出的评分,$n$ 为某一投票项的投票人数;$C$ 是一个与数据集大小成正比的数,我们可以令它等于每一个投票项的平均投票人数;$m$ 为每一个投票项的预设评分,我们可以令它等于总体投票人给出的评分的算术平均值。因此,贝叶斯平均 $\bar{x}$ 是一个随着投票人数的增加而不断修正的值。 它的意义也很容易看出,即相当于我们预先给每个投票项投了 $C$ 张票,每张票的评分为 $m$,然后再加上新增的用户投票计算一个算术平均分。这是贝叶斯推断的一个与直觉相悖的特点,即用后验统计作为先验条件。 通过豆瓣 Open API 获取数据 首先看如何获取豆瓣图书的数据。搜索图书的 API 为 https://api.douban.com/v2/book/search?q=[keywords] 其中参数 q 为搜索的关键词。 它以 JSON 格式返回数据,类似于 { "count": 20, "start": 0, "total": 199, "books": [ { ... }, { ... }, ... ] } 其中 count 为本页所含的数据条目数,start 为本页所含的数据条目的起始索引,total 为总的数据条目数,books 为本页数据条目列表。 搜索 API 可以带上参数 &start=[start],则其返回的数据条目从索引 [start] 开始。 JavaScript 实现 现在我们可以编写 JavaScript 程序去计算豆瓣图书的贝叶斯平均分了。 由于跨域访问的限制,在 JavaScript 中,我们不能直接通过 HTTPRequest 访问豆瓣 API,但是我们可以用 JSONP 技术。 /** * @param url 访问的 URL * @param callback 回调函数的名字,在此函数中我们处理 url 返回的 JSON 数据 */ function jsonp(url, callback) { const script = document.createElement("script"); script.type = "text/javascript"; script.src = url + `&callback=${callback}`; script.async = true; document.body.append(script); } 我们的回调函数命名为 jsonpCallback,在此函数中,我们解析、缓存数据,并最终计算出每一本书的贝叶斯平均分。 // 收集到的图书数据列表 let books = []; // 读取的最大图书数据条目数量 const MAX_BOOKS = 2048; // 已经读取的图书数据条目数量 let booksRead = 0; // 搜索 URL let searchURL; function jsonpCallback(page) { const count = page['count']; const start = page['start']; const total = page['total'] > MAX_BOOKS ? MAX_BOOKS : page['total']; if (total == 0) return; if (start == 0) { booksRead = 0; // 遍历每一页数据 for (let s = start + count; s < total; s += count) { jsonp(searchURL + `&start=${s}`, "jsonpCallback"); } } page['books'].forEach(book => { // 只读取评分大于 0 的图书 if (parseFloat(book['rating']['average']) > 0) { books.push(book); } }); booksRead += count; // 已读取的条目数达到了总数,解析数据完毕,可以计算贝叶斯平均了 if (booksRead >= total) { // 计算每一本书的贝叶斯平均分 calculateBayesian(books); // 依贝叶斯平均分从大到小排序 books.sort((a, b) => { return b['rating']['bayesian'] - a['rating']['bayesian']; }); // 展示结果给用户查看 showBooks(); } } 计算贝叶斯平均的代码也就是上面所提的公式的翻译了: function calculateBayesian(books) { let allRaters = 0; let allScore = 0; for (let book of books) { const n = book['rating']['numRaters']; allRaters += n; allScore += n * parseFloat(book['rating']['average']); } const C = allRaters / books.length; const m = allScore / allRaters; for (let book of books) { const n = book['rating']['numRaters']; book['rating']['bayesian'] = (C * m + n * parseFloat(book['rating']['average'])) / (C + n); } } 最后再封装一下: function sortBooks(keywords) { books = []; searchURL = `${api}?q=${encodeURI(keywords)}`; jsonp(searchURL, "jsonpCallback"); } 用户输入 keywords,点击“搜索”按钮,即调用 sortBooks(keywords)。

2019/10/9
articleCard.readMore