Blog logoJustZht

Blog logo

2017.08.11

 • 

之前事务较多,博客写的少了,即使写了也大多是抱怨和吐槽,还有一些私人邮件也没来得及回复。
好在八月初从公司走了,外包的事情做了大半,屋子也和房东阿姨交代妥了,和喵喵一起收拾屋子里的东西寄回家,然后带着行李去上海玩,总算是少了很多事情。


在上海的时候刷朋友圈,发现之前住的屋子已经被中介挂出来出租了,拍的照片和我三月份看到的时候变了很多,多了毯子,桌子和晾衣架以及很多细细碎碎的东西,都是这半年生活和装饰的痕迹,留给下一个租户了。
由于外包,工资,奖金加起来的原因,七八月是我月入最高的两个月份,想起来今年三四月份是我最拮据的时候,去杭州找喵喵玩的时候觉得车票都好贵,结果现在暂时不需要为出游的各种花销担忧了。换了一台新的 MBP 15 替代之前已经不能胜任 Unity 工作的 MBP 13,在游乐园玩了两天,买了很多迪斯尼的小纪念品,看了场烟花,这么闲散总觉得恍然隔世。


在今年三月份之前我还是对自己有充分的自信的,所谓充分的自信指,我觉得自己够厉害,不该在这个傻逼学校读书,即使辍学也能干得一份好工作。
但当时的我其实在此基础上还有更高的自信,因为不论如何,现在辍学的我靠着写写软件肯定都暂时饿不死,但总在饿死的事情上考虑总是略失体面,当时的我认为自己能去做更酷的事情,认为自己能战胜所有 hackathon,能吊打世界,能去做别人做不到的事情。
然后我就挨了一锤子,类似于王小波所说的一锤子,这四个月在被收购后的公司里,做啥貌似都不太顺,我自己的业务实现我自己知道和 PR 宣传的效果有多远,因为内部实现对于我来说其实一团糟,我是来重构的,即使过程中改了需求或加了功能,结果最后的产物仍然保持着丑陋的形态,这就是我的责任了。这种事情总归会让人心灰意冷。
有时候要回趟学校或者去看喵喵,然后就很拮据。学校的破事一堆堆,天天和辅导员扯皮,校招跪了两次,连带着有些压力,觉得自己怎么这么弱。到后来我的职位暂时闲了下来,也不知道去做什么,研究了会其他的技术栈就走了。
挨了这一锤子,我倒不算迷茫,只是想休息一会了。我的大学这几年一直是认准了目标直接做,学 iOS,学 Unity,去实习,都是能让我明白的感觉自己有成果的事情。这几个月我说不来自己做了什么成果,只是看了很多人和很多事情。


接下来暂时不回北京和百度了,因为有很多学校的事情要我去处理,大四还要准备毕设。
玩够了我就回家,想好了事情就去面对。

大三一年

 • 

七月了,考试成绩逐渐都出了,大三也算完结了。
成绩自然不是很好,包括因为我在北京没去参加实验所以一分没有的,不过大四会在学校呆一段时间,学分什么的还是能过的罢。
这一年算是我和学校各个管理人员摩擦最大的一年,包括和辅导员,课程设计的检查老师等。贵校贵院的作风大概就是在学院内部弄弄简历大赛看谁做的最好看,让文院出身的辅导员来检查计算机科学专业学生的职业规划,在信息院就业群里发红星美凯龙的管理岗位实习信息,和百度的 PM 以及 HR 说管不住我实习让我好自为之,我见过的至少有两三个大厂的实习生被学院逼离职回学校。
我只想说,贵校贵学院再这样下去估计是真的要完了。

大二一年里面写到的 GitHub Star 貌似目前有 800 个了,不过没有什么太大用处,该被腾讯面挂还是要挂,等忙完这一阵要多看看算法和数据结构了。今年也没有些什么开源的大项目,大多数 Commit 都提交给了私有仓库 Epoch,Cetacea 以及一些小的 macOS 软件。
至于公司,渡鸦变成了百度,人马还是同一批,中间动过去小米的心思,但是没去成。准备辞职了,忙完目前的项目后就走,回学校处理学校的破事,自己也要好好想想后面的发展。

写在最后,我想说,为了所谓管理上的方便,教学计划的安排而拖累一个人的发展,是有很大的恶的。很不幸在贵校的大三一年就是这个样子被拖累,看着各种机会被学院的破事干扰,心情和效率都会下降,祝我在贵校的最后一年赶紧走人。

2017.6.10

 • 

