Blog logoJustZht

Blog logo

Justin Fincher

2018.12.14

 • 

可爱的配色
事情颇多,个人的大项目一个都没来得及做,唯一做出来的是把之前写 Skyline 的时候验证想法的几个 Playground 套上了统一的 Skyline 2.0 风格 UI。水墨画风格的叫 Ink,星空长时曝光风格的叫 Vortex,这些配色还是挺可爱的。
好久没写自己听了什么了,最近值得一提的也就是 Ups & Downs 这首,之前在 Soulection 第 369 期里面听到过。算起来加上上个月的话也是看了几部电影,在电影院看了 Bohemian Rhapsody, 在小龙推荐下看了 Public Enemies 和 Lord Of War,然后重新看了遍 War Dogs,坐飞机的时候又重新看了十一/十三罗汉。这里面 War Dogs 和 Lord Of War 都是军火贩题材的电影,也是某天晚上和大家一起连着看的。我一直挺喜欢 War Dogs 这种放荡不羁的剧情,像是永远不会结束的旅行一样。时间一长我就发觉自己总是在复习之前的电影,每次我妈在发现我在家看《社交网络》或者《了不起的盖茨比》都会说重复看没意思,但对于我来说倒是挺乐在其中。
最近和某家公司的 Unity Leader 聊天说到 Unity 里架构设计的问题,我说自己就没怎么上过软件设计课,他说大学的时候没几个人会真正明白软件设计,真正的理解都是做项目踩坑理解出来的,做几个最后没办法维护的软件后自然就能真正明白当时软件设计课的内容。这个我倒是感触挺深的,毕竟大学初期写 iOS 外包怎么都写不死有大概率是因为 iOS 的 API 设计本身就足够周到同时也架构上避免了新手的错误,接触了 Unity 和 Android 编程后才发现一个写出后期无法维护的项目是一个多么容易的事情,然后自己就会重写,慢慢地让软件足够有条理地跑起来,对特定平台的编程模式也会有所见解。所以总的来说和看电影一样:其实写的平台和看的电影都没变,变的是自己罢了。软件架构和人物剧情也许没那么深刻,但自己的经历反射在上面,就让自己觉得历久弥新了。

2018.12.10

 • 

看到一个介绍 Mac Miller 逝世的视频 的下面评论说:“比他惨的人大有人在 明星就是容易感伤”云云,看的我莫名其妙。并非对其他人不敬,但我的确觉得虽然比他惨的人的确大有人在,却怕是很少有人能发行出同等的音乐吧,这里的关键并不是抠字眼一般的发行不发行音乐,而是作为个体对世界对他人的影响几何。当然这里也无意讨论是否一定要以对他人的影响作为评价个人价值的标准,只是作为一个他的听众来说,我觉得理应感谢他曾给别人带来的愉悦心情,而我也觉得需要说明让人产生悲伤的不只是面对悲惨的境遇时的同理心,还有多么才华横溢的人陷入了惨境无法自拔这种事情,越是能给别人带来欢乐之人内心却越在挣扎,这才是最令人唏嘘的事情。

2018.11.20

 • 

