2019.12.24

 • 

去拔了牙。打了麻醉又掉了牙齿,因此现在特别困,准备去睡了。
留学的第一批申请递交出去了,第二批还在准备,又是比较赶的安排了。
在 Xbox 上买了 Elite Dangerous,结果和 Steam 版本一样的 bug 众多,而且不能同步之前在 Steam 上的账户,因此玩了两小时就弃坑了。
听了 Soulection Radio 437SPACY。看了 The Illusion of Peace in Mamoru Oshii's Patlabor 2,最后把 World War II in Colour 也追完了。

2019.12.16

 • 

出了 18 年 3 月 Pi Day 买的 Alienware 15 R3,黑五的时候转运了 MSI P65,结果 UPS 给我送偏了,外加转运中国目前只能走 USPS 目的地清关,加起来时间和价格上与亚马逊海外购都不占优了。


为了申请学校更新了 Portfolio,加上了 Air Ink。这几天还会把之前许多 private 的仓库给 public 出来装点下 GitHub 门面,毕竟自己 star 比较多的几个仓库都是四年前写的,现在看惨不忍睹。也用 LaTeX 写了一份学术性质的 CV,为了防止教授们对 Justin Fincher 这个网络 ID 和 fincher.im 这个域名有疑问之前还买了一个新的中文拼音的域名做了重定向。


申请季自己的心态也有些变化。之前看到各类新 iOS 或者 macOS 的 concept video 都是嗤之以鼻,觉得大部分都经不起推敲,设计上也很刻意,总而言之没有能打的(反倒是有一些 Windows 的 redesign 项目我觉得还不错,当然也许只是流行度导致的样本总数不同导致的偏差)。但是自己又没有要去自己做一个的意思,时间不够,另外隐隐也觉得没有什么意义,毕竟一来不是行家二来没有弄清楚到底我做这个有什么好处。总之就是外人看起来比较狂妄又没有作品的感觉,因此我也没有对外提起过我的看法。不过申请季的时候突然觉得管他呢,找个周末当 hackathon 一样突击做出来,最后出成品至少也是一个能看的,对个人资料的丰富和 personal branding 都是有好处的,干嘛要担心那么多。


之前和猫猫说,负责托管她的简历的那台 VPS 只开了一个 nginx 每天无所事事的感觉,月终还扣我全款,不如拿来做些好玩的事情。因此目前属于她的那台服务器已经挂上了 BOINC 在以 70% 的占用率跑 SETI@Home,猫猫也觉得挺开心。


听了 SoundCloud 上的 2019 个人前三十 和 Spotify 上的 2019 个人前一百

2019.12.1

 • 

没想到都十二月了,过的真快。
自己的选校和文书都还没准备好,稍微有点迟了,然而还是在这里面对着电脑敲博客。
十一月底又去了东京一趟,作为 Inkathon 七个队伍其中一个队的 Session Speaker 参加了 Connected Ink,展示了下利用 Mirror 跨设备同步 Skinned Mesh 的改进版 Air Ink。这次的 Session 包括了 Tokyo 和 Portland 两场 Inkathon 的队伍,聊起来也挺愉快。结束了 Connected Ink 后面基了宅撩Maple,吃了好吃的烤肉。第二天睡到中午然后出去逛,然后第三天也就是前天回来了。
然后第四天也就是昨天上午去考了场 GRE,这场 GRE 完全是水过去的,原因是我本身 GRE 分数已经差不多够了不需要再刷但是又过了退考时间,因此想着那就考下罢,结果又飞机又火车的奔波导致考试的时候特别困,考试的时候特别后悔,想着睡觉或者研究黑五可比做题目舒服多了。不过神奇的是分数竟然没怎么掉,但是也没什么用了 hhh。

airink

Air Ink 的展位,下午的 Session 到来之前就在这里负责展示,不过 Air Ink 项目即使改进了一些后仍然是一个非常前卫的项目,而我这里说的前卫是指“好玩”但是“仍然没有具体的前景”,因此我们的展位人数相对少了很多。左边的 keynote 其实是用 Deckset 导出图片序列后塞进去的,我发现 Deckset 真是懒人必备。

fuji

