2018.1.20

 • 

出乎意料的,两天之内在 Google Play 挣了 1000+ 刀。感谢 Android Authority 介绍了我的软件,很多顾客购买了 Skyline,也提了很多意见,瞬间 Skyline 的功能排期表多了很多功能。
现在 Skyline 在美国区 Play 的游戏+软件总付费榜排第 20,应用类总付费排行榜上排第 6,个性化排行榜排第 2,当然只是暂时的,哈哈 :-)。


根据用户反馈,下一步 Skyline 的 Roadmap:

  • Better Swiping Gesture
  • Skyline Presents (already in dev)
  • Toggle For Animations

学校的考试考完了,下学期还有一些要重修。
我经常想和我妈表达一个意思,我对学校的态度,实际上是一个博弈的过程,因为是博弈,所以这中间没有什么反悔的,也没有什么“早知道”一类的话。我通过了解学校往年对我这类翘课学生的态度,估量自己的手段,评估自己是否能在毕业的前提下最大化我自己的独立发展,这中间总会有突然的变化,评估的失误,因此也会导致不尽人意的后果,比如学校取消了清考,导致我不得不在大四下跟着完整的课程走而延期毕业。这中间还有很多隐晦的,学校和学生之间的惯例,就不一一列举了。总而言之,因为大环境变严,很多原本模糊的地界开始变得严厉分明,我的下学期不得不跟流程着走了。

上学期最迷的莫过于生产实习了,写了整个班唯一一个完整功能带 GUI 的测试工具,结果成绩只是及格,后来经过崇哥点拨才想起来:生产实习结束后要写实习报告,套路是“我遇到了什么问题-我请教老师/和同学攻坚克难-我学到了什么-感谢老师提供机会+很多字”,而我当时的思路还在在百度写周报的状态,上来就写“我做了什么项目/实现了哪些功能/我对自己比较满意+完”。而且批阅人并不是实习公司的 Leader,而是学校这边偶尔来一次的带队老师。


想起两个月前见吴总聊起了不起的盖茨比。我一直觉得盖茨比真正了不起的一点是,他知道这个世界的困难和不公,但是并没有失去动力和憎恨世界,而是积极地投入其中,接受和利用规则,爬到顶端,然后再去实现他自己的理想。某些层面上看,他是最单纯的人。
现在再想起来,再抱着这个想法去审视周围的人,会突然觉得:我之前觉得他人做法欠妥当,但我是没有这个权利的,毕竟我何尝不也是一个穷尽一切可能走上去的人呢。


延期毕业

 • 

延期毕业的事情基本确凿了。近代史这门课是大三下学期的,当时在北京,因为没怎么在学校上课所以平时分比较低被挂了,类似的还有数据库实验课,提议说检查自己的个人项目也是被拒,因此挂了。学校对大四学生没有清考,只能跟着下一届的人上下半学期的课到下学期 16 周结课,在那之前就已经要发毕业证书了,所以只能延期。
延期对我的影响大致就两个,一个是腾讯的入职会比较麻烦,少了说会延期一到两个月,这期间只能办理实习生入职,等到拿到证书后才是正式员工和正式工资,多了说估计要半年。另一个就是事务上的,要花一些额外的时间对付学校的流程和课程。

Unity WebGL Canvas 窗口自适应

 • 

Unity WebGL 一个很奇怪的事情就是默认的模板没有默认充满窗口的选项,这个问题貌似可以在 WebGL Templates 里面纯 css 解决,不过我还是更偏向于 js 一些。
以下代码 Unity 2017.3.0f3 测试可用。

// CanvasResizerPlugin.jslib
var CanvasResizerPlugin =
  {
    CanvasResizerCheckBegin: function () {
      
      function ResizeCanvas ()
      {
        try {
          document.getElementById("gameContainer").style.width =
            (window.innerWidth
              || document.documentElement.clientWidth
              || document.body.clientWidth) + "px";
          document.getElementById("#canvas").width = window.innerWidth
            || document.documentElement.clientWidth
            || document.body.clientWidth;
          document.getElementById("gameContainer").style.height =
            (window.innerHeight
              || document.documentElement.clientHeight
              || document.body.clientHeight) + "px";
          document.getElementById("#canvas").height = window.innerHeight
            || document.documentElement.clientHeight
            || document.body.clientHeight;
        }
        catch (err) {
          console.log(err);
        }
      }
      document.body.style.margin = "0px";

      window.addEventListener('resize', function (event) 
      {
        ResizeCanvas();
      });
    }
  };