说两个好玩的事情。
第一个事情还蛮侦探的。最近在美国住在 Bell Canyon 附近,然后住处的路由器是 Google WI-FI。有天在外面玩我妈打电话给我说是不是我刷了我爸卡四笔共 2000 块钱,我说没啊最近都只用自己的卡,再一看我爸卡的消费记录是国内时间半夜,一个小时内连续四笔,每笔金额都差不多。自己以为是被盗刷了于是让我爸先去锁卡。然后我爸和银行客服沟通后说是优步扣的,我去看优步和 Paypal 都没有记录,又搜了下网络说有很多盗卡给别人提供低价打车服务的,因此觉得是不是遇到了啥盗刷套现或者幽灵车之类的事情,因此和 Uber 客服联系了下。
Uber 客服还没给我回信,我的一位也在 Bell Canyon 的同事和我说她今天查看了下她的银行卡发现也被刷了,而且模式很像:一小时内连刷四笔,四笔金额都差不多,而且时间就在我被盗刷后二十分钟内。我们就更摸不着头脑了,开始研究是不是有什么中间人或者奇怪的手段,不然不可能手法如此相像,气氛也有点奇怪了起来,感觉网络被做了什么手脚。
我的那位同事打了通电话给银行客服,客服说是 Lyft 的扣费,但是银行这边状态是拒绝交易了。然后我们回想起来貌似国内半夜的那个时候正是 LA 这边早上八九点,那天要一起去 LA 市内取车,因此我们都在不停地打车,但是都没有打到,最后是另外一位同事帮忙打到的。而信用卡盗刷消费每笔的金额差不多就是 Bell Canyon 到市内目的地的价格。
所以这个事情变成了:扣费应该是我们自己主观打车操作造成的,但是 Uber 和 Lyft 在那天早上不约而同都出现了没打到车但是扣费也没订单记录的问题。我们研究了下,感觉是不是 Google WI-FI 路由器的 Mesh Network 切换路由器导致状态没同步或者内建的防火墙拦截了什么不该拦截的请求。
不管怎么说至少不是奇怪的剧情走向了,而是变成了一个技术 bug 相关的问题,现在就希望 Uber 客服能早点退钱给我。🤔
第二个是:8012 年了,Unity 的 AudioSource 竟然还没有播放完成的 API。
这么一个简单必须的 API,竟然到现在还没有提供,因此搜遍论坛上得到大家普遍的最佳实践是:先获取 AudioClip 的长度,然后设置一个携程去做倒计时,在携程最后调用自己的回调。
因此为了一个简单的事件,就必须在引入 AudioSource 的类中再引入一个长度的倒计时,而且要在每次 AudioClip Re-Assign 的时候清零重新计算。这就是典型的我所痛恨的 Unity Pattern:为了一个简单的事情,在一个类里面引入不必要的 Flag 作为状态标识符,然后事情一多,多个 Flag 就开始打起来了,类变得非常混乱。
这个事情不是不能避免,但是对于很多新手来说,这种 API 设计就是在逼着大家去写 Shit Code,把所有东西都逼着写到 Update 里面重复检测,活生生写成面向过程的东西,而且系统提供的 API 缺乏很多必须的信息,导致如果不从头抛弃内建的音频系统(FMOD 和 Audio Mixer)的话,最后看起来比较简洁的代码背后的实现也是比较耗费资源的(比如做一个属性变动的观察,实际上只是把 Flag 标识符的代码写在一个全局的专门用来观察的地方罢了)。

2018.11.2

 • 

说说最近做了些啥

Skyline

Skyline 目前仍然在 alpha 封闭内测中,没有推到 beta 的原因是 1) 关键的地图黑屏 bug 还没有完全解决 2) 在一些 Android 定制系统(比如 MIUI)中会出现 UI 和稳定性上的问题,我估计需要去弄台对应的手机才能确切知道到底发生了什么,明天会去趟小米之家。
把手头一个 max 格式的上海的城市模型折腾了下变成标准材质,然后导出到 twinmotion 里面,效果还不错。twinmotion 是一个类似于 lumion 的建筑可视化渲染软件,目前看来视觉效果比 lumion 好的多,软件界面和逻辑也更好一些。如果 twinmotion 在这几天的实验里面效果不错,Skyline 2.0 的预告片估计会来得及完成。
上海模型渲染图:
2018-11-02-01.24.51
2018-11-02-01.24.56

Starry

Starry 是自己在实验 Unity 动态壁纸过程中的另外一个作品。Skyline 是 Android 动态壁纸,而 Starry 是 Windows 动态壁纸。这个项目基本上就是两天拿 WinForms 和 Unity 魔改的结果,但效果还不错,因此上架了 Microsoft Store,兑换码在这里
Starry 虽然上架了 Microsoft Store,但严格来说并不是一个 UWP 软件,而是一个 win32 软件,其 UWP 的外壳通过 Advanced Installer 转制得到的。后期 Starry 也许会加入更多的相机视角(这块是用 Cinemachine 做的转场)和一些基本的后期处理效果,也许不会,毕竟 Starry 只是一个试验性的东西,旨在证明 Unity 在主流平台魔改的可能性。
顺带一提,Starry 移植到 macOS 也并未难事,等这些事情都做完后我会写一篇文章专门说怎么调用两个桌面操作系统的 API 对 Unity 窗口进行调整,然后也许会在 Asset Store 发布一个针对 macOS 的动态壁纸 Toolkit。

一些其他项目的计划

