从 0 到 1:iOS 软件集成小游戏功能

导语

本文将从 iOS 工程角度出发,简要介绍 iOS 软件应该如何从 0 到 1 实现内嵌小游戏功能。

效果展示

效果展示

方案选型

经过前期的调研,主流的方案有微信小游戏、Canvas、Cocos2d,下面逐个分析一下这几个方案:

  • 微信小游戏:微信小游戏是闭源的,没有公开 SDK,直接 Pass;
  • Canvas:Canvas 方案对应 Cocos Creator 的发布到 Web 平台章节。本质上就是将游戏跑在浏览器上。这个方案可以说是最完善的方案,可以对游戏的全过程拥有更为完整的把控,但是需要 web 同事的协同开发 ,而且 web 同事的开发工作会成为最长路径,所以 Canvas 方案比较适合大公司且有足够的需求开发时间的情况下选择;
  • Cocos2d:Cocos2d 方案对应 Cocos Creator 的打包发布原生平台章节。底层是用原生平台调用 OpenGL 进行渲染的(cocos2d-x v4 已经支持了 Metal 渲染)。Cocos2d 有现成的解决方案,基本上只需要客户端即可完成,不需要 web 同事的参与,同时原生渲染性能比较好,比较适合人手不充足或者需要快速上线验证收益的情况;

我们根据自己的实际情况最终选定了 Cocos2d 方案。

整体流程

整体流程

当我们点击一个小游戏的时候,会有以下几个主要步骤:

  1. 对该小游戏进行预处理,预处理主要是当前游戏状态的检查(未在游戏状态则直接进入下一步、正在游戏状态则需要先关闭当前游戏再进入新的游戏等),预处理都通过则进入步骤 2;
  2. 展示小游戏加载页,同时从服务端拉取游戏信息 gameInfo,拉取游戏信息成功进入步骤 3;
  3. 根据游戏唯一标识 appId 为 key 查找游戏包缓存,如果游戏包已经缓存则进入步骤 5;
  4. 根据服务端下发的下载链接下载指定版本的游戏包,同时回调下载进度到加载页进行展示,下载成功后对游戏包进行 md5 校验,校验通过则进入步骤 5;
  5. 传入游戏包地址调用 Cocos2d 启动游戏,等 Cocos2d 渲染好第一帧画面然后将游戏控制器推入视图堆栈,并将状态切换为游戏中。

工程结构

工程结构

主工程

主工程也即是业务层,和小游戏模块是需要完全解耦的,游戏的状态转换不应该依赖业务层,业务层也不应该知晓过多的游戏内部细节。两者的交互只会发生在 MiniGameManager 类,MiniGameManager 需要做到低耦合、高内聚,下面先看一下 MiniGameManager 的主要接口:

#pragma mark - 小游戏相关通知
 
MiniGameWillStart       // 小游戏即将启动通知
MiniGameStartFailed     // 小游戏启动失败通知
MiniGameDidStart        // 小游戏启动成功通知
MiniGameWillEnd         // 小游戏即将结束通知
MiniGameDidEnd          // 小游戏结束通知
 
@interface MiniGameManager : NSObject
 
- (instancetype)sharedManager;
 
/// 游戏状态
@property (nonatomic, assign, readonly) MiniGameStatus gameStatus;
 
/// 启动游戏
/// @param gameAppid 游戏 appId
- (void)startGame:(NSString *_Nonnull)gameAppid;
 
/// 结束游戏,清理游戏相关资源
- (void)endGame;
 
@end
  • 启动游戏:理论上只需要传入游戏 appId 就能启动对应的小游戏,其他诸如视图堆栈处理、游戏状态处理、资源包下载解压、游戏运行等都由小游戏模块内部处理。
  • 结束游戏:只需要调用 endGame 方法即可,游戏相关资源的清理、视图堆栈处理等都由小游戏模块内部处理。

为了更好的解耦,主工程(业务层)只会通过通知的方式得知小游戏的状态转换,从而进行对应的处理,比如启动游戏时停止音频的播放、关闭有冲突的其他业务等。