mergeInto(LibraryManager.library, CanvasResizerPlugin);
// WindowResizerManager.cs
public class WindowResizerManager : Singleton<WindowResizerManager> 
    {

        [DllImport("__Internal")]
        private static extern void CanvasResizerCheckBegin();
 
        private void BeginResizeUpdating () 
        {
#if UNITY_WEBGL && !UNITY_EDITOR
			CanvasResizerCheckBegin ();
			#endif
        }

        private void Start()
        {
            try
            {
                BeginResizeUpdating();
            }
            catch (Exception e)
            {
                Console.WriteLine(e);
            }
        }
    }

效果:

2017.12.29

 • 

Updates on Skyline:

  • 之前提到的 Skyline Landing Page 的问题已经修好了。访问链接
  • 视频也做了一个基本的。仍然不是我想要的样子,不过至少比用 Element3D 渲染出来的要好很多,至少我是认为目前 Element3D 是无法做到大规模的植被/建筑物渲染的。目前这个视频是先在 Google Cloud 的一台 8 CPU 机器上用 3dsMax + Vray 渲染了 30 个小时,然后在我的 PC 上用 Cinema4D + Octane Render 渲染了一天,然后丢到 AE 里加了后期效果和文案之后做完的。访问链接
  • 在做视频的时候用了会 FStorm Render,在 3dsMax 上的表现感觉比 Octane 好太多了,至少材质转换器非常精准,不会出现转换的材质完全不对的情况。
  • 在写 Manual。目前完成了 隐私权协议

Updates on Me:

  • 毕设题目定了,看了其他做应用的人的毕设题目后发现自己还是定太高目标了。不过无所谓,反正自己做的这个课题后面会对 Epoch 有很大用处。
  • 陪喵喵抓了我们两个抓娃娃记录里面第一个娃娃。
  • 之前去了趟北京,正式从百度离职了。等待下一次去的时候能和带我入行的人和解。
  • 填写了腾讯的入职调查表。
  • 大学的事情,毕业,考试,实验报告。有的时候在想如果没办法毕业要怎么办。

2017.12.12

 • 

最近在做的个人项目:

Skyline Live Wallpaper

其实已经上架,Google Play 链接也已经有 Public Release 了,只不过有很多要收尾的事情:

  • 定价:USD 3.99 对我来说也的确有点贵了,当时只是填写上架表单的时候随便定的一个。考虑到 Mapbox 的 API 价格和我的预期收入,最终价格会在 1.99 刀左右 (1.49 至 2.49)。
  • Bug Fix:有一个已知的 Bug,不过貌似没头绪,因为代码看起来都是对的。除此以外用最新的 Unity 版本 Build 会导致 Mapbox SDK 工作出现问题,只能继续留着 Unity 2017.1.0f3。
  • 官网:官网其实也已经写好了:ProjectSkylineLandingWebGL,用了 three.js。具体内容是在浏览器端动态从 Mapbox API 获取特定经纬度周围的九张高度图+色彩图,然后在浏览器端做高度计算和 Mesh Deform,然后用一个摄像机拍摄地形渲染到 rendertarget 输出到另外一个摄像机拍摄的三星手机模型的屏幕材质上。这一系列工作在浏览器端做完可想而知,在 iOS Safari 上直接爆内存,Android Chrome 上也要等待很长时间。后期要做的就是放弃运行时过程生成这种看起来很酷但一点用都没有的做法(我对过程生成一直有不知道为什么的执念),然后改为预先 bake 好的贴图和地形 obj,这样稳定性和速度上都会好很多。
  • 用户手册+隐私协议:我个人的小项目一直没有写隐私协议来着,不过既然之前有准备了一个在线的用户手册(ProjectSkylineManual),顺带加上罢。
  • 宣传视频:大概一个月前就已经在做了。当时用 AE+Element3D 做了一个简单的手机移动到相机面前,看完效果觉得不爽。遂想做一个创意广告片,类似于在 YouTube 上能看到的那种脑洞广告。于是用了 3dsMax,C4D,Octane Render 等软件捣鼓,可惜的是我的唯一一台 Windows PC 内存不足以加载大场景。而网络渲染的话 Octane 需要 N 卡,3dsMax 需要 Windows 平台,两台 MBP 就毫无用处。渲染农场的效率还可以,不过价格太高了。我也可以在 Google Cloud 或者其他云上开一台高性能的虚拟机然后全程 VNC 到上面操作,不过这个想法暂时还没有实施。到现在只有零星的结果出来。还在做,如果在再做不出来我估计就要套一个类似于苹果 “Don't Blink” 的 AE 模板了,不能再耗时间了。
    一点结果
  • PR 准备:主要在忙做视频了,PR 部分没准备啥,其实我脑子里的 PR = 发到 Product Hunt 上然后盯自己的排名,其他的也想不出来。或许可以给国外的 Android 新闻站(AA,AP)和国内的应用推荐网站自荐下,不过至少要等到圣诞节之后啦。