富士山。在日本的前两天都在间歇下雨,只有临走的当天天气极好,因此在飞机上拍到了。

carbook

台场某个杂货店的二楼买的 Automobile Year 94/95 版,那里非常好玩,全部都是关于汽车的老旧书籍,甚至有某某越野车的维修指南,而且非日文的外文书半价,因此购得一本。

2019.11.24

 • 

前几天得知 GRE 的写作考试分数出来了,就此 GRE 也不用考了(虽然还有一场过了退考时间的可以月底再刷一次),只用准备文书了。
项目有一堆要修的,比如这个博客的主题也要找个时间修整下,Skyline 的手势识别还是没修,原本计划十一月能上架 Asset Store 的 Unity Android LWP 方案因为 2019.3 导出 gradle 项目结构的改变也要修改了。不过 2019.3 这一改倒是对开发方便了很多,可以直接把 Unity 当 lib 来管理了,只不过最近 Unity 的其他举措都有些迷,比如 LWRP 都要 production ready 了突然宣布 Deprecation 然后官方博客说只是把 LWRP rebrand 到 URP,但真正迁移了后才发现着色器的 namespace 变了,Post Processing V2 也不支持了,各种鸡飞狗跳,我自己的 Metropolis 项目因此彻底迁移回默认管线了,Skyline 暂时留着 6.x 的 LWRP 不动先。不知道再这么弄下去 Asset Store 的素材更新会不会因此断档一段时间,这对素材发布者和用 Unity 的开发者都是个很劝退的事情,至少对于我来说,用 Unity 的一大要素就是 Asset Store 东西很全,不会某项技能可以直接无脑买了即插即用,而如果 pipeline 三天两头出毫无意义的 break changes 导致上游的素材发布者不兼容甚至弃坑接着我也把我的项目弃坑或者直接去用 Godot 或者 Unreal 了,最后损害的反倒是 Unity。
最近听了 Summer Breeze(Piper 在 Spotify 港区,Apple Music 和 Tidal 上都没,最后还是只能用虾米,但说实话虾米被阿里买了之后现在已经特别恶心了),Soulection 434UnwindTemptation,当然还有 FKJ 新专 Ylang Ylang,虽然这张里面没有我想要的 Red Flowers,或许下张会收录吧。
总之,这个十一月大都在处理语言考试或者处理文档,寥寥无几(其实也有快一周啦)写代码的时间学了怎么用 Mirror,希望下个月能早点结束文书和选校然后我能去开始做跳票了快三年的 Epoch

2019.11.7

 • 

IMG_20191104_234719
去东京参加了 Wacom Inkathon 的 Final,到场其他人的项目基本上都是 production ready 了,自己的半成品自然是没有什么结果 hhhh,但是各个队伍都很友好,交流的很愉快,以及后期也许还有协作的可能。总之在东京的这几天真的非常开心,除了还是很困以外。

Google Sheet + Apps Script + Glide

 • 

背 GRE 单词的时候弄的一套自动化流程,可以批量查词修改 Spreadsheet,配合 GlideApp 生成 PWA 在手机上用,效果可以参考这里

1) 导入 Apps Script

这个脚本基本上就两个功能,一个是触发器,负责在修改文档的时候更新那一行的内容,第二个是一些数值函数,可以直接在 Sheet 的 f(x) 框里面用。所有查询的词都做了 lowercase 和 cache,毕竟 UrlFetchApp 有配额一天只能跑上几千次,要省着点用,而且如果用了 f(x) 表达式的话,每次打开页面都请求爱词霸也挺拖累别人服务器的。

appsscript.json

{
  "timeZone": "Asia/Hong_Kong",
  "dependencies": {
  },
  "webapp": {
    "access": "ANYONE",
    "executeAs": "USER_ACCESSING"
  },
  "exceptionLogging": "STACKDRIVER",
  "oauthScopes": ["https://www.googleapis.com/auth/documents", "https://www.googleapis.com/auth/script.external_request", "https://www.googleapis.com/auth/spreadsheets"]
}

code.gs

function onOpen() {
  var ui = SpreadsheetApp.getUi();
  // Or DocumentApp or FormApp.
  ui.createMenu('Words')
      .addItem('Reset Cache', 'ResetCache')
      .addToUi();
}