自己的 20 岁已经过了四分之一了。
妹子早上起来总是会和我说她做的梦,那个时候我一般在上班的地铁上。有时候我会感叹自己已经不怎么做梦了,我对妹子的解释是做梦是一个消耗额外精力的事情,所以小孩子精力充沛会多梦,而长大后精力不够就不怎么做梦了。但我总觉得自己是刻意把做梦的功能给关掉了,就像是电闸一样,那个功能再也不会用了。
在百度实习这段时间后,觉得自己的心态越来越糟糕了,因此博客也一直没更新,毕竟去总结自己为什么过的不开心本来就已经够令人不开心了。回到学校过后细细想了下,主要还是觉得现在的生活有种隐隐的约束感,而我这个人对于不自由是非常敏感的。
每天通勤 2 小时,白天上班要写的项目处于维护状态没有什么新鲜感,晚上不加班的情况下下班后到家一般要八九点,然后妹子和我妈会打电话过来,接完差不多就要去洗漱了,晚上就没什么时间写自己的项目。有时候会觉得自己难道要一辈子一直这样下去,在公司写没有意义的代码,也没有时间做自己的事情,所有的时间貌似都在为别人而过。
当然这种担心肯定是不可能发生的,因为我还只是大三,还是实习,所有这些担忧都只是自己在某种情绪下的极度担忧而已,况且离职就可以立刻解决这种问题,因为这样的话白天的时间就会有大部分是自己的了。但这种隐隐的对不自由的担忧会仍然存在,而一旦我觉得自己不自由了,就会各种折腾,最后把大家都弄的不开心。我生性如此,而且貌似可见的十年之内不会有改动,而且我自己无计可施。

Funny Things About the Launch of BoardForGitHub

 • 

  • 发了 40 个 Promo Code,V2EX 和 Product Hunt 各 20。
  • Product Hunt 上当日(五月 25 日)排名第四,目前 520 个 Upvotes。
  • 特性请求(Feature Request)最多的两个:1.发布 Windows/Linux 版本 2.发布 GitLab 版本(上次的 Contributions 也有小哥问我能不能写 GitLab 的)
  • 有很多人以为这是一个 Electron 包装的软件,其实并不是。BoardForGitHub 是一个 ObjC + WebView 的软件。
  • 昨天 61 和我说 BoardForGitHub 在主显示器里被拖到边缘的时候不能自动移动到另一个 Space 上去,这个貌似是因为 Override 了 NSWindow 的 MouseDragged 方法
  • Softpedia 给这个软件写了一个 Review,功能介绍比我自己的都详细(我自己很懒的),因此我决定以后使用他们写的 Review 来介绍自己的软件 hhhh
  • Paddle 的 Fee 特别高,而且是每次购买都收费,因此 $1.99 会去除 $0.6 的 Fee 和 $0 - $0.4 的 Tax,截止目前大概有十几个人购买,差不多挣了二十刀。
  • 最近也说不出是忙还是闲,很多邮件一直没来得及回,这次 launch 后也多了一些邮件,问合作的,提功能需求的,还好没有要退款的

CSS & Cetacea Dev Blog

 • 

最近在进行两个项目的重写,进度都还不错。

China Startup Simulator (CSS)

CSS 作为一个小型游戏其实没什么好说的,但特殊的是 CSS 是一个类似于 Regins 的文字游戏,因此有特别多的文本。之前用的是 xls 文件进行事件(JZEvent)和事件回应(JZEventResponse)的储存,能用但比较繁琐,比如 xls 文件修改一次就要重新 deploy 一次。所以最近把储存改为了 Google Doc,可以在 iOS / Android 上实时地从 Google Drive 上拉取解析。
不过整个数据库仍然是基于列表的,对于文字对话类游戏来说,或许是一个好的序列化方式,但不是一个好的编辑方式。
所以最近在基于 https://github.com/Seneral/Node_Editor/ 写一个可视化的节点故事流程图,拖拽故事节点后编辑内容,然后序列化到 ScriptableObject 中。

Cetacea

把 Cetacea-Mac 拆成了包含四个 project 的大 workspace:

- Cetacea-Mac
- Cetacea-iOS
- CetaceaSharedFramework
- Pods

其中 CetaceaSharedFramework 包含了 Data 层的大部分内容,让 iCloud 这部分写起来更容易了些。目前两个系统的 Cetacea 如下:

2017.5.7

 • 

这两周在路上花了不少时间,事情呢做了一些,但都不怎么顺。

面试&笔试