啰嗦了这么多,发几个 Promo Code:

CU2G32Y7NA4SZ12D8XZS9SL
QLL17JKB6PLU8QWGWDCSKXK
0QFV3FSRB55AY3E08J071QK
RCV8DGUH7ET7CGHQPW041CJ
PY9BZS7RXY3E36UWEA3PZYZ
HN3D31976B3PWAFA9LB6Y67
YL96RKN1X9C4NPN66BAZX1D
48YYFFA77GSEZ4SPM0P4XHN
VS8YNCMSGSBJXUR3ZRPECCL
SMP4J6DL9JVRP8CLV3LZPQU

目前的版本内存占用会有些大,缩放的设定有的时候会丢,其他都一切正常。

Flint Expense Tracker

一个缺钱的程序员,因为银行的消费短信不告诉余额,经常陷入不知道还有多少钱的惶恐,又不想开银行专用 app,所以在写一个跨平台 app:

  • 可以接入邮箱账号从里面用正则读取银行账号发来的消费提醒自动计算余额
  • 可以 Dropbox 同步存储了消费记录的 Realm 数据库
  • 可以在今日挂件(iOS/macOS)和桌面挂件(Android)上看自己还有多少存款

Lonely Planet For macOS

简单移植下,没有什么特别的功能更新。移植完成后再往下就是对 Epoch 的高度图生成脚本大修改下,更新下 LOD 系统。之前提到的 Depth Culling 系统我实际写起来发现貌似是个先有鸡还是先有蛋的问题,不了了之。

一个外包

没啥新奇的,因为缺钱。

2017.11.24

 • 

最近世事挺乱的。
好长时间没写了,这次更新下关于校招的进程,回头再更新下生活相关的。
距离上一篇的这一个半月内,投了一些公司,最终面了以下公司:
饿了么,Musical.ly,LeanCloud,滴滴,腾讯。
有正面结果的是 Musical.ly 和腾讯,然后加上百度提前批和一个学术性的 offer,当时我的选择总共有这些:

职位 公司/学校 部门 地点
iOS & Unity 腾讯 增值产品部 深圳
Unity 为主 百度 智能家居部门 北京
iOS Musical.ly 上海
PhD / RA 香港城市大学 CS 香港
  • Musical.ly 是我觉得比较神奇的一家公司,毕竟是国内团队做到国外市场前五的,在我心中觉得这么大用户量,进去后应该能真的去学到什么东西。他们的技术面算比较顺利,HR 也对我很好,和我约了一个终面。不过估计当时他们同时在招很多人,因此约我的时间比较靠后了。等到我去上海参加 HackxFDU 顺便终面的时候,自己已经敲定了另外一家。
  • 香港 cityU 对我来说是一个很有吸引力的地方,特别是在现在国内这种魔幻的状态下,更显得珍贵。我当时也考虑了很久,不过专业方向有可能会不是最喜欢的,因此对自己贸然读 PhD / RA 有些发怵。自己肯定还是要出国的,有可能工作几年就出,争取留下来。
  • 百度那边是大二就认识的同一批人马,有感情。不过团队也在演化,有走有留。关于我对留在百度的疑问和我今年三月考虑要不要去小米的时候仍然一样,就是我觉得我还是要去看用户量很多的团队是怎么做事,怎么走流程的。如果当前用户比较少,那么有可能自己有很多事情最终并不明白,也不能证明。
  • 腾讯,一面表现很好,二面表现一般。做的东西和游戏相关,但不是游戏。换一家公司试试应该不错,反正还年轻。签了。