function onEditInstalledTrigger(e) {
  var range = e.range;
  var sheet = SpreadsheetApp.getActiveSheet();
  
  Logger.log("row = " + range.getRow() + " column = " + range.getColumn);
  Logger.log("row Number = " + range.getNumRows() + " column Number = " + range.getNumColumns());
  
  for (var i = range.getRow(); i <= range.getRow() + range.getNumRows() - 1; i++) {
    for (var j = range.getColumn(); j <= range.getColumn() + range.getNumColumns() - 1; j++) 
    {
      if (j == 1)
      {
        var currentValue = sheet.getRange(i,j).getValue();
        var wordLowCase = GetWordLowerCase(currentValue);
        var empty = IsEmpty(wordLowCase);
        var cache = IsCached(wordLowCase);
        
        Logger.log("row i = " + i + " column j = " + i + " word = " + wordLowCase);
        var attribute = {
          cache:cache,
          value:wordLowCase,
          empty:empty
        }
        sheet.getRange(i,j).setNote(empty ? "" : JSON.stringify(attribute));
        sheet.getRange(i,j+1).setValue(empty ? "" : "LOADING");
        sheet.getRange(i,j+2).setValue(empty ? "" : "LOADING");
        sheet.getRange(i,j+3).setValue(empty ? "" : "LOADING");
        sheet.getRange(i,j+4).setValue(empty ? "" : "LOADING");
        sheet.getRange(i,j+5).setValue(empty ? "" : "LOADING");
        
        var wordJsonObject = empty ? {} : GetWord(wordLowCase);
        attribute.json = wordJsonObject;
        sheet.getRange(i,j).setNote(empty ? "" : JSON.stringify(attribute));
        Logger.log(JSON.stringify(attribute));
        
        if (wordJsonObject.status == 0)
        {
          var wordMeaning = empty ? "" : wordJsonObject.content.word_mean.join("\r\n");
          sheet.getRange(i,j+1).setValue(wordMeaning);
          
          var worldPronunciationUS = empty ? "" : "/" + wordJsonObject.content.ph_am + "/";
          sheet.getRange(i,j+2).setValue(worldPronunciationUS);
          
          var worldPronunciationURLUS = empty ? "" : wordJsonObject.content.ph_am_mp3;
          sheet.getRange(i,j+3).setValue(worldPronunciationURLUS);
          
          var cachedString = empty ? "" : (cache ? "YES" : "NO");
          sheet.getRange(i,j+4).setValue(cachedString);
      
        }
        
        var updateTime = empty ? "" : new Date().toISOString();
        sheet.getRange(i,j+5).setValue(updateTime);
       
      }
    }
  }
}

function ResetCache() {
  var cache = CacheService.getScriptCache();
  var selection = SpreadsheetApp.getActiveSheet();
  var data = selection.getSelection().getActiveRange().getValues()
  for (var i = 0; i < data.length; i++) {
    var wordLowCase = GetWordLowerCase(data[i][0]);
    Logger.log('Cache remove: ' + wordLowCase);
    cache.remove(wordLowCase);
  }
}

function GetWordLowerCase(word)
{
  return String(word).toLowerCase();
}

function GetWordUrl(word)
{
  var wordLowCase = GetWordLowerCase(word);
  var url = "http://fy.iciba.com/ajax.php?a=fy&f=auto&t=zh&w=".concat(encodeURIComponent(wordLowCase));
  return url;
}

function IsCached(word)
{
  if (IsEmpty(word))
  {
    return false;
  }
  var wordLowCase = GetWordLowerCase(word);
  var cache = CacheService.getScriptCache();
  var cached = cache.get(wordLowCase);
  return cached != null;
}

function IsEmpty(str) {
    return (!str || 0 === str.length);
}

/**
 * Get Word JSON Object.
 *
 * @param {word} the word.
 * @return JSON returned from iciba.
 * @customfunction
 */
