近期,触控科技在北京国际会议中心举办了以“挑战 变革”为主题的Cocos 2016开发者大会(春季),心动网络主程陈剑做了以“《神仙道》5年修炼的转世轮回”为主题的精彩演讲,分享了“二合一”的开发秘技,以下为演讲实录:
心动网络主程陈剑
陈剑:大家好,我先自我介绍一下,我是来自心动网络的陈剑,目前在公司负责《神仙道》项目的开发工作。《神仙道》从2011年8月立项,到目前为止已经有接近五年的时间了,从2012年上线到现在运营时间也已经超过四年。
项目起源
《神仙道》从最初上线时的1.1版本,到今年3月份已经是9.7版本,这几年中经过了非常多的版本迭代。我们一直在不断地增加游戏内容,增加游戏的玩法。
《神仙道》是一个典型的长线运营的游戏,因为它开发比较早,所以会存在一些历史遗留的问题。我这里列了几项,比如说我们游戏采用OC开发的,没法进行热更新操作,每次出现重大bug的时候都要连夜修复,赶紧提交一个版本。同时《神仙道》全部采用序列帧动画,包括人物、地图、副本,体积非常之大。今天《神仙道》老版本下架了,搜索不到。之前上一个版本安装后有600多M,非常恐怖的。在《神仙道》iOS上线后,安卓版本开始立项,但跟我们iOS版本不是同样的引擎。因为某些原因,安卓版本开发的不是很顺利。
从去年6月份开始,公司因为安卓版本开发不顺利,想弥补安卓市场的遗憾,所以立项了一个新的《神仙道》,等于把以前的《神仙道》再重新做一遍,采用新的技术,新的引擎。这个《神仙道》就叫《神仙道》2016。它有什么样的特点呢?它用的是Cocos2d-js引擎。同时我们新版大胆采用了比较新的3D功能,把目前《神仙道》的人物、怪物、NBC、伙伴全部用3D模型取代,替代以前的序列帧。现在iOS和安卓两个平台可以兼顾的,他们的代码是同一份,所以不会造成两个平台不一致的情况。
神仙道2016诞生
就在今年2月25日准备安卓和iOS两个平台一起上线的时候,我听到这么一个需求,公司说不允许苹果商店上出现两个《神仙道》。我当时心里比较着急,因为离上线只有一周的时间了,当时没有更好的办法。只好在25号让安卓版本先上,但iOS总得想一个办法,不能放弃。最后我们做了一个非常疯狂的决定,将两个游戏合体,把两个游戏做在一个包里,这就是我今天要跟大家分享的主题:二合一。
《神仙道》登陆视频
我录了一段二合一的视频,这是《神仙道》2016的登陆界面,大家可以先看一下效果。我们进到2016的登陆界面之后,首先弹出帐号登陆,然后选择心动帐号登陆,登陆有一个条件,你的心动帐号必须以前玩过老版的《神仙道》,这个时候才会在右下角出现一个入口图标,点了这个入口图标之后,就从新版进入到了老版。那具体怎么实现呢?我后面一一说明。
二合一的原理
如果要用一句话来说明实现原理其实也非常简单,就是把老版作为一个静态库编译到新版里去,这就是整个实现的原理。好,我们先来看一下旧版采用的一些技术,旧版就是2011年开始开发的,当时采用的是Objective-C版的Cocos2d引擎,并且是1.x版,后来我做了一次升级,升级到2.0版本。新版2016的话,采用的是Cocos2d-x 3.9,用JS作为开发语言,只是有些帐号分享代码用了C++做 JSB绑定,这是两个项目的一个大概情况。
这里我们看一下旧版《神仙道》的工程目录。除了官方版本的《神仙道》之外,我们工程里面还有许多其它联运的《神仙道》,比如91、UC等越狱渠道的《神仙道》,这个里面包含相当多的文件。如果来做二合一肯定要做一些改动,我没有把它做在以前的工程文件里,而是新复制了一份xcodeproj工程文件。复制之后,在项目版本库中新建了一条分支专门用来做二合一的操作。然后我们打开新复制的工程文件,新建一个静态库工程。在把这个工程复制之后,我们把左侧以前的源代码做一个整理和删除,有些不用的从项目里移除掉,大部分有用的则保留下来。
下一步我们要确保这个静态库可以编译通过,这个其实比较简单,这个静态库的源代码都是《神仙道》之前的源代码,既然之前的能编译通过的话,这个静态库也应该能够编译通过。接着我又定义一个宏用于后面预编译操作,可以屏蔽一些不需要的代码,比如第三方运营统计的库,我们可以用预编译宏屏蔽掉。
接下来我把以前的《神仙道》版本库作为一个subrepo添加到2016的版本库里面,这样有一个好处,就是旧的源代码不需要再拷贝一份,版本库中只需要保留一份引用,就可以自动跟踪两个版本库的修改。
添加进来之后,我们再次打开2016工程,然后把旧版的工程文件添加到2016里面来。添加之后,还要将旧版作为一个2016的依赖项目添加到这里。同时有一个.a文件添加到链接里。这个时候编译2016的时候会自动编译旧版的源代码。接下来就是比较复杂的,编译的时候会有各种各样的编译错误,大部分的错误是比较常见的:两个函数名同名,两个类名同名等等,解决办法也很简单。如果两份源代码是一模一样的,保留其中一份。如果两份源代码不一样,你可以去改名,或者C++的话加上命名空间等。如果遇到函数没有链接进来的话,那肯定是源代码没有编译进来。
我们把基础工作做好之后,就准备实现二合一的入口了,就是大家在视频里面看到的右下角的入口。我总结了一下有大概有这么几个步骤,第一步,我们是从2016进入老版,要清理各种资源,各种缓存,把2016的资源释放掉,因为旧版中用不到。第二步,断开网络连接。第三步,销毁各种单例,第四步,停止2016的Cocos引擎,最后调用旧版的入口。
这样讲的话,大家还不太明白,现在我们直接上代码,我们可以看到,最上面有四个,这些是2016的单例,主要是管理资源和素材的,我们会把资源释放掉。中间有一个调用AppController的end函数,这个函数会释放2016里的UIWindow和RootViewController,进入老版之后这些东西用不上。接下来最重要的一环,就是停止2016的主循环,也就是调用Director的end函数,这个函数会在下一帧将Cocos里面很多单例和缓存释放掉,同时停止JS引擎。这些做完后就是进入旧版,进入旧版非常简单,这个函数大家遇到过,叫application:DidFinishWithLaunchingWithOptions,非常长的函数,相当于iOS的入口函数,在这个时候不能指望系统去调用这个函数,我们要手动去调用它,它里面会创建新的UIWindow和RootViewController,同时会启动旧版的Cocos引擎,这个时候就完全进入旧版了。
到这个时候二合一是不是已经完成了?其实还没有。为什么我这样说?因为你现在只是说把两个版本编译进来,并且能够运行。但是后面还有很多你看不见的东西要做。有哪些,我列举了三个系统,第一个帐号系统,把旧版的游戏跟新版的合并之后,新版和老版要共用一个帐号系统,你的客户端要能进行区分,你的服务端也要进行区分。在这里如果帐号系统是你们公司自己写的那就比较容易,如果是用的第三方渠道的,碰到问题的话就比较难解决。
第二个,充值系统,既然把老版和新版进行了合并,就不存在两个游戏了。所以你进入老版之后,它的充值档位要和新版共用。
第三个是缓存目录,你的素材资源要放在哪个位置读取。我们这两个游戏做了一个目录区分,素材放在了不同目录去读取。如果你的缓存目录用的是同一个,那么下载素材后,这个新版和老版就可能有冲突,这个时候要去做一个区分和解决。
最后我花费了两周时间完成二合一,并提交到苹果商店审核。大概三天时间是在解决编译问题,最后一周多时间都是在调整帐号充值这一块。
对运营和市场的影响
把两个游戏合并之后,不仅仅对我们技术有很大的影响,对运营和市场也有非常大的影响。首先合并之后它们不再是分开的两个游戏,他们是一个完整的游戏,发布到苹果商店后就是一个安装包,就是一个2016。老版只是一个入口,你可以看成是2016的资料片。你市场做推广的时候,也只能按新的进行,因为老版已经不存在了。然后运营统计的话,这个如果要区分新老用户可能比较困难,主要看用了什么样的统计系统。第四点,我刚才已经说过了,旧版必须用新版的充值档位。
成功经验总结
二合一为什么能成功实现?我总结了这样几点,因为新版和旧版两个引擎不一样,可能大家觉得新版和旧版两个引擎混合在一起,会出问题。但恰好相反,新版和旧版不一样的话,混合在一起编译所造成的冲突反而比较少。反之如果你新版和旧版是一个引擎,但是版本号不一样的话,会出很多编译问题。
第二个,新版采用JS做开发语言,C++代码比较少,少量的C++和OC混合编译,造成冲突的风险比较小。
第三个,旧版的《神仙道》虽然有快五年的历史,但它的游戏代码主要还是一个OC的代码,接入的第三方SDK比较少,大概只接入过统计、帐号系统、微博、微信分享SDK,其它SDK非常少,只要在二合一屏蔽掉,就会减少这种编译冲突。
最后一个新老版本资源目录分开存放,不会出现新版覆盖旧版,旧版覆盖新版的情况。
《神仙道》2016于昨天十点在苹果商店上线,iOS因为某些原因,不能叫《神仙道》2016,所以改了个名字,请大家搜索:《神仙道》高清重制版。
最后说一下,其实我还有一点内容没有写到PPT里面,因为我觉得做得还不够完美。我刚才讲的这些是从新版进入到老版,其实我们也实现了老版进入新版。怎么做的呢?我利用safari做了一个中转。在老版里的设置界面点了进入2016后,会调用系统的openURL打开一个网页,接着exit结束游戏进程。这个网页只做了一件事情,就是打开sxddc://sxd2016这个地址,我在2016里配置了一个叫sxddc的scheme,这样safari就可以调起我们的游戏,从而实现了老版进入新版过程。因为不是从老版直接进入的2016,还要借助safari,所以我觉得不完美,就没写进ppt里。
想看高清的源码图片?下面是不打码的PPT下载链接,你懂的:
http://pan.baidu.com/s/1kVs6run 密码: sia8