除此以外对于其他公司的一些印象:

  • 听说头条今年给钱很多。头条的笔试很多算法,做了半小时就放弃了。然后后来听说头条把 Musical.ly 买了。差点就第二次变相入职。
  • 饿了么是先找朋友内推的,还在正常批次上报了名。技术面了四次,然后说会有人联系我的。然后就没有消息了,然后 HR 通知我走正常批次的笔试,所以我估计自己是内推的批次上挂了 hhhh。
  • LeanCloud 问的比较深。被婉拒(“目前该职位的实习岗位尚未开放”)。
  • 滴滴一面很顺利,然后紧接着二面问了算法,跪了。
  • 在 Linkedin 上的 Unity Technologies 2018 校招投了不下五六次,没有一次回复。虽然没邮件回复我审核没过是一个很正常的事情,毕竟我的简历估计在他们看来很渣,但是这样会让我怀疑他们的网页表单估计是假的。对 Unity 还是有执念的。

2017.10.9

 • 

在家呆了几天,精神终于放松了些,慢慢也想通了些事情。
我一直没有认真去融入到大学生活里去,传统宣传口径下的国内大学生活无非是前三年青春年少和最后一年踏入社会,看起来朝气蓬勃,但那种所谓的被外界觉得青春年少的大学生活,对于我来说是一半太无聊一半太奢侈了。所以为了我最后一年能相对轻松地踏入社会,或者不如从一开始我就觉得社会比学校更有趣些,因此我前三年一直照着自己的路线做事情。
结果到了大四我突然发现,已经没有所谓体验踏入社会的机会了。学校课很多,马原毛概大四才上,再加上毕设等都是毕业的关键,我只能一直呆在学校里。而面试我的公司到后面一致问我什么时候有空去实习,我只能说学校这边事情较多去不了,然后几家公司都渐渐没了消息。至此我突然有种感觉自己虽然上了所大学,但是从来没有真正体验过所谓的大学生活,连实习找工作熟悉新生活这种大学生活的结尾都被迫狼狈地夹在各种学校的事物中匆匆结尾,周围的同学大部分找到一家不需要实习的公司就赶紧签了三方,然后生活照常继续。而我偶尔去上马原和毛概,把电脑放在腿上偷偷写自己的项目和外包,过着自己并不熟悉的要上课的生活。
而我想尝试新的机会,但现在是我性价比最低的时候:之前大二大三有大把时间翘课,能给实习工资做出来还不错的东西。而现在留在学校没时间去实习,最快入职也要等到毕业后,一入职就是应届生工资水平,很多公司宁愿要两个可以立刻入职拿较低的实习工资的人来代替我,一方面可以慢慢熟悉业务,另一方面大公司很多时候也并不需要一个设计和技术都吃一点的应届生,不像初创公司缺人,来一个又能做三维又能写代码的人很划算,大公司里拿我放哪里都尴尬。
明知这种状况下几乎没啥公司会要我,但之前我还是想去投各种公司,因为我总觉得,靠着之前所在公司的提前批 offer 就不参加其他招聘的话,就是连最后一年也这么安静地过去了,大学四年是真的没有体验过了,这种事情上我觉得还是需要些仪式感的,即使面试的公司到后来都没消息。

放松了点后我想通了些。既然都这个样子了,不如呆在学校写我的 Epoch,没有更好的选择的话就安心签之前公司的 offer,然后准备出国。至于我现在的这个大学,毕业了以后我是再也不想有关联了。

2017.9.19

 • 