function GetWord(word) {
  if (IsEmpty(word))
  {
    Logger.log("word == null");
    return {};
  }
  var wordLowCase = GetWordLowerCase(word);
  var cache = CacheService.getScriptCache();
  var cached = cache.get(wordLowCase);
  if (cached != null) {
    return JSON.parse(cached);
  }
  var url = GetWordUrl(wordLowCase);
  var jsonData = UrlFetchApp.fetch(url);
  var jsonContent = jsonData.getContentText();
  cache.put(wordLowCase, jsonContent, 604800); // cache for random minutes
  var object   = JSON.parse(jsonContent);
  return object;
}
/**
 * @customfunction
 */
function GetWordMeaning(word)
{
  var object = GetWord(word);
  return object.content.word_mean.join("\r\n");
}
/**
 * @customfunction
 */
function GetWordPronunciationUS(word)
{
  var object = GetWord(word);
  return object.content.ph_am;
}
/**
 * @customfunction
 */
function GetWordPronunciationUSURL(word)
{
  var object = GetWord(word);
  return object.content.ph_am_mp3;
}
/**
 * @customfunction
 */
function GetWordJSON(word)
{
  var object = GetWord(word);
  return JSON.stringify(object);
}

然后部署下,允许下文件访问

2) 安装 Trigger

表格 - 工具 - 脚本触发器 - 修改 - 当前项目触发器 - 添加:

  • 选择要运行的功能 OnEditInstalledTrigger
  • 选择活动来源 基于电子表格
  • 选择活动来源 编辑时
3) 输入数据

第一行最好 freeze 住,然后在第二行开始输入英文,Trigger 会自动补全后面几列
Screenshot-2019-10-24-at-12.44.30-AM

4) 同步到 Glide 里

Glide 里新建一个项目绑定这个表格。
Glide 比较好的地方在于,他们的 Detail Page 可以设定 Audio 组件绑定 URL,刚好可以对应爱词霸 API 里的音标地址,基本配置完可以当一个自定义单词本来用了。

Screenshot-2019-10-24-at-12.49.27-AM-2
Screenshot-2019-10-24-at-12.50.28-AM

2019.10.21

 • 

Hackathon 入围第一轮了,接下来下个月会去东京参加 Final。自己的作品原本就只是三四天完成的,因此选题和实现上都是典型的求快的方式,在长期改进方面相比其他队伍就会比较被动,加上自己还有语言考试没有结束,只能抽时间做了。
回家休息了一周,心态懒散了不少。
看了 Hayao Miyazaki's AirshipsTrevor Noah: Afraid of the Dark

2019.10.4

 • 

9.21 的托福成绩出来了,连续每两周一考的最后一次,目前看来三次下来分别是 102/107/108,目前四科加起来的 best score 总算是上 110 了,虽然单次考试没过线,但申请总算是有一个大致的保障了,接下来会去考 GRE。

6CD8BD79-EF75-462F-9A8E-BD98688D61D9

顺带一提,浙大的考点是六边形的开放桌子,会比较影响人发挥,还是科大的封闭桌子好用。


国庆也没有出去玩,也没有看电影,而是窝在住处给将要参加的一个 Online Hackathon 写了三四天的 Unity,和 ARKit 相关。ARKit 3 的 Motion Tracking 其实还是挺不稳定的,有的时候极其精确,有的时候又都连叉开的左右脚都分不清。一开始想做成 Unity Editor 的样式跑在 iPad 上,因此买了 Runtime Editor,效果还行,但对移动设备的支持不太好,加上多个 RenderTarget 也挺吃内存的最后就放弃了,直接用 UGUI 做 World Space 的 3D UI。

iPad 的效果
算下来也是这几个月第一次花了全天的时间在写代码了,GitHub 上也少有地出现了绿格,虽然过几天还有 GRE 考试因此心里还是挺担心因为光顾着代码没时间准备考试,但总的来说还是挺高兴的,有点类似于阅兵一样,算是在检验自己的快速出原型的能力。


之前趁着降价买了 XCOM,而且鬼使神差地 Google Play 和 App Store 上都买了,结果 Easy 模式对于我来说都有点够呛,中期经常全员覆灭,到中后期打完神秘组织后靠着机器坦克慢慢缓过来,又觉得没有挑战了。不过总归来说这个游戏太让人入迷了,因此最后被我被迫删了。
之前貌似又玩了 EVE 手游版的 testflight,但是后来貌似测试服务器关了,因此也删了。总之貌似还在准备语言考试的阶段,因此自己也没办法过的太懒散。