MiniGame

MiniGame 是负责业务层和小游戏底层交互的中间层,囊括了游戏账号体系、生命周期、资源包管理、网络请求、支付、广告、分享、心跳、上报等模块。

以游戏账号体系为例简述这一层的职责,我们会接收到来自下一层 Interface 层的事件回调,事件回调通过 delegate 的方式进行解耦。以下是账号体系相关 delegate 的部分代码:

@protocol CocosAccountProtocol <NSObject>
 
@required
 
// 登录回调
- (void)login:(int)timeout success:(SuccessCallBack)successBlock fail:(FailureCallBack)failureBlock;
 
// 获取用户信息回调
- (void)getUserInfoWithSuccess:(nonnull GetUserInfoSuccessCallBack)successBlock fail:(nonnull FailureCallBack)failureBlock;
 
@end

当接收到 Interface 层的事件回调时,我们校验对应的参数,然后由客户端向服务端发起对应请求,请求成功则调用 successBlock 回调给游戏侧成功回调事件,否则调用 failureBlock 进行失败回调。其他的模块对应的处理不外如是。

Interface

Interface 主要分为两部分:

  1. CocosRootViewController 负责提供承载游戏的视图(CCEAGLView),这部分是 iOS 独有的;
  2. 接收到游戏侧发出的 JS 事件,通过 BussinessManagerFactory 区分 iOS、Android 平台进行事件分发,iOS 端会分发到 BusinessiOSImpl 类,BusinessiOSImpl 再往上抛给 MiniGame 层进行实际的事件处理,处理完成后会通过回调把结果传回给游戏侧。示意可以参考下图:
Interface

Cocos2d

游戏引擎选择的是 Cocos2d,Cocos2d 是一个开源的跨平台游戏框架,也是目前最流行的游戏引擎之一。Cocos2d 适配了 iOS、Android、Windows 和 Mac 系统,功能侧重于原生移动平台。

我们目前使用的其实是 Cocos2d 的简化版 cocos-2d-lite
,cocos2d-x-lite 移除了 3D 特性、Linux 平台支持、摄像头等,更加小巧。

我们在 Cocos2d 层的开发工作,是为小游戏提供扩展能力,包括账号、生命周期、支付、广告、分享等。扩展能力的接口设计主要参考的是微信小游戏

说回代码,Cocos2d 层我们所做的事情比较机械,主要就是绑定 JS 事件然后往 Interface 层抛。值得一提的是,这一层的代码是两端共用的,纯 C++,。下面贴一下绑定分发 JS 事件的代码,其他接口可以按这种方式快速完成:


// 绑定 login 事件
SE_DECLARE_FUNC(js_login);
 
// 定义 login 事件的处理
static bool js_login(se::State &s)
{
    const auto &args = s.args();
    if (args.size() != 1) {
        SE_REPORT_ERROR("wrong number of argument: %d", (int)args.size());
        return false;
    }
 
    if (!args[0].isObject()) {
        SE_REPORT_ERROR("wrong type of argument: %d", (int)args[0].getType());
        return false;
    }
 
    auto obj = args[0].toObject();
    auto param = std::make_shared<LoginParam>(obj, s.thisObject());
    // 往上抛到 BusinessFactory 进行事件分发
    BusinessFactory::get()->login(param->getTimeout(),
                                  std::static_pointer_cast<LoginCallback>(param));
    return true;
}
SE_BIND_FUNC(js_login)

结语

Cocos2d 方案相比起其他方案而言更加适合我们,使我们得以快速上线验证。但是在实际的测试过程中,我们也发现 Cocos2d 官方其实并没有支持游戏重入,也就是游戏大厅的功能是不支持的,会导致游戏重入的时候会有一些内存泄漏问题和 crash,我们也花了相当一部分时间去解决这些问题。这也更加提醒我们,在方案选型的阶段应该更加细致,应该做出一个 demo 包实际的测试帧率、内存、CPU、GPU 等性能数据,才可以尽可能早地暴露问题并解决。

以上

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335