学校的生产实习结束了。关于生产实习没什么好说的,就是用 python 写黑盒测试,每天要去市内一个培训班。闷热(不开空调),没网(意味着没 pip),外加没有 IDE 的一排电脑。
实习老师提供了一些基础的文档,自己遇到了一些文档没说的坑,不过都还好。后来带着自己的笔记本跑着 Windows 虚拟机过去写。怎么说我也是做工程向的,这种项目上相对其他同学总会有些优势,最后答辩也很顺利。
这期间面了一些公司,做了一些笔试。之前在百度的经历让我转正容易很多,我暂时能确定的也只有这个了,但我想去其他公司和其他城市看看,所以也没有停止投递。投了 Unity 的上海校招,不过没消息,估计是不符合吧。
越是面试越是在说项目经验的时候觉得自己最近产出的东西怎么这么匮乏,但学校的事情在可见的几个月内不会变少,同时我认识的一些朋友都在做非常有趣的事情,比如 AR Camera,比如 RSS Reader,让我有点羡慕别人。
因为忙着学校和校招,我自己的几个软件和游戏都没有进一步更新的计划。GitHub Contributions 的 ARKit 初步集成效果不是很好被我先暂停了。Cetacea 编辑器我想进行第三次重写,从 NSRegularExpression 改为 CMark,从基于 iCloud 的自定义 NSDocument 改为基于任意云平台的 plain text 储存,但开始时间未定。Epoch 的进展也就是加上了能在 runtime 用的云层,其余内容我也不知道何时有时间实现出来。这让我有一种自己停止生产内容的感觉,我多想一天到晚只写自己的东西啊。


和 Kevin 聊天的时候我说我想做 Art,不管是软件,游戏,还是 VFX,只要有我的风格我都想做。这让我的简历有点尴尬:我同时在做 Cocoa 和 Unity 这两个技术栈,写过点 Android 和 Rails。这导致我在每个平台上的深度都没有到面试官期望的程度,然后我还做过 UI Design,Adobe AE,MODO,Substance Painter 以及其他奇奇怪怪的东西,但这些对于技术面试并没有什么太大用处。
我的博客也是技术和生活混杂在一起的,虽然我一直以来都觉得挺好,毕竟本来就是自己的博客嘛,但面试的时候面试官想找我的技术文就会有些尴尬。不过我暂时没有分拆的想法,估计以后也不一定会有吧。

Epoch Dev Log 9

 • 

上一篇 Epoch Dev Log 还是去年九月。一年过去由于实习,其他项目,以及懒,Epoch 本身并没有进展多少,不过 Unity 引擎自身倒是多了很多功能,我得以突破之前的一些问题。
首先,Epoch 的云层终于加上了,不过现有的 Ray Marching 部分还是一个 Cube,我需要后面改为一个 Sphere 的表达式。其次,得益于 Instacing API,大规模的 SpeedTree 在没有物理(风速)的影响下是可以重新实现一遍了。

接下来要实现的是升级之前的 Noise Shader。之前的 Shader 实现是靠 C# 对 Shader SetFloatArray 插入了一个 Lookup Table,而在老版本的 Unity 部分是有这个长度限制的,因此当时的 Lookup Table 从 LibNoise 的 1024 压缩到了 768。但我的新实现将完全抛弃 Lookup Table,也尽量独立于 LibNoise 的做法,这样 HeightMap 生成时间会进一步缩短。
以及基于 DepthMap 的 Runtime Culling,这个之前看到过一些 Paper 有说过,据说 BattleField 4 也有一个小分辨率的 DepthMap 用来做这个事情。但我预计要做的肯定没那么复杂,就是在某个 Layer 上的所有 GameObject 依附的 Renderer 如果在相机的 Z 轴百分比大于 DepthMap 即设置为非活跃。不过这个做法有两个隐藏的担忧:1. Mesh 过多的话是不是每 Update 检测 Depth 消耗的时间会大于省去的时间 2. Depth 的 Float 精度对于 zMin = 0.1 zMax = 99999 的游戏是否足够。

UniOSIntegrationKit

 • 

UniOSIntegrationKit 是在做之前的一个外包的时候面临需求临时写的一些代码和脚本的集合。之前给 Unity 写 iOS 插件,基本上都是 Unity 为主包,iOS 代码放在 Plugins 文件夹内。但是如果有一个现成的 iOS 项目需要插入 Unity 部分,则问题就比较多了,主要有这几点:

  • Unity Xcode 工程是代码生成,每次都会覆盖
  • Unity Xcode 工程自身有一个 AppDelegate
  • 直接把 Unity Xcode 文件拖入 iOS Xcode 工程会导致结构特别乱

