Cocos Creator 是什么?
它是一个完整的游戏开发解决方案,包括了 cocos2d-x 引擎的 JavaScript 实现,以及能让你更快速开发游戏所需要的各种图形界面工具,在 Cocos2d-x 基础上实现了彻底脚本化、组件化和数据驱动等特点。
初次创建并打开一个 Cocos Creator 项目后,您的项目文件夹将会包括以下结构:
ProjectName(项目文件夹)
├──assets (资源文件夹:游戏中所有本地资源、脚本和第三方库文件)
├──library(资料库:将 assets 中的资源导入后自动生成的,不需要版本控制)
├──local(包含该项目的本地设置,包括编辑器面板布局,窗口大小,位置等信息)
├──settings(settings 里保存项目相关的设置,如 构建发布 菜单里的包名、场景和平台选择等)
├──temp(临时文件)
├──project.json(project.json 文件和 assets 文件夹一起,作为验证 Cocos Creator 项目合法性的标志,必须有)
└──build(构建目标:构建的微信小程序等项目)
cc.Class({
extends: cc.Component,
properties: {
},
// use this for initialization
onLoad: function () {
},
// called every frame, uncomment this function to activate update callback
update: function (dt) {
},
});
cc.Class 是一个很常用的 API,用于声明 Cocos Creator 中的类,为了方便区分,我们把使用 cc.Class 声明的类叫做 CCClass。
调用 cc.Class,传入一个原型对象,在原型对象中以键值对的形式设定所需的类型参数,就能创建出所需要的类。
var Sprite = cc.Class({
name: "sprite"
});
以上代码用 cc.Class 创建了一个类型,并且赋给了 Sprite 变量。同时还将类名设为 “sprite”,类名用于序列化,一般可以省略。
Sprite 变量保存的是一个 JavaScript 构造函数,可以直接 new 出一个对象:
var obj = new Sprite();
需要做类型判断时,可以用 JavaScript 原生的 instanceof:
cc.log(obj instanceof Sprite); // true
使用 ctor 声明构造函数:
var Sprite = cc.Class({
ctor: function () {
cc.log(this instanceof Sprite); // true
}
});
var Sprite = cc.Class({
// 声明一个名叫 "print" 的实例方法
print: function () { }
});
使用 extends 实现继承:
// 父类
var Shape = cc.Class();
// 子类
var Rect = cc.Class({
extends: Shape
});
通过在组件脚本中声明属性,我们可以将脚本组件中的字段可视化地展示在 属性检查器 中,从而方便地在场景中调整属性值。
要声明属性,仅需要在 cc.Class 定义的 properties 字段中,填写属性名字和属性参数即可,如:
cc.Class({
extends: cc.Component,
properties: {
userID: 20,
userName: "Foobar"
}
});
在多数情况下,我们都可以使用简单声明。
当声明的属性为基本 JavaScript 类型时,可以直接赋予默认值:
properties: {
height: 20, // number
type: "actor", // string
loaded: false, // boolean
target: null, // object
}
当声明的属性具备类型时(如:cc.Node,cc.Vec2 等),可以在声明处填写他们的构造函数来完成声明,如:
properties: {
target: cc.Node,
pos: cc.Vec2,
}
当声明属性的类型继承自 cc.ValueType 时(如:cc.Vec2,cc.Color 或 cc.Rect),除了上面的构造函数,还可以直接使用实例作为默认值:
properties: {
pos: new cc.Vec2(10, 20),
color: new cc.Color(255, 255, 255, 128),
}
当声明属性是一个数组时,可以在声明处填写他们的类型或构造函数来完成声明,如:
properties: {
any: [], // 不定义具体类型的数组
bools: [cc.Boolean],
strings: [cc.String],
floats: [cc.Float],
ints: [cc.Integer],
values: [cc.Vec2],
nodes: [cc.Node],
frames: [cc.SpriteFrame],
}
properties: {
score: {
default: 设置属性的默认值,这个默认值仅在组件第一次添加到节点上时才会用到
type: 限定属性的数据类型,详见 CCClass 进阶参考:type 参数
visible: 设为 false 则不在 属性检查器 面板中显示该属性
serializable: 设为false 则不序列化(保存)该属性
displayName: 在属性检查器 面板中显示成指定名字
tooltip: 在属性检查器 面板中添加属性的 Tooltip
}
}
var node = this.node;
//获取单个组件
var label = this.getComponent(cc.Label);
//获取多个组件
var labels = this.getComponents(cc.Label);
可以在属性检查器 中拖拽设置你需要的对象或组件
//获取孩子节点数组
var childs= this.node.children;
//根据名字获取孩子节点
this.node.getChildByName("Cannon");
this.backNode = cc.find("Canvas/Menu/Back");
window代表了当前浏览器的单例对象
window.Global = {
backNode: null,
backLabel: null,
};
定义全局变量时的 window. 不可省略。
接着你可以在合适的地方直接访问并初始化 Global:
初始化后,你就能在任何地方访问到 Global 里的值:
// Back.js
cc.Class({
extends: cc.Component,
onLoad: function () {
Global.backNode = this.node;
Global.backLabel = this.getComponent(cc.Label);
}
start: function () {
var text = 'Back';
Global.backLabel.string = text;
}
});
访问全局变量时,如果变量未定义将会抛出异常。
添加全局变量时,请小心不要和系统已有的全局变量重名。
你需要小心确保全局变量使用之前都已初始化和赋值。
// Global.js, now the filename matters
module.exports = {
backNode: null,
backLabel: null,
};
每个脚本都能用 require + 文件名(不含路径) 来获取到对方 exports 的对象。
// Back.js
// this feels more safe since you know where the object comes from
var Global = require("Global");
cc.Class({
extends: cc.Component,
onLoad: function () {
Global.backNode = this.node;
Global.backLabel = this.getComponent(cc.Label);
}
start: function () {
var text = "Back";
Global.backLabel.string = text;
}
});
目前提供给用户的生命周期回调函数主要有:
onLoad
onEnable
start
update
lateUpdate
onDestroy
onDisable
除了通过场景编辑器创建节点外,我们也可以在脚本中动态创建节点。通过 new cc.Node() 并将它加入到场景中,可以实现整个创建过程
start: function () {
var node = new cc.Node('Sprite');
var sp = node.addComponent(cc.Sprite);
sp.spriteFrame = this.sprite;
node.parent = this.node;
},
有时我们希望动态的克隆场景中的已有节点,我们可以通过 cc.instantiate 方法完成。使用方法如下:
cc.Class({
extends: cc.Component,
properties: {
target: cc.Node,
},
start: function () {
var scene = cc.director.getScene();
var node = cc.instantiate(this.target);
node.parent = scene;
node.setPosition(0, 0);
},
});
和克隆已有节点相似,你可以设置一个预制(Prefab)并通过 cc.instantiate 生成节点。使用方法如下:
cc.Class({
extends: cc.Component,
properties: {
target: cc.Prefab,
},
start: function () {
var scene = cc.director.getScene();
var node = cc.instantiate(this.target);
node.parent = scene;
node.setPosition(0, 0);
},
});
通过 node.destroy() 函数,可以销毁节点。值得一提的是,销毁节点并不会立刻被移除,而是在当前帧逻辑更新结束后,统一执行。当一个节点销毁后,该节点就处于无效状态,可以通过 cc.isValid 判断当前节点是否已经被销毁。
使用方法如下:
cc.Class({
extends: cc.Component,
properties: {
target: cc.Node,
},
start: function () {
// 5 秒后销毁目标节点
setTimeout(function () {
this.target.destroy();
}.bind(this), 5000);
},
update: function (dt) {
if (cc.isValid(this.target)) {
this.target.rotation += dt * 10.0;
}
},
});
在 Cocos Creator 中,我们使用场景文件名(不包含扩展名)来索引指代场景。并通过以下接口进行加载和切换操作:
cc.director.loadScene("MyScene");
cc.director.loadScene("MyScene", onSceneLaunched);
当切换场景时,该组件所在节点标记为「常驻节点」,使它在场景切换时不被自动销毁,常驻内存。
//设置为常驻节点
cc.game.addPersistRootNode(myNode);
//取消常驻节点
cc.game.removePersistRootNode(myNode);
Creator 中,所有继承自 cc.Asset 的类型都统称资源,如 cc.Texture2D, cc.SpriteFrame, cc.AnimationClip, cc.Prefab 等。它们的加载是统一并且自动化的,相互依赖的资源能够被自动预加载。
动态加载资源要注意两点,
一是所有需要通过脚本动态加载的资源,都必须放置在 resources 文件夹或它的子文件夹下。resources 需要在 assets 文件夹中手工创建,并且必须位于 assets 的根目录。、
二是 Creator 相比之前的 Cocos2d-JS,资源动态加载的时候都是 异步 的,需要在回调函数中获得载入的资源。这么做是因为 Creator 除了场景关联的资源,没有另外的资源预加载列表,动态加载的资源是真正的动态加载。
Creator 提供了 cc.loader.loadRes 这个 API 来专门加载那些位于 resources 目录下的 Asset,和 cc.loader.load 不同的是,loadRes 一次只能加载单个 Asset。调用时,你只要传入相对 resources 的路径即可,并且路径的结尾处 不能 包含文件扩展名
// 加载 Prefab
cc.loader.loadRes("test assets/prefab", function (err, prefab) {
var newNode = cc.instantiate(prefab);
cc.director.getScene().addChild(newNode);
});
// 加载 AnimationClip
var self = this;
cc.loader.loadRes("test assets/anim", function (err, clip) {
self.node.getComponent(cc.Animation).addClip(clip, "anim");
});
图片设置为 Sprite 后,将会在 资源管理器 中生成一个对应的 SpriteFrame。但如果直接加载 test assets/image,得到的类型将会是 cc.Texture2D。你必须指定第二个参数为资源的类型,才能加载到图片生成的 cc.SpriteFrame:
// 加载 SpriteFrame
var self = this;
cc.loader.loadRes("test assets/image", cc.SpriteFrame, function (err, spriteFrame) {
self.node.getComponent(cc.Sprite).spriteFrame = spriteFrame;
});
对从 TexturePacker 等第三方工具导入的图集而言,如果要加载其中的 SpriteFrame,则只能先加载图集,再获取其中的 SpriteFrame。这是一种特殊情况。
// 加载 SpriteAtlas(图集),并且获取其中的一个 SpriteFrame
// 注意 atlas 资源文件(plist)通常会和一个同名的图片文件(png)放在一个目录下, 所以需要在第二个参数指定资源类型
cc.loader.loadRes("test assets/sheep", cc.SpriteAtlas, function (err, atlas) {
var frame = atlas.getSpriteFrame('sheep_down_0');
sprite.spriteFrame = frame;
});
cc.loader.releaseRes("test assets/image", cc.SpriteFrame);
cc.loader.releaseRes("test assets/anim");
Cocos Creator 支持加载远程贴图资源,开发者直接调用 cc.loader.load。同时,如果用户用其他方式下载了资源到本地设备存储中,也需要用同样的 API 来加载,上文中的 loadRes 等 API 只适用于应用包内的资源和热更新的本地资源。
// 远程 url 带图片后缀名
var remoteUrl = "http://unknown.org/someres.png";
cc.loader.load(remoteUrl, function (err, texture) {
// Use texture to create sprite frame
});
// 远程 url 不带图片后缀名,此时必须指定远程图片文件的类型
remoteUrl = "http://unknown.org/emoji?id=124982374";
cc.loader.load({url: remoteUrl, type: 'png'}, function () {
// Use texture to create sprite frame
});
// 用绝对路径加载设备存储内的资源,比如相册
var absolutePath = "/dara/data/some/path/to/image.png"
cc.loader.load(absolutePath, function () {
// Use texture to create sprite frame
});
事件处理是在节点(cc.Node)中完成的。对于组件,可以通过访问节点 this.node 来注册和监听事件。监听事件可以 通过 this.node.on() 函数来注册,方法如下:
cc.Class({
extends: cc.Component,
_sayHello: function () {
console.log('Hello World');
},
onEnable: function () {
//监听事件
this.node.on('foobar', this._sayHello, this);
},
onDisable: function () {
//关闭监听
this.node.off('foobar', this._sayHello, this);
},
start () {
// 发送监听,最多传递5个参数
this.node.emit('foobar', arg1, arg2, arg3);
},
});
Cocos Creator 支持的系统事件包含鼠标、触摸、键盘、重力传感四种
// 使用枚举类型来注册
node.on(cc.Node.EventType.MOUSE_DOWN, function (event) {
console.log('Mouse down');
}, this);
// 使用事件名来注册
node.on('mousedown', function (event) {
console.log('Mouse down');
}, this);
枚举对象定义 | 对应的事件名 | 事件触发的时机 |
cc.Node.EventType.MOUSE_DOWN | mousedown | 当鼠标在目标节点区域按下时触发一次 |
cc.Node.EventType.MOUSE_ENTER | mouseenter | 当鼠标移入目标节点区域时,不论是否按下 |
cc.Node.EventType.MOUSE_MOVE | mousemove | 当鼠标在目标节点在目标节点区域中移动时,不论是否按下 |
cc.Node.EventType.MOUSE_LEAVE | mouseleave | 当鼠标移出目标节点区域时,不论是否按下 |
cc.Node.EventType.MOUSE_UP | mouseup | 当鼠标从按下状态松开时触发一次 |
cc.Node.EventType.MOUSE_WHEEL | mousewheel | 当鼠标滚轮滚动时 |
枚举对象定义 | 对应的事件名 | 事件触发的时机 |
cc.Node.EventType.TOUCH_START | touchstart | 当手指触点落在目标节点区域内时 |
cc.Node.EventType.TOUCH_MOVE | touchmove | 当手指在屏幕上目标节点区域内移动时 |
cc.Node.EventType.TOUCH_END | touchend | 当手指在目标节点区域内离开屏幕时 |
cc.Node.EventType.TOUCH_CANCEL | touchcancel | 当手指在目标节点区域外离开屏幕时 |
键盘、设备重力传感器此类全局事件是通过函数 cc.systemEvent.on(type, callback, target) 注册的。
cc.Class({
extends: cc.Component,
onLoad: function () {
// add key down and key up event
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
cc.systemEvent.on(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
},
onDestroy () {
cc.systemEvent.off(cc.SystemEvent.EventType.KEY_DOWN, this.onKeyDown, this);
cc.systemEvent.off(cc.SystemEvent.EventType.KEY_UP, this.onKeyUp, this);
},
onKeyDown: function (event) {
switch(event.keyCode) {
case cc.macro.KEY.a:
console.log('Press a key');
break;
}
},
onKeyUp: function (event) {
switch(event.keyCode) {
case cc.macro.KEY.a:
console.log('release a key');
break;
}
}
});
Cocos Creator 提供的动作系统源自 Cocos2d-x,API 和使用方法均一脉相承。动作系统可以在一定时间内对节点完成位移,缩放,旋转等各种动作。
// 创建一个移动动作
var action = cc.moveTo(2, 100, 100);
// 执行动作
node.runAction(action);
// 停止一个动作
node.stopAction(action);
// 停止所有动作
node.stopAllActions();
// 让节点左右来回移动
var seq = cc.sequence(cc.moveBy(0.5, 200, 0), cc.moveBy(0.5, -200, 0));
node.runAction(seq);
// 让节点在向上移动的同时缩放
var spawn = cc.spawn(cc.moveBy(0.5, 0, 50), cc.scaleTo(0.5, 0.8, 1.4));
node.runAction(spawn);
// 让节点左右来回移动,并重复5次
var seq = cc.repeat(
cc.sequence(
cc.moveBy(2, 200, 0),
cc.moveBy(2, -200, 0)
), 5);
node.runAction(seq);
// 让节点左右来回移动并一直重复
var seq = cc.repeatForever(
cc.sequence(
cc.moveBy(2, 200, 0),
cc.moveBy(2, -200, 0)
));
// 让目标动作速度加快一倍,相当于原本2秒的动作在1秒内完成
var action = cc.speed(
cc.spawn(
cc.moveBy(2, 0, 50),
cc.scaleTo(2, 0.8, 1.4)
), 0.5);
node.runAction(action);
cc.tween 会比 cc.Action 更加简洁易用,因为 cc.tween 提供了链式创建的方法,可以对任何对象进行操作,并且可以对对象的任意属性进行缓动。
cc.tween(this.node)
//同时对 position, rotation 属性缓动
.to(1, { position: cc.v2(100, 100), rotation: 360 })
//1秒后,开始缩放
.to(1, { scale: 2 })
.start()
Cocos Creator 中,我们为组件提供了方便的计时器,这个计时器源自于 Cocos2d-x 中的 cc.Scheduler,我们将它保留在了 Cocos Creator 中并适配了基于组件的使用方式。
下面来看看它的具体使用方式:
开始一个计时器
component.schedule(function() {
// 这里的 this 指向 component
this.doSomething();
}, 5);
上面这个计时器将每隔 5s 执行一次。
更灵活的计时器
// 以秒为单位的时间间隔
var interval = 5;
// 重复次数
var repeat = 3;
// 开始延时
var delay = 10;
component.schedule(function() {
// 这里的 this 指向 component
this.doSomething();
}, interval, repeat, delay);
上面的计时器将在10秒后开始计时,每5秒执行一次回调,重复3次。
只执行一次的计时器(快捷方式)
component.scheduleOnce(function() {
// 这里的 this 指向 component
this.doSomething();
}, 2);
上面的计时器将在两秒后执行一次回调函数,之后就停止计时。
取消计时器
开发者可以使用回调函数本身来取消计时器:
this.count = 0;
this.callback = function () {
if (this.count === 5) {
// 在第六次执行回调时取消这个计时器
this.unschedule(this.callback);
}
this.doSomething();
this.count++;
}
component.schedule(this.callback, 1);
Creator 里的物理系统包括两个部分:碰撞系统和刚体系统,(刚体系统使用box2d,里面也有碰撞,我们简称刚体碰撞和碰撞系统中的碰撞不同,且两者不兼容使用);
获取碰撞检测系统
var manager = cc.director.getCollisionManager();
默认碰撞检测系统是禁用的,如果需要使用则需要以下方法开启碰撞检测系统
manager.enabled = true;
默认碰撞检测系统的 debug 绘制是禁用的,如果需要使用则需要以下方法开启 debug 绘制:
manager.enabledDebugDraw = true;
碰撞系统回调
/**
* 当碰撞产生的时候调用
* @param {Collider} other 产生碰撞的另一个碰撞组件
* @param {Collider} self 产生碰撞的自身的碰撞组件
*/
onCollisionEnter: function (other, self) {
console.log('on collision enter');
// 碰撞系统会计算出碰撞组件在世界坐标系下的相关的值,并放到 world 这个属性里面
var world = self.world;
},
properties: {
collider: cc.BoxCollider
},
start () {
// 开启碰撞检测系统,未开启时无法检测
cc.director.getCollisionManager().enabled = true;
this.collider.node.on(cc.Node.EventType.TOUCH_START, function (touch, event) {
// 返回世界坐标
let touchLoc = touch.getLocation();
if (cc.Intersection.pointInPolygon(touchLoc, this.collider.world.points)) {
console.log("Hit!");
} else {
console.log("No hit");
} }, this);
}
开启物理系统
物理系统默认是关闭的,如果需要使用物理系统,那么首先需要做的事情就是开启物理系统,否则你在编辑器里做的所有物理编辑都不会产生任何效果。
cc.director.getPhysicsManager().enabled = true;
绘制物理调试信息
物理系统默认是不绘制任何调试信息的,如果需要绘制调试信息,请使用 debugDrawFlags 。 物理系统提供了各种各样的调试信息,你可以通过组合这些信息来绘制相关的内容。
cc.director.getPhysicsManager().debugDrawFlags = cc.PhysicsManager.DrawBits.e_aabbBit |
cc.PhysicsManager.DrawBits.e_pairBit |
cc.PhysicsManager.DrawBits.e_centerOfMassBit |
cc.PhysicsManager.DrawBits.e_jointBit |
cc.PhysicsManager.DrawBits.e_shapeBit;
点测试
点测试将测试是否有碰撞体会包含一个世界坐标系下的点,如果测试成功,则会返回一个包含这个点的碰撞体。注意,如果有多个碰撞体同时满足条件,下面的接口只会返回一个随机的结果。
var collider = cc.director.getPhysicsManager().testPoint(point);
矩形测试
矩形测试将测试指定的一个世界坐标系下的矩形,如果一个碰撞体的包围盒与这个矩形有重叠部分,则这个碰撞体会给添加到返回列表中。
var colliderList = cc.director.getPhysicsManager().testAABB(rect);
射线测试
射线检测用来检测给定的线段穿过哪些碰撞体,我们还可以获取到碰撞体在线段穿过碰撞体的那个点的法线向量和其他一些有用的信息。
var results = cc.director.getPhysicsManager().rayCast(p1, p2, type);
for (var i = 0; i < results.length; i++) {
var result = results[i];
var collider = result.collider;
var point = result.point;
var normal = result.normal;
var fraction = result.fraction;
}
刚体属性
//质量
var mass = rigidbody.getMass();
// 设置移动速度
rigidbody.linearVelocity = velocity;
// 设置旋转速度
rigidbody.angularVelocity = velocity;
//获取刚体世界坐标值
var out = rigidbody.getWorldPosition();
// 施加一个力到刚体上指定的点上,这个点是世界坐标系下的一个点
rigidbody.applyForce(force, point);
// 或者直接施加力到刚体的质心上
rigidbody.applyForceToCenter(force);
// 施加一个冲量到刚体上指定的点上,这个点是世界坐标系下的一个点
rigidbody.applyLinearImpulse(impulse, point);
只有开启了刚体的碰撞监听,刚体发生碰撞时才会回调到对应的组件上。
rigidbody.enabledContactListener = true;
定义一个碰撞回调函数很简单,只需要在刚体所在的节点上挂一个脚本,脚本中添加上你需要的回调函数即可。
cc.Class({
extends: cc.Component,
// 只在两个碰撞体开始接触时被调用一次
onBeginContact: function (contact, selfCollider, otherCollider) {
console.log(otherCollider.tag);
},
// 只在两个碰撞体结束接触时被调用一次
onEndContact: function (contact, selfCollider, otherCollider) {
},
// 每次将要处理碰撞体接触逻辑时被调用
onPreSolve: function (contact, selfCollider, otherCollider) {
},
// 每次处理完碰撞体接触逻辑时被调用
onPostSolve: function (contact, selfCollider, otherCollider) {
}
});
通过脚本控制 AudioSource 组件,如下所示:
// AudioSourceControl.js
cc.Class({
extends: cc.Component,
properties: {
audioSource: {
type: cc.AudioSource,
default: null
},
},
play: function () {
this.audioSource.play();
},
pause: function () {
this.audioSource.pause();
},
});
AudioEngine 与 AudioSource 都能播放音频,它们的区别在于 AudioSource 是组件,可以添加到场景中,由编辑器设置。而 AudioEngine 是引擎提供的纯 API,只能在脚本中进行调用。如下所示:
在脚本的 properties 中定义一个 AudioClip 资源对象
直接使用 cc.audioEngine.play(audio, loop, volume); 播放,如下所示:
// AudioEngine.js
cc.Class({
extends: cc.Component,
properties: {
audio: {
default: null,
type: cc.AudioClip
}
},
onLoad: function () {
this.current = cc.audioEngine.play(this.audio, false, 1);
},
onDestroy: function () {
cc.audioEngine.stop(this.current);
}
});