Unity 实例在 iOS 13.1 上无法连接 Profiler

 • 

Workaround:Xcode 项目里 Add Capability 'Access WIFI Information' && 申请地理位置权限
PostBuildCapabilityChanger.cs

using UnityEditor;
using UnityEditor.Callbacks;
using UnityEditor.iOS.Xcode;
using UnityEngine;

namespace FinGameWorks.Scripts.Editor.Modifer
{
    public static class PostBuildCapabilityChanger 
    {
        [PostProcessBuild(999)]
        public static void OnPostprocessBuild(BuildTarget target, string pathToBuiltProject)
        {
            string path = pathToBuiltProject + "/Unity-iPhone.xcodeproj/project.pbxproj";
            Debug.Log(path);
            Debug.Log(PBXProject.GetUnityTargetName());
            ProjectCapabilityManager capabilityManager = new ProjectCapabilityManager(path, "InkCloth.entitlements", PBXProject.GetUnityTargetName());
            capabilityManager.AddAccessWiFiInformation();;
            capabilityManager.WriteToFile();
        }
    }
}

PermissionManager.cs

using System.Collections;
using UnityEngine;

namespace FinGameWorks.Scripts.Manager
{
    public class PermissionManager : Singleton<PermissionManager>
    {
        private LocationService m_LocationService;
        protected override void OnAwake()
        {
            base.OnAwake();
            m_LocationService = new LocationService();
            m_LocationService.Start();
        }
    }
}

2019.8.29

 • 

连着上了两个月课,也有些懈怠了。
上英语课的最大发现倒不是语言上的什么诀窍,而是发现自己在表达看法上的匮乏。有些话题给我我就没话说也没什么想写,因为“感觉一句话就说完了”。换言之,一直以来是刻意地训练自己把看法内化,而突然被要求对这些看法重新评估的时候就已经有预设了,自然感觉没什么好说。这件事倒是没什么好或不好的,只是在没有足够的理论方法却又建立了太多个人观点的尴尬境地里徘徊一会。
因为上课也没有太多时间,很多想法也没有在 Ghost 上写了,转而偶尔在 Day One 里记一下。之前花了很大功夫从 Day One 迁移到 Journey,结果 Journey 的 Chrome App 说关就关,电脑上只能用桌面客户端,虽然是一次性购买但软件质量真的不值几十美元,因此又转而续了 Day One 的订阅。
有了几个新的想法,比如闲暇时有在慢慢写一个 iOS 上基于 Files 的 DocumentPickerUI 的背单词软件。此外还有一些 Unity 项目和 Unity Asset 的计划,但是 Unity 项目的调试周期都太长,目前是没有可能做了。目前看来语言考试的排期已经到了一两个月后,如此看来今年下半年是不太可能有时间去 Unity Shanghai 写喜欢的项目了,其实还挺可惜的。如果年底语言考试完结,估计也不打算去什么公司了,而是直接重写 Epoch,希望明年动身前能差不多做到核心玩法都完成的地步,后面再走一步看一步。
看了好几部电影,包括 Shaft 1 & 2,哪吒和速度与激情,记得之前还重新看了遍星银岛。
密集地修了两次电脑,一次是 MBP 18 的键盘,一次是 MBP 15 的电池召回。两次都是在南京修的,每次去都要顺路去买泡芙,因此和背单词记含义一样现在建立起来了苹果店和泡芙店的奇怪联系。
博客写的越来越少了,各种平台也不怎么说话了,除去上课的原因,自己相比以前也更没话说了。有的时候看以前的推发觉还挺好玩,那种心态倒是挺难得的,一种急切又乐观的状态,但是也因此很难维持下去。自己倒是逐渐在适应沉住气的节奏,因此和都在做好玩的事的大家相比显得就更为无话了,默默围观下大公司雇员,独立开发者群,设计群和游戏开发们的动静。有的时候大家又都在发看法,我想说些什么却似乎也没有发表看法的必要,因为某种意义上来说,不暴露自己的政治倾向也是政治倾向的一部分。