UniOSIntegrationKit 主要解决的是第一和第二个问题,即在不改动 Unity Xcode 生成工程的前提下,使用各类方法(Method-Swizzling,Build Script)在外部对 Unity 的行为进行改动,避免每次重新生成 Unity Xcode 工程的时候需要重新修改。
有关 iOS 和 Unity 项目如何接入可以去看 Integrating Unity3D with native iOS application for Xcode 7 & Unity 5,这里的很多思路都是从程序的方式上去把这个视频做的事情自动化地做一遍。

NSObject+UnitySwizzling 是用来做 Method-Swizzling 的,不过也包括了对于 UnityWindow 的操作。Unity 的 AppDelegate 称之为 UnityAppController,如果进入看可以发现 .mm 文件里有 didFinishLaunchingWithOptions 等 delegate 方法的实现。而 iOS 本身自己已经有了一个 AppDelegate 文件,则在保留 iOS 的文件同时要把 Delegate 事件(软件进入后台,软件即将结束等)传给 Unity,所以使用 Method-Swizzling,关于此可以看 nshipster.cn,这里就直接给代码了,作用是在 iOS 本身的 AppDelegate 实现里都加上发给 UnityAppController 的代码。

首先,把 iOS 工程自带的 main.mm 替换为 unity 自带的 main.mm,然后修改

const char* AppControllerClassName = "AppDelegate"; // iOS 工程自己的实现了 AppDelegate 的那个类的名字

然后可以把 unity 的 main.mm 删除了,当然也可以直接删除 iOS 的 main.mm 修改 Unity 的,自己决定。

NSObject+UnitySwizzling.h

#import <Foundation/Foundation.h>
#import "AppDelegate.h"


@interface AppDelegate ()
@property(strong, nonatomic) UnityAppController *unityAppController;
@end

@interface AppDelegate (UnitySwizzling)

+ (NSMutableArray *)getUnityOriginalList;
+ (NSMutableArray *)getUnitySwizzlingList;
+ (NSMutableDictionary *)getUnitySwizzlingDict;

- (UIWindow *)getUnityWindow;
- (void)showUnityWindow;
- (void)hideUnityWindow;
@end

NSObject+UnitySwizzling.m

//
//  NSObject+UnitySwizzling.m
//
//  Created by Fincher Justin on 25/6/17.
//  Copyright © 2017年. All rights reserved.
//
#import <objc/runtime.h>
#import "NSObject+UnitySwizzling.h"


#import "AppDelegate.h"

#import "UnityAppController.h"
#import "UnityMessageCenter.h"


@implementation AppDelegate (UnitySwizzling)

+ (NSMutableArray *)getUnityOriginalList
{
    return [NSMutableArray arrayWithObjects:
            @"application:didFinishLaunchingWithOptions:",
            @"applicationWillResignActive:",
            @"applicationDidEnterBackground:",
            @"applicationWillEnterForeground:",
            @"applicationDidBecomeActive:",
            @"applicationWillTerminate:",
            nil];
}
+ (NSMutableArray *)getUnitySwizzlingList
{
    NSMutableArray *array = [[AppDelegate getUnityOriginalList] mutableCopy];
    for (int i = 0; i < [array count]; i ++)
    {
        NSString *originalString = (NSString *)[array objectAtIndex:i];
        NSString *swizzledString = [NSString stringWithFormat:@"xxx%@", originalString];
        [array replaceObjectAtIndex:i withObject:swizzledString];
    }
    return array;
}
+ (NSMutableDictionary *)getUnitySwizzlingDict
{
    return [NSMutableDictionary dictionaryWithObjects:
            [AppDelegate getUnitySwizzlingList]
                                              forKeys:
            [AppDelegate getUnityOriginalList]];
}

- (UIWindow *)getUnityWindow
{
    return UnityGetMainWindow();
}


- (void)showUnityWindow
{
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        [[UnityMessageCenter sharedManager] runCommand:@"OnUniShow" WithParameter:@""];
        [[self getUnityWindow] makeKeyAndVisible];
    }];
    
}