准备更新下 portfolio,换用 Wordpress + Docker。
因为被 Ghost 2.0 恶心到了,只能本地写好再上传。在寻求 Mac 上的 Markdown 客户端过程中发现还是没有最满意的,于是又动了重写 Cetacea 的念头。
Epoch 做了惯常的依赖更新,整个系统仍然停留在 “模拟器” 的阶段。我也想多做一点,但是整个项目的规模太大,一天花上半小时根本没办法系统地做上什么。之前和别人聊天还谈到这个事情:
“你财富自由了准备干嘛呀”。
“我估计就去写 Epoch 了,有钱有时间,不用担心饿死”
“那你为啥不直接雇上几十个人帮你写”
“不不不,这个项目就是要靠自己写把整个宇宙都模拟下来,才有那种感觉”
虽然听起来很可笑但是的确就是这个样子,我也只能寄希望于我啥时候能天降横财让我财富自由去写 Epoch 了。

玩游戏

买了 Party Hard 2,相比第一代这个游戏真的是太难了,特别是标注眼睛的保镖,基本上见到就会游戏结束,导致真正的乐趣反而很容易就没了。
上个月还玩了休闲向的皮皮,记得在去合肥看猫猫的火车上都在打这个游戏。要分析的话,这个游戏做的成功的地方在于每局时间不会占用很长时间,到后期不花钱推关卡又很容易耗尽体力值,但是偶尔运气好了总能过关,这条让玩家惦记的线拿捏的很准。
貌似还玩了 Thumper 手机版,我倒是很好奇他的抗锯齿怎么做的。

喝水

在北京呆了一段时间,天气太干燥了,每天起床像是噎了钉子一样,说话特别疼。只能不停喝水,寄希望于补水会让喉咙好些。最近还吃了好几次满记甜品的白雪黑糯米。

听音乐

听了 Sun FlowerTime Moves Slow(这首之前在 Soulection 还是 Voyage Funktastique 里面作为 Mix 的第一首过),Come InsideVoyageBehaveSlinky Man

2018.10.30

 • 

这篇文章是在 Sublime Text 里面敲出来的。原因是我把 Ghost 从 0.11 升级到了 2.x 后发现 Ghost 2.0 内置的文本编辑器对中文拼音输入特别差,有点类似于 iOS 开发里面在 UITextField 没有做好对 Multistage Text Input 的支持的感觉(拼音输入有 Marked 和 Commited 两种 Stage),在拼音尚未完全输入的时候就自动格式化为纯文本了。至于升级 Ghost 的原因是愿愿说我的 RSS 挂了,我感觉像是之前的 Ghost 没有做好 escaping 于是决定升级大版本,结果还不能一次性升级到 2.0:
2018-10-30-11.19.43
总算是折腾好迁移之后进入后台发现那篇导致 RSS 挂掉的文章文字之间里面多了很多红点,不知道是 BOM 字符还是什么奇怪的东西,删除了之后终于能够输出格式正确的 RSS 了。
为了升级 Ghost 2.0 给当前在用的主题(魔改版 Daring)做了一些适配,顺便把主题色和 Banner 换了。
这次适配完我就不想再继续用 Ghost 了,把 MD 编辑器变为支持 MD Block 的富文本编辑器这个事情之前就让我一直没有升级到 1.x,结果 2.0 还是有这么多恶心的事(比如说富文本编辑器的 MD Block 插入图片不能像之前一样写好 ![]() 然后点击上传了,因为两栏预览功能貌似取消了,其次只能点击 Block 下方的插入图片按钮选择图片)

2018.10.14

 • 


偶尔翻了下 timehop 才发现刚好一年前的今天是校招季,对比下最近这两个时间还挺接近。
当时我恐怕不知道后面一整年的自己会那样疲于奔波,真是漫长的一年啊。

LogMeow

 • 

写 Skyline 的过程中发现 Android Studio 的 Logcat 界面经常假死,于是自己尝试写了一个 Logcat 界面,基于 kotlin-graphics/imguicosysoft/device。dear IMGUI 比 Unity 的 IMGUI 强大许多,写起来也比较有趣。

导出后就是一个 jar,可以运行在 macOS / Windows 上。

2018.8.3

 • 

