一次让刷新控件好玩起来的尝试
写在前面的话
虽然我所处的团队与业务开发息息相关,但近一年,我个人已经很少写一些业务代码了,做的事情可能更偏向基础技术的建设,技术栈也从 Objective-C 转向了 JavaScript 的世界。
然而,我内心还是想写点 Swift 相关的东西,写点与 iOS 相关的东西,或许可能我内心是真的很热爱它们吧。
所以年底的时候,我又重新捡起了 Swift 这门语言 ,并在 瓜瓜 的公众号上持续输出一些与此技术相关的文章,大多是一些使用技巧和语法细节。而我因为工作原因,经常会在 Objective-C,JavaScript,TypeScript,Ruby,Python 之间来回切换,不得不说 Swift 的综合体验还是可以的,除了 Xcode。
没错! 它就是 Swift Tips 系列,如果你感兴趣,记得关注我们的公众号 - 让技术一瓜共食
不过,光研究语言本身,是枯燥的,也不立体,我还是想把 Swift 用到一些具体的场景上,毕竟玩起来才有意思!
于是我想起了自己早年间立的一个 flag:尝试一次游戏开发。
所以我迅速下单买了 Ray Wenderlich 家的《2D Apple Games by Tutorials》一书,虽然是 Swift 4 和 iOS 11 的版本,但说实话 Swift 4 到 5 的变化算比较温和,另外教材里的 iOS 系统也只落后现实世界里两个版本,SpriteKit 整体也没有什么翻天覆地的变化,落后的内容补上两个季度的 WWDC 就好了,所以,学就完事了!
看完这本书后,我不得不说老外的教材真的很好,只是可惜这书以后不再更新了。
大概花了一个月的时间,陆陆续续的把《2D Apple Games by Tutorials》里面的代码和作业也都敲了一遍,自我感觉还不错,问题就来了,怎么把学的东西用起来呢?
一开始我是想做个策略回合制的游戏,与战场女武神,皇家骑士团的游戏类型相似。说实话,在开动之前,我对自己还挺有信心的,因为有做游戏开发的想法,所以很早就有为自己培养一些与游戏相关的技能点,例如原画绘制,音乐制作等,下面是我自己的一些小作品。
但真的进入开发状态后,我发现事情并不像自己想的那么简单……
这里我就不展开说了,只说说核心观点吧,我理解游戏的本质还是在讲故事,至于音乐,原画,动效,编程都是为这个故事而服务的,如果没有一个好的故事,游戏也就失去了灵魂。
IDEA 的由来
在意识到自己并没有一个好故事可以说的状态下,我决定把新 get 到的开发技能用到自己的工作场景中,可问题就是怎么把一个轻量级的游戏与日常的 App 结合起来呢?
我脑海里突然想起来了 Chrome 浏览器里的小恐龙!
说实话,我并不清楚做这个 Feature 的初衷是什么,但作为一名用户,在无网的时候玩上一会这样的游戏,既可以转移我的注意力,又可以缓解我无法上网的焦虑感。
所以从用户体验的角度上来说,这是一个很不错的 IDEA!而它很好的启发了我!
带着改善用户体验的想法,我反复观察了自己和周围好朋友使用 App 的习惯。我发现使用户焦虑感增强的几个操作无外乎都是围绕页面刷新的,例如:
- 下拉刷新时转动的指示器
- Web 页面的加载条
- 下载资源时的进度条
- 刷新失败或者无网状态下的空白页
不过从以上操作的频率来看,下拉刷新应该是最频繁的,也是最常见的。
所以决定了,就是你了!让我们做一个更好玩的下拉刷新控件吧!
- 从体验角度的考量
在关于下拉操作,国内的应用主要有两种交互形式,一种是以淘宝,京东为代表的“二楼形态”,另一种是以美团,拼多多为代表的“经典形态”,
“经典形态”就不用多说了,“二楼形态”的本质是通过下拉跳转到另一个页面,当然在这个下拉的过程中可以触发刷新,不过我认为这并不是一个必要的过程,在我的理解范围里,微信的下拉操作其实也是一个“二楼形态”,只不过它没有刷新,只是通过下拉跳转到了小程序选择界面。
所以,如果仅仅从这两种传统的思路下手来改进用户体验,提升的空间是十分有限的。
这时候我发现手机版本的 Chrome 在下拉交互上是有点与众不同的!
我发现它的下拉操作是可以与使用者进行更细粒度的交互,这一点很重要!这让我意识到它会赋予下拉刷新很多新的可能性!
试想一下,如果我们在下拉刷新的同时,能玩到小时候 FC 机上的公路战士(Road Fighter),或者曾经风靡一时的手游 —- 神庙逃亡(Temple Run)会是何种体验!
当然,在下拉操作里玩塞尔达,无双系列也不是不可能,但体验未必好,因为那种游戏的交互实在太复杂了,不是一个手指左滑右划就能完成的,但像公路战士和神庙逃亡这一类游戏的交互就很简单,逻辑也足够轻量,放在下拉刷新的交互中也显得比较合适。
从体验角度上来说,作为用户,一旦触发下拉刷新就可以玩一把轻松有趣儿的小游戏,让自己从等待网络数据的焦虑中解脱出来,听起来还不错吧?
- 从技术角度的考量
作为一名程序员,我们总要需要考虑代码上的改动会带来哪些好的变化,哪些不好的变化。
如果真的在下拉刷新里做个小游戏,从某种程度上来说,它一定会增加工程的复杂性,说不定还会影响到项目的包大小,首页的加载时长,FPS 等性能指标或者业务指标。
那么它能带来哪些好的变化呢?
在回答这个问题之前,我们先看看国内这些年客户端技术的大趋势,在国内,大部分电商都在做动态化相关的事儿,从 RN,Weex,Flutter,再到一些自研的技术,例如天猫的 Tangram,手淘的 Dinamic,美团的 Flexbox 等。
简单来说,它们的出发点可能都是希望自己的 App 做到不依赖客户端发版,就能实现内容上或者逻辑上的更新。但从原理上来说,它们的思路大体都是下发一个布局描述文件,然后绑定实际的业务数据,最终通过渲染引擎变成我们所看到的界面。
不过这也就带来了一些变化,原先的后台只需要下发视图所展示的数据,例如 UILabel 中的内容,而动态化方案不仅要下发 UILabel 展示的内容,还需要下发 UILable 自身的描述信息。这种变化会使整体传输的数据量迅速变大,甚至会使得网络请求的次数变多(一次请求业务数据,一次请求布局文件)。
在性能监控或者业务监控的指标中,页面的加载时长是一种十分常见的指标。但动态化方案因为多了一次转换的过程,所以在某种程度上,它会使这个指标发生恶化的。
虽然我们会做很多事情来优化加载时长,并改善这一指标,但所有的优化都是有尽头的,而且根据二八原则,这样优化在后期的投入产出比是十分有限的,开发者绞尽脑汁的做了一堆优化,但用户可能并不会感知到这 1-2 ms 的优化,反而让代码的维护成本加大了不少。
那么我们能不能从另一个角度出发,例如通过一些引导来转移用户的注意力,从而让他们忽略掉这些加载时长?
刚好,前面提到的可交互的游戏式的下拉刷新,它就满足了转移用户注意力的诉求。如果需要一些指标衡量它,我们可以通过下面的方式来观察和推导:
建立两个时间指标,一个是刷新页面所需的时长,从发起网络请求,到页面刷新完毕的总时间 ,T-Net,另一个是用户下拉刷新后的游玩时长,从触发游戏开始,到用户结束游戏的总时间,T-Game。
对于 T-Game - T-Net > 0 的用户, 我们可以假设他们对加载时长不敏感,针对这种情况,我们可以通过在游戏里植入红包或者其他方式增加用户的留存,并改善它们的用户体验。
对于 T-Game - T-Net < 0 的用户,我们可以假设他们对加载时长十分敏感,那么我们就可以对这些用户做有针对性的优化。例如从智能预加载或者推荐策略上入手。
所以从这个角度来看,可交互的游戏式的下拉刷新在技术层面也有了实际的价值和意义!
- 从产品角度的考量
在大公司待久了,你总要面对这样一个问题,这个需求到底有什么意义?它能带来哪些实际的利益,能为我们这个部门赚钱么?
这个 feature 同样会面临这样的问题,于是我仔细的思考了自己所处的业务线和业务形态。(PS: 下面的这些思考是我个人的理解,不用过分解读)
我所在的部门负责了国内某电商平台的首页,很像京东,手淘的首页业务,它自身并不包含一个完整的交易链,承担的首要任务是流量分发,为公司内其他业务线带来用户和持续增加的可能性。
说白了,在我的理解范围内,首页就像一个展览厅,为了让展览厅里面的商品卖得更好,我们通常会有两种思路:
第一种:在最合适的位置向顾客展示最合适的商品。 第二种:尽可能的增加展位,展示更多的商品
如果将这个思路放到电商平台的首页模块中,我们也可以看到它们的踪影,例如手淘技术文章里经常提到的千人千面,它本质上就是一种优化推荐策略的方案,它可以看做是第一种方式的变体,而近年来,很多 App 从原先的单卡片流变成双列卡片流,它就是第二种策略的变体,在同样的空间内增加了展示位置,提升了展示密度。
之前我们也说过下拉刷新的两种形态,“二楼形态”就是将下拉刷新作为了一个入口,为 App 内的其他内容进行导流,它本质上是属于第二种策略的。
因为在“经典形态”下,下拉刷新是不能作为一个入口的,而“二楼形态”的出现,使得下拉刷新的操作可以进入到其他页面,这就在有限的空间里增加了更多的展位。
在这个前提下,我们再来看下可交互的游戏式下拉刷新,它与二楼形态很接近,当游戏结束后,它完全能够作为一个入口,例如玩完了某个品牌的红包雨活动后跳转到某个指定频道,另外在游戏过程中,又可以结合一些页面上的元素进行品牌宣传,增加曝光量,例如红包雨里面的背景图,红包的品牌等。
总体来说,相比于传统的“二楼形态”,这种玩法让下拉刷新变得更有“价值”了!
冷静的分析
在通过用户角度,技术角度,产品角度三个方面分析后,我认为,这个事儿是有搞头的!但我相信我肯定不是第一个想到这个点子的人,所以我迅速在 Google 上搜索了一下,果然不出所料,Github 上已经有人做过类似的 Demo,这里刚好它们做一些介绍,也说说我的个人观点。
- BreakOutToRefresh
这个 Demo 里的游戏实现使用的 SpriteKit 框架完成的,支持 Swift 和 Objective-C。它的 Github 链接是 https://github.com/dasdom/BreakOutToRefresh。 它基本符合了我需要的两个核心功能点,一个是可交互的,一个是游戏化的。
但它实际的游戏体验并没有达到我的预期,主要有以下几个问题:
- 游戏的交互是上下移动的,这和触发刷新的下拉操作有一定相似性,这就导致误操作的几率增大。
- 游戏中的交互元素体积较小,物体的碰撞感和操作性都比较差,使得游戏的体验并不理想。
- 即使是这种打方块的游戏,它的逻辑在下拉刷新里也显得略微复杂,尤其在小球弹到边界时,会出现一些奇怪的不符合逻辑的现象,例如小球弹到右边界时会返回而不是游戏结束。
当然 BreakOutToRefresh 的作者 dasdom 提到了它的这个 Demo 又是源于 boztalay 的 BOZPongRefreshControl,不过可惜的是 BOZPongRefreshControl 并不是可交互的,我们看到的效果可以简单的理解为是已经预置好的动画,所以这里也就不展开说明了。
至于其他实现,我在市面上还真是再也没找到了,如果你有什么发现,请记得给我留言!
思考,思考,再思考
在有了上面的思考后,我开始着手设计自己理解的那种可交互的,游戏化的下拉刷新组件。
- 关于交互方式
在 iOS 系统内,常见的手势有以下几种:
结合到下拉刷新的场景中,我们会发现 Swipe,Pinch,Rotation,Screen Edge Pan 等操作并不合适,相对合适的可能就只剩 Tap,Pan 和 Long Press 了。为了不那么反人类,我们应该避免游戏里的交互操作与刷新的下拉操作发生冲突。
在结合上面几点后,我们发现游戏里操作精灵的方式就只能在左右滑动和点按中选择了。
当然,你可以发明一些新的手势,但这里就不展开讨论了!
- 关于游戏内容
在确定了可交互的方式后,我们需要进一步讨论游戏的呈现形式,也就是游戏的内容。在之前的调研中,我们得出了游戏的交互逻辑不能过于复杂,且要避免精灵的体积过小,不知道你脑海里想到了哪些游戏?
在我的脑海里,马上就想起了小时候玩的红白机,例如魂斗罗,超级马里奥,热血物语等,但说实话,这些8 Bit 的游戏还是有太复杂了,放到下拉刷新里显得太重太重了,不光是游戏逻辑,就连交互也有很大的区别,毕竟咱们应该都还都记得 “上上 下下 左左 右右 AB AB” 的秘籍!
所以,我们应该把时间再往前推一推,回到雅达利时代的游戏机上!
怎么样,看起来够复古了吧!
雅达利游戏机本身的操作方式十分简单,一个方向键,一个按钮,与我们现在仅有的左右滑动和点按操作相似,另一点是那个时代的游戏逻辑轻量简单,这里我们举几个例子:乒乓,太空侵略者,吃豆人等,所以,我们可以完全借鉴这些当年的雅达利游戏机上的经典作品来实现自身的诉求。
当然,为了让游戏能更接地气,也就是更好的服务到自身的业务中,我这里整合几个实际的游戏场景好了!
例如临近双十一,淘宝要派发红包了!
例如正在使用滴滴打车时,可以让等待中的用户模拟一把正在赶路的司机!
又例如在下单点外卖的时候,我们可以来一把太空大战!
Show Me The Code
在有了上面的设想后,我以抢红包的点子为例开发一个真实的 Demo,这里我并没有像 BreakOutToRefresh 那样把代码抽象成一个 SDK,因为我的初衷是希望大家将这个思路应用到自己的 App 中,而不是那些代码。
这里我也不会对细节做过多的讲解,主要是做一些笼统的介绍和分析,具体的示例代码请移步到我的 Repo 中查看。如果你觉得这个项目很有意思,记得给我一个 Star 哦!
Github 地址:https://github.com/SketchK/a-playable-refresh-demo
- SpriteKit
因为之前自学的就是 2D 游戏开发,所欲这里我毫不犹豫的选择了苹果自己的开发框架 SpriteKit,至于语言方面,我选择了 Swift,完全是个人喜好。
示例代码中的核心文件包含以下几个部分:
这几个文件承载了游戏的核心逻辑,而其中最最最重要的两个文件就属 GameScene 和 GameView,这里我们先说 GameScene。
这个场景下主要有两个类型的精灵,一种是红包精灵,它在游戏开始后,会不断的从界面上方出现,并逐渐掉落到界面的下方,这里使用了 SKAction 来完成相应的工作,而另一种是英雄精灵,它在游戏里只有一个并在界面初始化的时候被创建,根据手势的左右偏移量进行移动,这里我使用了较为传统的手动计算方式。
这个类型的游戏核心点就是碰撞问题,一方面是英雄与游戏界面的碰撞,这里我们要做到英雄精灵的活动范围不能超出游戏界面,它的逻辑放在了 update 这个生命周期方法中,另一方面就是英雄与红包的碰撞,我们需要通过这个检测它们之间的碰撞来让游戏继续进行和结束,这一部分的逻辑放在了 didEvaluateActions 中。
- 刷新机制
至于 GameView ,它承担了两个重要的工作,一个是刷新控件的逻辑,一个是游戏界面的切换逻辑,这两个工作有一定的内在联系,你可以拆分成两个模板,也可以整合到一个模块中,就像代码里的做法,我的方案并不是一个最佳解决方案。
另外刷新机制这块,其实这个 Demo 做的并不完美,如果想深入了解,我建议大家看看社区里一些优秀的下拉刷新组件库,这里你只需要了解游戏界面的切换逻辑是与刷新控件相关联的就好。
尾声
看到这里,这篇文章就要结束了,可能干货也不是那么多,主要说了说我在编写这个 Demo 时都想了什么,做了什么,干了什么。
很遗憾的是,我无法将这个想法用在自家的 App 中,但我十分希望它能被用起来,并可以观察到它的实际上线效果。如果有人这么做了,欢迎与我联系讨论!
项目的代码地址为:https://github.com/SketchK/a-playable-refresh-demo,如果你觉得它还不错,请记得给我一个 Star 哦!
最后祝大家新年快乐!