- (void)hideUnityWindow
{
    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
        [[self window] makeKeyAndVisible];
    }];
}

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        Class class = [self class];
        
        NSMutableDictionary *dict = [AppDelegate getUnitySwizzlingDict];
        NSArray* keys = [dict allKeys];
        
        for (NSString *originalSelectorString in keys)
        {
            NSString *swizzledSelectorString = dict[originalSelectorString];
            NSLog(@"Method Swizzling From %@ to %@ ",originalSelectorString,swizzledSelectorString);
            
            SEL originalSelector = NSSelectorFromString(originalSelectorString);
            SEL swizzledSelector = NSSelectorFromString(swizzledSelectorString);
            
            Method originalMethod = class_getInstanceMethod(class, originalSelector);
            Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
            
            BOOL didAddMethod =
            class_addMethod(class,
                            originalSelector,
                            method_getImplementation(swizzledMethod),
                            method_getTypeEncoding(swizzledMethod));
            
            if (didAddMethod) {
                class_replaceMethod(class,
                                    swizzledSelector,
                                    method_getImplementation(originalMethod),
                                    method_getTypeEncoding(originalMethod));
            } else {
                method_exchangeImplementations(originalMethod, swizzledMethod);
            }
        }
        
    });
}

#pragma mark - Method Swizzling

- (BOOL)xxxapplication:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
    NSLog(@"xxxapplication:didFinishLaunchingWithOptions:launchOptions: Begin");
    [self xxxapplication:application didFinishLaunchingWithOptions:launchOptions];
    [self hideUnityWindow];
    
    self.unityAppController = [[UnityAppController alloc] init];
    [self.unityAppController application:application didFinishLaunchingWithOptions:launchOptions];
    
    NSLog(@"xxxapplication:didFinishLaunchingWithOptions:launchOptions: End");
    [self hideUnityWindow];
    return YES;
}

- (void)xxxapplicationWillResignActive:(UIApplication *)application
{
    [self.unityAppController applicationWillResignActive:application];
    [self xxxapplicationWillResignActive:application];
}


- (void)xxxapplicationDidEnterBackground:(UIApplication *)application
{
    NSLog(@"xxxapplicationDidEnterBackground: Begin");
    [self.unityAppController applicationDidEnterBackground:application];
    [self xxxapplicationDidEnterBackground:application];
}


- (void)xxxapplicationWillEnterForeground:(UIApplication *)application
{
    NSLog(@"xxxapplicationWillEnterForeground: Begin");
    [self.unityAppController applicationWillEnterForeground:application];
    [self xxxapplicationWillEnterForeground:application];
}


- (void)xxxapplicationDidBecomeActive:(UIApplication *)application
{
    NSLog(@"xxxapplicationDidBecomeActive: Begin");
    [self.unityAppController applicationDidBecomeActive:application];
    [self xxxapplicationDidBecomeActive:application];
}


- (void)xxxapplicationWillTerminate:(UIApplication *)application
{
    NSLog(@"xxxapplicationWillTerminate: Begin");
    [self.unityAppController applicationWillTerminate:application];
    [self xxxapplicationWillTerminate:application];
}
@end

UniOSIntegrationKitScript 是一个 Build Script,作用是将 UnityAppController.h 里的这段:

inline UnityAppController*  GetAppController()
{
    return (UnityAppController*)[UIApplication sharedApplication].delegate;
}

改为

inline UnityAppController*  GetAppController()
{
    return (UnityAppController*)[UIApplication sharedApplication].unityAppController;
}

原因是当我们使用 iOS 工程本身的实现了 AppDelegate 的文件后,对于 Unity Xcode 工程来说 [UIApplication sharedApplication].delegate 就不再是 UnityAppController 了,而 [UIApplication sharedApplication].unityAppController 才是,这是因为我们在之前的文件里声明了:

@interface AppDelegate ()
@property(strong, nonatomic) UnityAppController *unityAppController;
@end

UniOSIntegrationKitScript.sh

#!/bin/sh

#  UniOSIntegrationKitScript.sh
#
#  Created by Fincher Justin on 27/6/17.
#  Copyright © 2017年. All rights reserved.

set -x

echo "UniOSIntegrationKit"
find $SRCROOT/../ -name "UnityAppController.h"
find $SRCROOT/../ -name "UnityAppController.h" -print0 | xargs -0 sed -i "" "s/return (UnityAppController\*)\[UIApplication sharedApplication\]\.delegate;/return ((AppDelegate *)[UIApplication sharedApplication]\.delegate)\.unityAppController;/"

基本上问题一和二都差不多解决了,至于问题三我原本想把整个 Unity 部分打成一个 Pod,不过外包一直在拖,我也开学了,就没再继续下去。等什么时候还有这类集成 Unity 到 iOS 的项目再说吧。