回长沙考期中,顺便去腾讯面试。当天长沙下雨,立珊专线还堵了一下,迟了半小时到。面试官大致问了以下内容:

  • KVO / KVC,问了下概念。
  • 线程相关,问我平时用什么。我说 NSOperation 和 GCD,然后问我用过 NSThread 没,我说没怎么用过。
  • 问 Atomic / Noatomic 的含义
  • 让我写一个线程安全的 Singleton,我用 Dispatch_once 写的,不过貌似写的不全
  • 让我乱序向一个 NSMutableArray 插入 0 - 99 这一百个数字。我问能不能用 NSSortDescriptor 然后在 Block 里面随机返回真假,面试官有些疑惑的样子,一直问我这个排序到底是怎么拍的,复杂度多少,我说这个就是苹果的内部实现,我也不敢打包票。
  • 问项目经验,特别问了 RavenTech 的 EVA VR。听完项目后问我为什么要给 Unity 写 Native WebBroswer to Texutre2D 的 Plugin,我说 Unity 只 pack 了一个 Mono Runtime,至于打包 WebKit 这种事情不是 Unity 关心的,只能自己写。估计面试官以为 Unity 应该有 Universal 的那种浏览器 UI,所以问我这个问题。
  • 问我挂过科没,回答上学期刚挂过。问我为什么有这么多项目经验,我说翘课,除了考试以外都在写项目。面试官说那是挺胆大的,我也不好揣摩意思回答,只能耸耸肩。
  • 最后问我有什么想问的没,我很实诚地回答没有。后来听同学说应该回答有问题的以显示自己很有诚意。

面完当天晚上查,显示没有过。

至于阿里的笔试,Objective-C 的运行环境是用 GNUStep 搭建的,编译也很严格,有 Warning 就不能过。我在本地 Xcode 写好的代码在在线评测上不能用,因为总会有各种类型转换的警告,比如 id 到 NSNumber,最后索性放弃了。

至于感受,校招不关心你有多少 star,自己最好还是多看看算法和数据结构,还有就是面试这种东西,还是要准备下的,不是说仗着自己忙就不去看,很多东西就是在公司用不到忘掉,但临面试看下就又记得的。

Board For GitHub

这是一个去年十月份写的开源小项目,后来有意向把这个软件上架到 Mac App Store,就又加了些功能,例如 NSTouchBar,NSUserNotifcation。结果苹果的审核团队连续五次拒绝了上架请求,原因还是和写 ArtWall 的时候一样:用户体验和用 Safari 类似。不同的是 ArtWall 是货真价实的 Native Application,而 Board For GitHub 是真的 WebView 软件,就更难过审了。
最后决定在 MAS 外分发。原本想用 DevMate 做试用功能集成 + Stripe 付款,后来发现 Paddle 直接把这两个功能都合并在了一个 SDK 里,有 Cocoa 和 JS 的版本,就很方便。但 Paddle 不包括自动升级功能,于是又用了 HockeyApp 做了自动升级。目前的发布版本可见 https://justinfincher.github.io/BoardForGitHub-Landing/

日常

和喵喵一起看了几部电影。周末窝在家里打 Gang Beasts 和 Broforce。学校管的越来越严了,我不在长沙后据说又加了脸部打卡什么的,但我已经没有精力去对付这些事情了。
回学校后发现其实还是暗流涌动,学院里同学平时都是一副不关心工作的样子,暗地里都在忙着找这个那个实习。
差不多两三年来我第一次对自己走的这个方向是不是对的有了怀疑。面试没过让我有点疑惑,在学校呆的一周发现学校有事情要对付,公司因为我回长沙也少发了些工资,和妹子有时候会有误解,让我在四月底有一种什么都不好了的感觉。
银河护卫队 2 的 Soundtrack 挺好听。

2017.4.25

 • 

最近烦心事挺多的。
回长沙考期中,然后和妹子订好了去杭州和北京的票。突然一门课的考试向后推延两天,定的票几乎全废,想要再改签也不容易,毕竟临近五一了。
WWDC 奖学金没过。我认识的两三个国内大学在读的 iOS 开发都没过,反倒是有一些苹果设置教育点的大学里的学员过的比较轻松。在这就不讨论到底公不公平的事情了,只是对于我来说,今年没过意味着短期内是不可能参加 WWDC 了:去年因为在渡鸦写 VR 没提交,今年提交了没过,明年要忙毕设,后年就在正式工作没学生这个身份了。
我从北京回长沙后,我负责的项目只能交给另一个同事来做,而从三月份重构到现在,整个 Unity 工程已经不是那位同事熟悉的样子了。我只能在长沙远程打电话或者发消息告诉同事项目的架构以及工具链如何用。然后 HR 和我说回学校考试算缺勤,扣了一大半工资。虽然知道会有这个规定,但我觉得还是有些寒心的,毕竟考虑到断断续续一年多来在公司加班,下班后留公司干活这些事情。
还是有些令人高兴的事情的,等考完回北京后再写吧。

My WWDC 2017 Playground For Application

 • 