Skyline 2.0 写了一半多了,因为重写的缘故遇到了一些之前没遇到的问题,估计发包还要推后一些。
写 Skyline 需要很多 Android 测试机,因此从家里搜了五六台设备用于测试,其中有一台 15 年买的坚果 Y601。这台 Y601 从买回来之后就一直只要卡顿就自动重启,之前折腾了半天总算是升级到了 Smartisan OS 3.x 版本,这次发现系统提示可以升级到 4.2 进入升级界面却显示无网络,因此点了重置手机想看看清楚数据后会不会好些,哪知道重置后卡在了激活界面提示无法连接网络。在论坛里搜了半天后才知道 3.8 以下版本网络激活的通道关闭了,只能手机卡激活。而我也没有大卡可以塞进去激活,因此直接下了一个 OTA 包 sideload 进去了。
这个事情让我哭笑不得,一个做手机的厂,自家深度定制的 OS 在版本兼容上出了各种问题,之前 2.x 版本升级签名不对,需要手动安装新的系统更新 apk 才能检测新的 OTA,然后是应用商店里的微信签名不对导致更新后数据丢了,还有这次老系统的激活验证通道直接关闭,实在不像是一个正常厂商干的事情,毕竟我这种独立开发者玩票随时弃坑就算了,商业公司关闭接口竟然也没有 API fallback 或者远程弹窗提示,让普通用户干瞪眼送修吗,况且 Google 地图应用现在仍然可以在十年前的 Android 0.9 系统上查看,iOS 6 的 App Store 仍然能获取商店信息和下载更新,这些公司对老系统的接口兼容性可算是比锤子这种一个版本号就过不去的做法不知成熟到哪里去。
这个事情让我坚定了自己的软件最好不要带服务端的想法,毕竟之前就写过这个事情,优雅但因为服务关停而无法使用的软件太多了,还是数据自给自足的软件更有生命力些。
这个事情也让我对自己发展的选择隐隐的担忧更加深了些。身边的人都去大厂锻炼,不出意外我也是要去腾讯的人(目前因为延期毕业和一个其他事情暂时推到八月中下了),去大厂相比创业公司主要就是看工程,看这类流程和大项目才会有的问题,而如果我又去折腾一些不成熟或者求快的东西,我会真的有收获吗,我还是没想通。

2018.7.29

 • 

FKJ Beijing Live

去北京看了场 FKJ 的演出,应该是北京首演了。

FKJ 是这两年来最喜欢的音乐家,说来有趣,我的 Skyline 动态壁纸 就是听着他的 Skyline 写出来的,软件名字也因此或多或少地顺手叫了 Skyline。
之前这个票价让我觉得有点太便宜了(或许是早鸟票的原因),毕竟是大佬来华。初到现场时以为不会有太多人,哪知道排了两层楼的队伍,组织有些不利等到了十点才正式开始。至于 Live 怕是不可言说只能亲身感受,最后返场的时间也超长,把 Vibin Out 和 So Much to Me 都演了,十分满足。
顺带一提,暖场的 HFigures 也不错,还专门搜了下,Deep Diving 挺好听,喵喵也喜欢这类 Jazz hiphop。

Skyline LWP

2.0 正在编写。的确还是跳票了,由于学校的考试,课设,延期毕业,个人的办签证,去北京处理东西等其他事项,2.0 从三月份拖到现在,让我自己都有一种赚了钱就跑的感觉,但是这么就太不厚道了,而且 1.0 版本问题的确很多,于是最近有些空,完全重写了下。
这次仍然没有摆脱 Unity,原因是 Android 上暂时没找到一个现代的的对开发友好的 Scene Graph / 游戏引擎:

  • ViroCore API 设计非常适合有 SceneKit 经验的人,但是暂时不支持自定义着色器,因此没办法做到根据高度图实时 Vertex Offest,而如果手动构造 Mesh 的话为啥不用 Unity 有现成的 Mapbox SDK 呢?
  • Rajawali 太老了,以及文档不够
  • libGDX 是一个跨平台的引擎,然后生成的 Gradle 项目是 Native 和游戏主干分开的,我试了一会还是觉得太憋屈了
  • UrhoSharp 配合 Xamarian 差点就成功了。Urho3D 本身功能强大,但是死在了不知道怎么用 C# 绑定去把 UrhoSharp 的视图扔到 Wallpaper.Engine 里面去
  • Sceneform,谷歌亲生框架,但是目前只能在 ARCore 里面用
  • Ogre,emmm 我忘了,反正应该也是觉得用起来难受

因此,在没一个能打的情况下,Skyline 2.0 还是用了 Unity,不过这次带了 LWRP ,期许能对移动设备更友好一些。

至此,图形前端还是 Unity,但 UI 部分完全是 Android 原生了,再也不会有因为 Unity UGUI 导致的奇怪的横屏布局问题。
之前 Unity 和 Android 对于每个功能都是单独写接口做传递,维护到后面就会有很多问题,比如状态的同步,回调的处理,异步调用因为 Unity 在后台 inactive 导致的阻塞等。因此这次使用了一个全局的 JSON 同步状态。为了避免之前为了每个属性在两边都 getter / setter 写面条代码的问题,这次在 Unity 端写了一个通过 Attribute 定义属性到 JSON 路径的映射功能,运行时在解析 JSON 后直接用反射查找属性 setter 调用,方便了很多,不过也因此选不了 il2cpp 作为 scripting backend,只能使用 mono。但其实也没什么,毕竟 il2cpp 对于我来说较为突出的优点在于 arm64 支持,但 Mapbox 的 Unity SDK 内嵌了一个 SQLite Wrapper 目前是只有 x86 和 armv7 库的,因此 arm64 显得聊胜于无。总之,stay tuned,以及希望我这次不要像之前为了一个宣传片直接跳票两个月

