一.前言
之前有机会接触了cocos2d-js(cocos2d-x的一个分支)相关的技术,这里就做一个回顾和讲解,会从cocos2d-x的历史架构再到cocos2d-js和2d-x的差异开始,同时也介绍下之前cocos项目的整体结构和使用到的核心知识点,也方便自己后续回顾,目前,cocos官方目前大力推荐的是cocos creator,由于之前在做项目时候cocos creator不是非常成熟,加上js语法简洁和跨平台支持等原因,再到项目中的游戏模块不是很复杂择最后了就选择使用cocos2d-js分支来开发。
如果在现阶段要开发一个cocos 游戏,大力推荐使用cocos creator,这个也是cocos官方大力推荐的,cocos2d-x引擎的更新速度已放慢,cocos2d-js已经不再更新了,creator 已经是未来开发cocos游戏的必选的工具和趋势,cocos creator 和cocos2d-x 是完全不同的两个东西,包括引擎架构和开发流程是完全不一样的,creator这里不再多说,我这边还是要介绍下cocos2d-x相关的技术知识。
二.cocos2d-x 介绍
cocos2d-x是什么
Cocos2d-x 是 MIT 许可证下发布的一款功能强大的开源游戏引擎。
允许开发人员使用 C++、Javascript 及 Lua 三种语言来进行游戏开发。
支持所有常见平台,包括 iOS、Android、Windows、macOS、Linux。
引擎特性
- 现代化的 C++ API
- 立足于 C++ 同时支持 JavaScript/Lua 作为开发语言
- 可以跨平台部署, 支持 iOS、Android、Windows、macOS 和 Linux
- 可以在 PC 端完成游戏的测试,最终发布到移动端
- 完善的游戏功能支持,包含精灵、动作、动画、粒子特效、场景转换、事件、文件 IO、数据持久化、骨骼动画、3D
市场占有
Cocos2d-x 用户不仅包括个人开发者和游戏开发爱好者,还包括许多知名大公司如 Zynga、Wooga、Gamevil、Glu、GREE、Konami、TinyCo、HandyGames、IGG 及 Disney Mobile 等。
使用 Cocos2d-x 开发的许多游戏占据苹果应用商店和谷歌应用商店排行榜,同时许多公司如触控、谷歌、微软、ARM,英特尔及黑莓的工程师在 Cocos2d-x 领域也非常活跃。
在中国,每一年的手游榜单大作,Cocos2d-x 从未缺席,市场份额占 50% 以上,游戏品类覆盖从轻度休闲,热火棋牌,到横版,SLG,重度 MMO 等市面全品类。一些以 Cocos2d-x 为基础开发出的游戏如下:
游戏引擎是一种特殊的软件,它提供游戏开发时需要的常见功能;引擎会提供许多组件,使用这些组件能缩短开发时间,让游戏开发变得更简单;专业引擎通常会能比自制引擎表现出更好的性能。游戏引擎通常会包含渲染器,2D/3D 图形元素,碰撞检测,物理引擎,声音,控制器支持,动画等部分。
Cocos2d-x 就是这样的一个游戏引擎,它提供了许多易于使用的组件,有着更好的性能,还同时支持移动端和桌面端。Cocos2d-x 通过封装底层图形接口提供了易用的API,降低了游戏开发的门槛,让使用者可以专注于开发游戏,而不用关注底层的技术细节。更重要的是 Cocos2d-x 是一个完全开源的游戏引擎,这就允许您在游戏开发过程中根据实际需要,定制化引擎的功能,如果您想要一个功能但又不知如何修改,提出这个需求,全世界的开发者可以一起为您完成。
cocos2d-x的几个基本核心概念
导演
Cocos2d-x 使用导演的概念,这个导演和电影制作过程中的导演一样!导演控制电影制作流程,指导团队完成各项任务。在使用 Cocos2d-x 开发游戏的过程中,你可以认为自己是执行制片人,告诉 导演(Director) 该怎么办!一个常见的 Director 任务是控制场景替换和转换。 Director是一个共享的单例对象,可以在代码中的任何地方调用。
如下 Director 就负责场景的转换:
场景(Scene)
在游戏开发过程中,你可能需要一个主菜单,几个关卡和一个结束场景。如何组织所有这些分开的部分?使用 场景(Scene) !当你想到喜欢的电影时,你能观察到它是被分解为不同场景或不同故事线。现在我们对游戏开发应用这个相同的思维过程,你应该很容易就能想出几个场景。
场景图(Scene Graph)是一种安排场景内对象的数据结构,它把场景内所有的 节点 (Node) 都包含在一个 树 (tree) 上。(场景图虽然叫做"图",但实际使用一个树结构来表示)。
分解这个场景,看一下它有哪些元素,这些最终会被渲染为一个树。
精灵(Sprite)
所有的游戏都有 精灵(Sprite) 对象,精灵是您在屏幕上移动的对象,它能被控制。你喜欢玩的游戏中主角可能就是一个精灵,我知道你在想是不是每个图形对象都是一个精灵,不是的,为什么? 如果你能控制它,它才是一个精灵,如果无法控制,那就只是一个节点(Node)。
看下面的图片,我们来指出一下,哪个是精灵(Sprite),哪个是节点(Node)。
动作(Action)
创建一个场景,在场景里面增加精灵只是完成一个游戏的第一步,接下来我们要解决的问题就是,怎么让精灵动起来。动作(Action) 就是用来解决这个问题的,它可以让精灵在场景中移动,如从一个点移动到另外一个点。你还可以创建一个动作 序列(Sequence) ,让精灵按照这个序列做连续的动作,在动作过程中你可以改变精灵的位置,旋转角度,缩放比例等等。
序列(Sequence)
能在屏幕上移动精灵,是制作一个游戏所需的一切,是吗?不是的,至少要考虑一下如何执行多个 Action。Cocos2d-x 通过 序列(Sequence) 来支持这种需求。
顾名思义,序列就是多个动作按照特定顺序的一个排列,当然反向执行这个序列也是可以的,Cocos2d-x 能很方便的完成这项工作。
如下是一个通过序列控制精灵移动的例子:
当然远远不止这些,除此之外还有物理引擎的支持(例如碰撞检查重力等)、音视频模块如(音乐音效支持等模块)具体可以查看官方文档
Cocos2d引擎家族介绍
cocos2d架构图
Cocos2d-JS是Cocos2d-x中的JavaScript版本,是Cocos2d-HTML5的延伸,官方对基于Web引擎的H5版本和基于Native的C++版本进行了整合,并在API层提供了统一的JavaScript API,使得Cocos开发更加容易
由于之前选用的是cocos2d-js,以下会侧重讲解js相关内容
二.快速开发一个cocos2d-js项目
项目结构
文件目录介绍
- framework
- cocos2d-html5 网页端的工程目录
- cocos2d-x cocos引擎代码目录
- runtime-src
runtime-src
-
Classes 是navtive 的app入口文件
-
proj.xxx 针对 iOS 安卓 Mac 等平台的工程目录
-
Index.html是网页端的启动文件,如果只是发布客户端可以忽略
-
main.js 是 cocos2d-js的启动入口文件,相当于 iOS 中工程的main文件
-
manifest.webapp 是一些描述信息一般用不到
- project.json 是配置项目资源的核心文件 主要包括后续js类文件的引用
- res 所有用到资源的文件目录(图片 音频 字体 动画资源等等)
- src
也就是js代码的目录 这个文件夹下有一个resource.js 文件 项目中用到的资源文件需要在此文件中引用方可加载
以上为整个工程的文件目录功能介绍
开发工具
因为cocos2d-js 是基于js来开发的,那么关于编辑器就需要用到js相关开发工具,推荐webstrom 或者 vscode ,另外如果需要用到原生相关的功能,譬如iOS 相关的,那么就需要用到xcode,如果是安卓就要用Android studio。
环境搭建&生成工程
这里拿3.17.2版本为例,下载完成后目录如下
Setup.py为环境配置脚本,由于目前此版本脚本python 要求 2.x 版本 ,我这边就不去演示了,因为我电脑是python 3.x ,有兴趣的可以自己去安装一个2.x 尝试。
官方的目录下有关于js的demo 示例 目录在 js-tests里边,有兴趣的可以逐个研究下官方demo
安装完成环境 通过 cocos new 快速生成工程模板
cocos **new** -**l** js ProjectName
模板如下
模板核心源码介绍
拿iOS 工程举例讲解
可以看到 和 iOS 新建工程类似,区别在于cocos 项目是在启动生命周期做了cocos引擎的初始化并且加载了一个GLView来渲染
scss
cocos2d::Application *app = cocos2d::Application::getInstance();
// Initialize the GLView attributes
app->initGLContextAttrs();
cocos2d::GLViewImpl::convertAttrs();
// Override point for customization after application launch.
// Add the view controller's view to the window and display.
window = [[UIWindow alloc] initWithFrame: [[UIScreen mainScreen] bounds]];
// Use RootViewController to manage CCEAGLView
_viewController = [[RootViewController alloc]init];
_viewController.wantsFullScreenLayout = YES;
// Set RootViewController to window
if ( [[UIDevice currentDevice].systemVersion floatValue] < 6.0)
{
// warning: addSubView doesn't work on iOS6
[window addSubview: _viewController.view];
}
else
{
// use this method on ios6
[window setRootViewController:_viewController];
}
[window makeKeyAndVisible];
[[UIApplication sharedApplication] setStatusBarHidden:true];
// IMPORTANT: Setting the GLView should be done after creating the RootViewController
cocos2d::GLView *glview = cocos2d::GLViewImpl::createWithEAGLView((__bridge void *)_viewController.view);
cocos2d::Director::getInstance()->setOpenGLView(glview);
//run the cocos2d-x game scene
app->run();
return YES;
}
此外initGLContextAttrs是cocos 的初始化核心方法,也就是启动js相关模块的初始化方法,核心方法都在AppDelegate.cpp类里边
同时可以看到applicationDidFinishLaunching 中的 ScriptingCore::getInstance()->runScript("main.js"); 即为启动js模块的入口类
App前后台切换暂停 重启游戏都是通过原生调用cocos引擎来完成的
这里main.js 运行起来后 就开始了游戏的初始化流程了,我这里拿我之前做的项目来展示
javascript
//load resources
cc.LoaderScene.preload(g_resources, function () {
cc.director.runScene(new SXTHomePageScene());
}, this);
即为第一个页面场景的类,在场景加载之前可以自定义的处理一些事情,如预加载资源,做屏幕适配等初始化等,后边我会详细讲下我之前项目的整体结构和大概功能点。
实战项目功能介绍
我这边会结合我之前做的项目 介绍下 cocos2d-js相关的开发 以及 cocos和 na交互能技术,包括cocos2d-js 功能代码结构等,以及 cocos 核心概念动画等。
我这边拿部分功能举例
cocos2d-js项目结构
我这边不详细介绍游戏实现,主要说下项目模块的功能梳理和框架
游戏模块
游戏网络请求
主要通过Api 实现游戏业务网络请求处理
统一通过Network 类实现网络请求处理
Cocos2d-js 网络请求用的是 cc.loader.getXMLHttpRequest() ,此类主要是封装post 和get方法和业务明确的一些状态码以及加密解密数据的逻辑
代码如下
- 请求参数处理
- 设置参数相关编码和预处理等
- 真正的请求逻辑
- 错误超时的处理
游戏部分功能模块
游戏模块使用 layer scene 和script 来分层
Layer 主要做ui的布局和交互逻辑
Scene 来做场景的切换和游戏逻辑处理 每个场景都会用一个layer来处理核心逻辑
Script 就是对一些自定义精灵 的封装
场景的切换统一封装了route
游戏列表页面
列表页面使用的是ccui.ScrollView 实现的
游戏写字模块
1.轮廓绘制
首先使用了一个开源的笔画数据,这个数据是每个文字的每一笔画的点,核心绘制笔画轮廓的部分代码
ini
//绘制轮廓
makePath: function (strokeStr) {
let strokeCommands = strokeStr.split(" ");
let offset = GC.h - this._Offset_y * this.getStrokeYRatio();
let lastPos = cc.p;
let outlineColor = cc.color(221, 113, 60);
let counter = 0;
while (counter < strokeCommands.length) {
let commend = strokeCommands[counter];
if (commend == "M") {
let x = this.scalePointX(strokeCommands[counter + 1]);
let y = this.scalePointY(strokeCommands[counter + 2]);
this._m_drawNode.drawDot(cc.p(x, offset - y), 0, outlineColor);
lastPos = cc.p(x, offset - y);
counter += 3;
}
if (commend == "Q") {
let x1 = this.scalePointX(strokeCommands[counter + 1]);
let y1 = this.scalePointY(strokeCommands[counter + 2]);
let x2 = this.scalePointX(strokeCommands[counter + 3]);
let y2 = this.scalePointY(strokeCommands[counter + 4]);
this._m_drawNode.drawQuadBezier(lastPos, cc.p(x1, offset - y1), cc.p(x2, offset - y2), 50, 1, outlineColor);
lastPos = cc.p(x2, offset - y2);
counter += 5;
}
if (commend == "C") {
let control1x = this.scalePointX(strokeCommands[counter + 1]);
let control1y = this.scalePointY(strokeCommands[counter + 2]);
let control2x = this.scalePointX(strokeCommands[counter + 3]);
let control2y = this.scalePointY(strokeCommands[counter + 4]);
let destinationX = this.scalePointX(strokeCommands[counter + 5]);
let destinationY = this.scalePointY(strokeCommands[counter + 6]);
this._m_drawNode.drawCubicBezier(lastPos, cc.p(control1x, offset - control1y), cc.p(control2x, offset - control2y), cc.p(destinationX, offset - destinationY), 50, 1, outlineColor);
lastPos = cc.p(destinationX, offset - destinationY);
counter += 7;
}
if (commend == "L") {
let x = this.scalePointX(strokeCommands[counter + 1]);
let y = this.scalePointY(strokeCommands[counter + 2]);
this._m_drawNode.drawSegment(lastPos, cc.p(x, offset - y), 1, outlineColor);
lastPos = cc.p(x, offset - y);
counter += 3;
}
if (commend == "Z") {
break;
}
}
},
2.画笔处理
通过自定义一个精灵元素,实现拖拽逻辑进行笔画绘制
ini
onTouchBegan : function (touch, event) {
this. _enableTouch = true;
var target = event.getCurrentTarget();
if (false == target._enableMoved){
return;
}
if (!target.isTouchInRect(touch)){
return false
}
target._callback();
return true;
},
onTouchMoved : function (touch, event) {
var pos = touch.getLocation();
var target = event.getCurrentTarget();
var delta = touch.getDelta();
var point = target.parent.convertToNodeSpace(cc.p(target.x,target.y));
target.setPosition(cc.p(target.x+delta.x,target.y+delta.y));
target._callback();
},
在layer层通过移动的坐标点和 之前绘制出来目标点的位置进行碰撞检测cc.rectContainsPoint,如果绘制出的目标点最后一个被吃掉视为完成写字。
游戏场景模块
主要有打地鼠,推箱子,朗诵,连线等10几种小游戏。
1.游戏音频播放
短音频
scss
//播放
this.audioId =audio.playEffect(res);
audio.setEffectsVolume(1);
//停止
cc.audioEngine.stopEffect(audioID)
长音频
用的ccui.VideoPlayer()
有些场景cocos的播放器不太好用,就通过原生封装了一个播放器,通过jsb调用实现,这里iOS 是单独用的iOS avplayer播放器,安卓用的cocos系统的,其实系统底层也是用的原生播放器。
原生模块
主要做语音评测 ,分享能力,登录能力 支付能力 以及一些竖屏用户学习数据展示列表页面等等。
原生游戏交互模块
1.核心调用是通过jsb.reflection实现的
2.这里也处理了一下多方法调用原生,并且需要原生返回处理的逻辑
譬如获取用户信息是异步的,会把js方法对象传递到native,最终端上处理完成回调到callbackmannager,通过传递生成的时间戳作对比找到回调
3.端上其实就是一个类方法实现
三.官方学习资料
总结:
以上为我在项目实战中的一些记录,有兴趣可以自己去尝试学习,希望也能对大家有一定的帮助,同时也是自己对过去的一个简要总结。