From noreply-appledev@email.apple.com
Your WWDC Scholarship Status
Thank you for applying for a WWDC 2017 Scholarship. We appreciate your hard work and enjoyed seeing your creativity. With an extraordinary number of engaging Swift playground submissions, the selection process was not easy. We regret that we are unable to offer you a scholarship this year.

Now Open Sourced at https://github.com/JustinFincher/WWDC-17-Scholarship-Project

Unity Custom Inspector Value Saving

 • 

最近发现 SetDirty 不太好用,总是没办法让自定义 Inspector 里面的值序列化。然后在 https://docs.unity3d.com/ScriptReference/EditorUtility.SetDirty.html 这里发现有一段话:

NOTE: ''Prior to Unity 5.3, this was the primary method of marking objects as dirty. From 5.3 onwards, with the introduction of Multi-Scene Editing, this function should no-longer be used for modifying objects in scenes. Instead, you should use Undo.RecordObject prior to making changes to the object. This will mark the object's scene as dirty and provide an undo entry in the editor.''

看了下 Undo.RecordObject 的文档,唯一的不好是 EditorGUI.BeginChangeCheck() 和 EditorGUI.EndChangeCheck() 需要一前一后,对于继承的自定义 Custom Editor 有些难办,因此用 Action 把代码变成一个 Code Block:

Helper Code

using System.Collections;  
using System.Collections.Generic;  
using UnityEngine;  
using System;  
#if UNITY_EDITOR

using UnityEditor;  
using UnityEditor.SceneManagement;

namespace FinGameWorksEditor.Helper  
{
    public static class FGWEditorUtility
    {   
        /// <summary>
        /// Helper Function to use the Undo In Custom Inspector
        /// </summary>
        /// <param name="guiCode">code block to write editorguilayout stuff</param>
        /// <param name="targetCode">code block to write updating target variable stuff</param>
        /// <param name="target">the target monobehavior</param>
        public static void RecordUndoBlock(Action guiCode,Action targetCode,UnityEngine.Object target)
        {
            EditorGUI.BeginChangeCheck();
            guiCode();
            if (EditorGUI.EndChangeCheck())
            {
                Undo.RecordObject(target, "Changed");
                targetCode();
            }
        }
    }
}
#endif

Usage Example

using System.Collections;  
using System.Collections.Generic;  
using UnityEngine;  
using System.Linq;  
using UnityEngine.SceneManagement;  
using FinGameWorks.Helper;

#if UNITY_EDITOR
using UnityEditor;  
using FinGameWorksEditor;  
using FinGameWorksEditor.Helper;  
using FinGameWorks.Controller.SceneManagement;

namespace FinGameWorksEditor.Controller.SceneManagement  
{
    [CustomEditor(typeof(FGWSceneLoader), true)]
    public class FGWSceneLoaderEditor : FGWMonobehaviorEditorBase
    {
        public List<string> scenesInBuild
        {
            get
            {
                if (_scenesInBuild == null)
                {
                    _scenesInBuild = FGWSceneHelper.getAllSceneNameInBuild();
                }
                return FGWSceneHelper.getAllSceneNameInBuild();;
            }
        }
        private List<string> _scenesInBuild;

        public FGWSceneLoader loader
        {
            get { return (FGWSceneLoader)target; }
        }

        public override void OnInspectorGUI()
        {
            base.OnInspectorGUI();
            bool loadSceneOnStart = false;
            int sceneIndexToLoad = 0;
            FGWEditorUtility.RecordUndoBlock(()=>
            {
                GUILayout.Label("Parameters",EditorStyles.boldLabel);
                loadSceneOnStart = EditorGUILayout.Toggle("Load Scene On Start", loader.loadSceneOnStart);
                sceneIndexToLoad = EditorGUILayout.Popup("Scene To Set",loader.sceneIndexToLoad,scenesInBuild.ToArray());

                GUILayout.Label("Action",EditorStyles.boldLabel);
                if (GUILayout.Button("Refresh Scene List"))
                {
                    _scenesInBuild = null;
                    foreach (var item in scenesInBuild)
                    {
                        Debug.Log(item);
                    }
                }
            },
            ()=>{
                loader.loadSceneOnStart = loadSceneOnStart;
                loader.sceneIndexToLoad = sceneIndexToLoad;
            },loader);
        }
    }

}


#endif

namespace FinGameWorks.Controller.SceneManagement  
{
    public class FGWSceneLoader : MonoBehaviour
    {
        public bool loadSceneOnStart;
        public int sceneIndexToLoad;

        void Start ()
        {
            if (loadSceneOnStart)
            {
                SceneManager.LoadScene(sceneIndexToLoad);
            }
        }

        void Update () {

        }
    }
}