Unity 后期处理保证背景颜色

 • 

Unity 后期处理中如果用了 Color Grading 之类的效果器同时要保证相机背景颜色不变(比如 Unity 作为一个 View 挂在手机客户端中),就要费些功夫,因为使用多相机(一个背景相机+一个实物渲染相机)仍然是无法保证相机背景不被 depth 较高的相机上的效果器处理的,因此写了个根据相机渲染深度信息来填充特定颜色,挂在 Color Grading 后面即可。

namespace FinGameWorks.Scripts.PostProcess
{
    using System;
    using UnityEngine;

    [RequireComponent(typeof(Camera))]
    [ExecuteInEditMode]
    public class MaskedFirstPass : MonoBehaviour
    {
        [Range(0f, 3f)] public float depthLevel = 0.5f;
        public Color backgroundColor = Color.white;

        public Shader _shader;

        private Shader shader
        {
            get { return _shader != null ? _shader : (_shader = Shader.Find("MaskedFirstPassShader")); }
        }

        private Material _material;

        private Material material
        {
            get
            {
                if (_material == null)
                {
                    _material = new Material(shader);
                    _material.hideFlags = HideFlags.HideAndDontSave;
                }

                return _material;
            }
        }

        private void Start()
        {
            if (!SystemInfo.supportsImageEffects)
            {
                print("System doesn't support image effects");
                enabled = false;
                return;
            }

            if (shader == null || !shader.isSupported)
            {
                enabled = false;
                print("Shader " + shader.name + " is not supported");
                return;
            }

            GetComponent<Camera>().depthTextureMode = DepthTextureMode.Depth;
        }

        private void OnDisable()
        {
            if (_material != null)
                DestroyImmediate(_material);
        }

        private void OnRenderImage(RenderTexture src, RenderTexture dest)
        {
            if (shader != null)
            {
                material.SetFloat("_DepthLevel", depthLevel);
                material.SetColor("_BGColor",backgroundColor);
                Graphics.Blit(src, dest, material);
            }
            else
            {
                Graphics.Blit(src, dest);
            }
        }
    }
}
Shader "MaskedFirstPassShader"
{
   Properties
     {
         _BGColor ("Background Color", COLOR) = (1.0,1.0,1.0,1.0)
         _DepthLevel ("Depth Level", Range(1, 3)) = 1
         _MainTex ("Source", 2D) = "white" {}
     }
     SubShader
     {
         Pass
         {
             CGPROGRAM
 
             #pragma vertex vert
             #pragma fragment frag
             #include "UnityCG.cginc"
             
             uniform sampler2D _CameraDepthTexture;
             uniform half4 _MainTex_TexelSize;
             uniform float4 _BGColor;
             uniform fixed _DepthLevel;
 
             struct input
             {
                 float4 pos : POSITION;
                 half2 uv : TEXCOORD0;
             };
 
             struct output
             {
                 float4 pos : SV_POSITION;
                 half2 uv : TEXCOORD0;
             };
 
               output vert(input i)
             {
                 output o;
                 o.pos = UnityObjectToClipPos(i.pos);
                 o.uv = MultiplyUV(UNITY_MATRIX_TEXTURE0, i.uv);
                 #if UNITY_UV_STARTS_AT_TOP
                 if (_MainTex_TexelSize.y < 0)
                         o.uv.y = 1 - o.uv.y;
                 #endif
 
                 return o;
             }
             
              sampler2D _MainTex;
                float4 _MainTex_ST;
         
             fixed4 frag(output o) : COLOR
             {
                 float depth = UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture, o.uv));
                 depth = pow(Linear01Depth(depth), _DepthLevel);
                 if (depth < 0.5)
                 {
                                float4 color = tex2D(_MainTex, 
                       UnityStereoScreenSpaceUVAdjust(
                       o.uv, _MainTex_ST));	
                       return color;
                 }
                  else
                 {
                    return     _BGColor;
                 }
             }
             ENDCG
         }
     } 
}