cocos小记

CCclass

构造函数 ctor: function () {}

声明变量的时候加上类型 playerNode:cc.Node

节点访问和常用接口

获取组件所在的节点 this.node

获取其他组件
通过类型 this.getComponent(cc.Label);
通过类名 var rotate = this.getComponent("SinRotate");

模块化方法 var Player = require("Player"); 然后 player:{defualt:null , type:Player}

查找子节点this.node.children
his.node.getChildByName("Cannon 01");
cc.find("Cannon 01/Barrel/SFX", this.node);

全局查找,只有一个参数的find
this.backNode = cc.find("Canvas/Menu/Back");

通过全局变量访问或require模块化方法来实现跨文件操作
var Global = require("Global");

节点激活 this.node.active = false;

更改父节点 this.node.parent = parentNode;

更改节点位置
this.node.x = 100;
this.node.setPosition(cc.v2(100, 50));

更改节点旋转
this.node.rotation = 90;
this.node.setRotation(90);

缩放
this.node.scaleX = 2;this.node.setScale(2);

尺寸
this.node.width = 100;this.node.setContentSize(cc.size(100, 100));

锚点位置
this.node.setAnchorPoint(1, 0);this.node.anchorX = 1;

颜色透明度

在使用 Sprite、Label 这些基本的渲染组件时,要注意修改颜色和不透明度的操作只能在节点的实例上进行,因为这些渲染组件本身并没有设置颜色和不透明度的接口。)

mySprite.node.color = cc.Color.RED;
mySprite.node.opacity = 128;

cc.Component 是所有组件的基类,任何组件都包括如下的常见接口(该组件的脚本中,以 this 指代本组件):

this.node:该组件所属的节点实例
this.enabled:是否每帧执行该组件的 update 方法,同时也用来控制渲染组件是否显示
update(dt):作为组件的成员方法,在组件的 enabled 属性为 true 时,其中的代码会每帧执行
onLoad():组件所在节点进行初始化时(节点添加到节点树时)执行
start():会在该组件第一次 update 之前执行,通常用于需要在所有组件的 onLoad 初始化完毕后执行的逻辑

创建节点
new cc.Node() 并且通过node.addComponent(cc.Sprite)方式来进行
克隆和根据Prefab预制件生成节点 cc.instantiate

销毁节点 node.destroy() 销毁节点并不会立刻被移除,而是在当前帧逻辑更新结束后,统一执行。当一个节点销毁后,该节点就处于无效状态,可以通过 cc.isValid 判断当前节点是否已经被销毁。if (cc.isValid(this.target))

生命周期回调 Lifetime Callback Function

onLoad 组件脚本的初始化阶段,我们提供了 onLoad 回调函数。onLoad 回调会在节点首次激活时触发,比如所在的场景被载入,或者所在节点被激活的情况下。在 onLoad 阶段,保证了你可以获取到场景中的其他节点,以及节点关联的资源数据。onLoad 总是会在任何 start 方法调用前执行,这能用于安排脚本的初始化顺序。通常我们会在 onLoad 阶段去做一些初始化相关的操作
start start 回调函数会在组件第一次激活前,也就是第一次执行 update 之前触发。start 通常用于初始化一些需要经常修改的数据,这些数据可能在 update 时会发生改变。
update 游戏开发的一个关键点是在每一帧渲染前更新物体的行为,状态和方位。
lateUpdate update 会在所有动画更新前执行,但如果我们要在动效(如动画、粒子、物理等)更新之后才进行一些额外操作,或者希望在所有组件的 update 都执行完之后才进行其它操作,那就需要用到 lateUpdate 回调。

onEnable当组件的 enabled 属性从 false 变为 true 时,或者所在节点的 active 属性从 false 变为 true 时,会激活 onEnable 回调。倘若节点第一次被创建且 enabled 为 true,则会在 onLoad 之后,start 之前被调用。

onDisable 当组件的 enabled 属性从 true 变为 false 时,或者所在节点的 active 属性从 true 变为 false 时,会激活 onDisable 回调。

onDestroy当组件或者所在节点调用了 destroy(),则会调用 onDestroy 回调,并在当帧结束时统一回收组件。当同时声明了 onLoadonDestroy 时,它们将总是被成对调用。也就是说从组件初始化到销毁的过程中,它们要么就都会被调用,要么就都不会被调用。

一个组件从初始化到激活,再到最终销毁的完整生命周期函数调用顺序为:onLoad -> onEnable -> start -> update -> lateUpdate -> onDisable -> onDestroy

onLoad 节点激活时立即调用 组件 enabled 时才会调用?否

start 节点激活时延迟调用 组件 enabled 时才会调用?是

加载和场景切换

通过场景名索引和加载切换cc.director.loadScene("MyScene");
对于AssetBundle区分加载和运行需要使用 cc.director.runScene(scene);

如果需要有全局不因为场景加载而销毁的常驻节点,类似Unity DontDestroyOnLoad 可以使用常驻节点,或全局变量访问
cc.game.addPersistRootNode(myNode); 会将 myNode 变为常驻节点,这样挂在上面的组件都可以在场景之间持续作用,我们可以用这样的方法来储存玩家信息,或下一个场景初始化时需要的各种数据。
取消常驻 cc.game.removePersistRootNode(myNode);

场景加载回调函数 cc.director.loadScene("MyScene", onSceneLaunched); 上一行里 onSceneLaunched 就是声明在本脚本中的一个回调函数,在场景加载后可以用来进一步的进行初始化或数据传递的操作。由于回调函数只能写在本脚本中,所以场景加载回调通常用来配合常驻节点,在常驻节点上挂载的脚本中使用。

预加载 cc.director.preloadScene(string sceneName, callback()) 可以静默加载等待

设置和获取资源

所有继承自 cc.Asset 的类型都统称资源,如 cc.Texture2D, cc.SpriteFrame, cc.AnimationClip, cc.Prefab 等。它们的加载是统一并且自动化的,相互依赖的资源能够被自动预加载。例如,当引擎在加载场景时,会先自动加载场景关联到的资源,这些资源如果再关联其它资源,其它也会被先被加载,等加载全部完成后,场景加载才会结束。

手动加载

定义资源属性后手动加载 texture: { default: null, type: cc.Texture2D,},

动态加载

动态加载资源:cc.resources.load 通常我们会把项目中需要动态加载的资源放在 resources 目录下,配合接口动态加载。你只要传入相对 resources 的路径即可,并且路径的结尾处 不能 包含文件扩展名。

cc.resources.load("test assets/prefab", function (err, prefab) {
    var newNode = cc.instantiate(prefab);
    cc.director.getScene().addChild(newNode);
});

所有需要通过脚本动态加载的资源,都必须放置在 resources 文件夹或它的子文件夹下。resources 文件夹需要在 assets 根目录 下手动创建。资源动态加载的时候都是 异步 的,需要在回调函数中获得载入的资源。这么做是因为 Creator 除了场景关联的资源,没有另外的资源预加载列表,动态加载的资源是真正的动态加载。
有些资源需要指定其类型,否则加载出来的默认类型有误(例如把spriteFrame加载为Texture2D)

// 加载 SpriteFrame
var self = this;
cc.resources.load("test assets/image", cc.SpriteFrame, function (err, spriteFrame) {
    self.node.getComponent(cc.Sprite).spriteFrame = spriteFrame;
});

资源释放 cc.resources.release("test assets/image", cc.SpriteFrame);cc.assetManager.releaseAsset(spriteFrame); 进行释放

资源预加载 cc.resources.preload(path, cctype); 预加载的加载参数与正常加载时一样,不过预加载只会去下载必要的资源,并不会进行资源的反序列化和初始化工作,所以性能消耗更小,适合游戏运行中使用。

cc.resources.preload('test assets/image', cc.SpriteFrame);

// wait for while
cc.resources.load('test assets/image', cc.SpriteFrame, function (err, spriteFrame) {
    self.node.getComponent(cc.Sprite).spriteFrame = spriteFrame;
});

加载远程资源和设备资源 cc.assetManager.loadRemote
这种方式有一定限制:1. 这种加载方式只支持图片、声音、文本等原生资源类型,不支持 SpriteFrame、SpriteAtlas、Tilemap 等资源的直接加载和解析。(如需远程加载所有资源,可使用 Asset Bundle) 2. Web 端的远程加载受到浏览器的 CORS 跨域策略限制,如果对方服务器禁止跨域访问,那么会加载失败,而且由于 WebGL 安全策略的限制,即便对方服务器允许 http 请求成功之后也无法渲染。

// 远程 url 带图片后缀名
var remoteUrl = "http://unknown.org/someres.png";
cc.assetManager.loadRemote(remoteUrl, function (err, texture) {
    // Use texture to create sprite frame
});

资源的依赖与释放
在加载完资源之后,所有的资源都会临时被缓存到 cc.assetManager
针对 JavaScript 中无法跟踪对象引用的问题,Asset Manager 提供了一套基于引用计数的资源释放机制,让开发者可以简单高效地释放资源,不用担心项目规模的急剧膨胀。每一个资源对象都提供了两个方法 addRefdecRef,你可以使用这两个接口来对动态资源的引用进行控制

Asset Bundle
从 v2.4 开始,Cocos Creator 推出了 Asset Bundle 功能,支持 代码、资源 和 场景 的分包加载。开发者可将项目中的部分场景、资源、代码等内容划分到不同的 Asset Bundle 中,这些 Asset Bundle 不会在游戏启动时加载,而是由开发者在游戏过程中手动调用 loadBundle 进行加载,从而有效降低游戏启动的时间,尽可能做到按需加载。

加载asset bundle cc.assetManager.loadBundle 加载时需要传入 Asset Bundle 配置面板中的 Bundle 名称 或者 Asset Bundle 的 url。

cc.assetManager.loadBundle('01_graphics', (err, bundle) => {
    bundle.load('xxx');
});

从 v2.4.3 开始,cc.assetManager.loadBundle 还支持传入用户空间中的路径来加载用户空间中的 Asset Bundle。通过对应平台提供的下载接口将 Asset Bundle 提前下载到用户空间中,然后再使用 loadBundle 进行加载,开发者就可以完全自己管理 Asset Bundle 的下载与缓存过程,更加灵活。

// 提前下载某个 Asset Bundle 到用户空间 pathToBundle 目录下。
//需要保证用户空间下的 Asset Bundle 和对应原始 Asset Bundle 的结构和内容完全一样
// ...
// 通过 Asset Bundle 在用户空间中的路径进行加载
// 微信小游戏平台
cc.assetManager.loadBundle(wx.env.USER_DATA_PATH + '/pathToBundle/bundleName', (err, bundle) => {
    // ...
});

当 Asset Bundle 加载完成后,会触发回调并返回错误信息和 cc.AssetManager.Bundle 类的实例,这个实例就是 Asset Bundle API 的主要入口,开发者可以使用它去加载 Asset Bundle 中的各类资源。在 Asset Bundle 加载完成后,返回了一个 cc.AssetManager.Bundle 类的实例。

可以通过名称进行获取

let bundle = cc.assetManager.getBundle('01_graphics');

加载资源: bundle.load 方法来加载 Asset Bundle 中的资源,此方法的参数与 cc.resources.load 相同,

加载场景:Asset Bundle 提供了 loadScene 方法用于加载指定 bundle 中的场景,你只需要传入 场景名 即可。loadScenecc.director.loadScene 不同的地方在于 loadScene 只会加载指定 bundle 中的场景,而不会运行场景,你还需要使用 cc.director.runScene 来运行场景。

预加载:Asset Bundle 中提供了 preloadpreloadDir 接口用于预加载 Asset Bundle 中的资源。具体的使用方式和 cc.assetManager 一致

释放资源:常规的cc.assetManager.releaseAssetbundle.release('image', cc.SpriteFrame);bundle.releaseAll();

移除AssetBundle:cc.assetManager.removeBundle(bundle);

当手动移除了某个不需要的 bundle,那么此 bundle 的缓存也会被移除,如果需要再次使用,则必须再重新加载一次。在移除 Asset Bundle 时,并不会释放该 bundle 中被加载过的资源。如果需要释放,请先使用 Asset Bundle 的 release / releaseAll 方法:

监听与发射事件

事件处理是在节点(cc.Node)中完成的。对于组件,可以通过访问节点 this.node 来注册和监听事件。监听事件可以通过 this.node.on() 函数来注册,除了使用on 监听,我们还可以使用 once 方法。once 监听在监听函数响应后就会关闭监听事件。

监听事件
注册监听方法:this.node.on('mousedown', function ( event ) {console.log('Hello!');});
/ 例:点击事件的绑定 this.node.on(‘touchstart’,this.fire,this);
事件监听函数 on 可以传第三个参数 target,用于绑定响应函数的调用者。

// 使用函数绑定
this.node.on('mousedown', function ( event ) {this.enabled = false;}.bind(this));

// 使用第三个参数
this.node.on('mousedown', function (event) {this.enabled = false;}, this);

关闭监听:off方法,推荐和onEnable()onDisable() 配合案例

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);
  },
});

发射事件
发射事件有两种方式:emitdispatchEvent。两者的区别在于,后者可以做事件传递。

在发射事件时,我们可以在 emit 函数的第二个参数开始传递我们的事件参数。同时,在 on 注册的回调里,可以获取到对应的事件参数。出于底层事件派发的性能考虑,这里最多只支持传递 5 个事件参数。

onLoad () { //事件注册
    this.node.on('foo', function (arg1, arg2, arg3) {
      console.log(arg1, arg2, arg3);  // print 1, 2, 3
    });
  },
  start () {
    // 事件发射
    let arg1 = 1, arg2 = 2, arg3 = 3;
    // At most 5 args could be emit.
    this.node.emit('foo', arg1, arg2, arg3);
  },

派送事件 (用于我不想触发全部,而是可以截获)

dispatchEvent 方法,通过该方法发射的事件,会进入事件派送阶段。在 Cocos Creator 的事件派送系统中,我们采用冒泡派送的方式。冒泡派送会将事件从事件发起节点,不断地向上传递给他的父级节点,直到到达根节点或者在某个节点的响应函数中做了中断处理 event.stopPropagation()`。
 发送的节点c
 this.node.dispatchEvent( new cc.Event.EventCustom('foobar', true) );
 截获的节点b
 `this.node.on('foobar', function (event) {event.stopPropagation();});

事件对象
在事件监听回调中,开发者会接收到一个 cc.Event 类型的事件对象 eventstopPropagation 就是 cc.Event 的标准 API。
在发送用户自定义事件的时候,请不要直接创建 cc.Event 对象,因为它是一个抽象类,请创建 cc.Event.EventCustom 对象来进行派发。
其中重要的API包含:
API – 类型 – 意义
typeString 事件的类型(事件名)
targetcc.Node 接收到事件的原始对象
currentTargetcc.Node 接收到事件的当前对象,事件在冒泡阶段当前对象可能与原始对象不同
getTypeFunction 获取事件的类型
stopPropagationFunction 停止冒泡阶段,事件将不会继续向父节点传递,当前节点的剩余监听器仍然会接收到事件
stopPropagationImmediateFunction 立即停止事件的传递,事件将不会传给父节点以及当前节点的剩余监听器
getCurrentTargetFunction 获取当前接收到事件的目标节点
detailFunction 自定义事件的信息(属于 cc.Event.EventCustom)
setUserDataFunction 设置自定义事件的信息(属于 cc.Event.EventCustom)
getUserDataFunction 获取自定义事件的信息(属于 cc.Event.EventCustom)

系统内置事件

Cocos Creator 支持的系统事件包含鼠标、触摸、键盘、重力传感四种,其中本章节重点介绍与节点树相关联的鼠标和触摸事件,这些事件是被直接触发在相关节点上的,所以被称为节点系统事件。与之对应的,键盘和重力传感事件被称为全局系统事件。
系统事件遵守通用的注册方式,开发者既可以使用枚举类型也可以直接使用事件名来注册事件的监听器,事件名的定义遵循 DOM 事件标准。

// 使用枚举类型来注册
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_DOWNmousedown 当鼠标在目标节点区域按下时触发一次
cc.Node.EventType.MOUSE_ENTERmouseenter 当鼠标移入目标节点区域时,不论是否按下
cc.Node.EventType.MOUSE_MOVEmousemove 当鼠标在目标节点区域中移动时,不论是否按下
cc.Node.EventType.MOUSE_LEAVEmouseleave 当鼠标移出目标节点区域时,不论是否按下
cc.Node.EventType.MOUSE_UPmouseup 当鼠标从按下状态松开时触发一次
cc.Node.EventType.MOUSE_WHEELmousewheel 当鼠标滚轮滚动时

鼠标事件(cc.Event.EventMouse)的重要 API 如下(cc.Event 标准事件 API 除外):
函数名- 返回值类型 -意义
getScrollY Number 获取滚轮滚动的 Y 轴距离,只有滚动时才有效
getLocation Object 获取鼠标位置对象,对象包含 x 和 y 属性
getLocationX Number 获取鼠标的 X 轴位置
getLocationY Number 获取鼠标的 Y 轴位置
getPreviousLocation Object 获取鼠标事件上次触发时的位置对象,对象包含 x 和 y 属性
getDelta Object 获取鼠标距离上一次事件移动的距离对象,对象包含 x 和 y 属性
getButton Number 获取cc.Event.EventMouse.BUTTON_LEFTcc.Event.EventMouse.BUTTON_RIGHTcc.Event.EventMouse.BUTTON_MIDDLE

触摸事件类型和事件对象 网页
TOUCH_START还有TOUCH_MOVE TOUCH_END TOUCH_CANCEL 四种微妙差别
触摸事件API 部分可以获取 触点对象touch,getID,getLocation,获取上次触点getPreviousLocation,获取触点初始位置 getStartLocation , 获取移动的距离对象getDelta 需要注意的是,触摸事件支持多点触摸,每个触点都会发送一次事件给事件监听器。

触摸事件的传递问题,参考事件冒泡:如果c是b的子节点,b是a的子节点:会通过c – b – a 冒泡在触摸事件冒泡的过程中不会有触摸检测,这意味着即使触点不在 A B 节点区域内,A B 节点也会通过触摸事件冒泡的机制接收到这个事件。

cc.Node 的其它事件
position-changed 当位置属性修改
rotation-changed 当旋转属性修改时
scale-changed 当缩放属性修改时
size-changed 当宽高属性修改时
anchor-changed 当锚点属性修改时

屏蔽多点触摸系统
cc.macro.ENABLE_MULTI_TOUCH = false;

暂停和恢复节点事件系统
this.node.pauseSystemEvents();this.node.resumeSystemEvents();

全局系统事件

全局系统事件是指与节点树不相关的各种全局事件,由 cc.systemEvent 来统一派发

定义输入事件
键盘、设备重力传感器此类全局事件是通过函数 cc.systemEvent.on(type, callback, target)注册的。
可选的 type 类型有:
cc.SystemEvent.EventType.KEY_DOWN (键盘按下)
cc.SystemEvent.EventType.KEY_UP (键盘释放)
cc.SystemEvent.EventType.DEVICEMOTION (设备重力传感)

键盘事件

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;
        }
    }
});

重力传感器事件 cc.SystemEvent.EventType.DEVICEMOTION

cc.Tween 缓动系统

cc.tween 能够对对象的任意属性进行缓动,功能类似于 cc.Action(动作系统)。但是 cc.tween 会比 cc.Action 更加简洁易用,因为 cc.tween 提供了链式创建的方法,可以对任何对象进行操作,并且可以对对象的任意属性进行缓动。

动作系统 是从 Cocos2d-x 迁移到 Cocos Creator 的,提供的 API 比较繁琐,只支持在节点属性上使用,并且如果要支持新的属性就需要再添加一个新的动作。为了提供更好的 API,cc.tween动作系统 的基础上做了一层 API 封装。

动作系统目前已不推荐使用,未来将逐步移除,建议使用 缓动系统 做为替代。

使用对比

cc.Action:

this.node.runAction(
    cc.sequence(
        cc.spawn(
            cc.moveTo(1, 100, 100),
            cc.rotateTo(1, 360),
        ),
        cc.scale(1, 2)
    )
)

cc.tween:

cc.tween(this.node)
    .to(1, { position: cc.v2(100, 100), rotation: 360 })
    .to(1, { scale: 2 })
    .start()

链式 API
cc.tween 的每一个 API 都会在内部生成一个 action,并将这个 action 添加到内部队列中,在 API 调用完后会再返回自身实例,这样就可以通过链式调用的方式来组织代码。
cc.tween 在调用 start 时会将之前生成的 action 队列重新组合生成一个 cc.sequence 队列,所以 cc.tween 的链式结构是依次执行每一个 API 的,也就是会执行完一个 API 再执行下一个 API。

cc.tween(this.node)
    // 0s 时,node 的 scale 还是 1
    .to(1, { scale: 2 })
    // 1s 时,执行完第一个 action,scale 为 2
    .to(1, { scale: 3 })
    // 2s 时,执行完第二个 action,scale 为 3
    .start()
    // 调用 start 开始执行 cc.tween

cc.tween 提供了两个设置属性的 API:
to:对属性进行绝对值计算,最终的运行结果即是设置的属性值,即改变到某个值。
by:对属性进行相对值计算,最终的运行结果是设置的属性值加上开始运行时节点的属性值,即变化值。

cc.tween(node)
  .to(1, {scale: 2})      // node.scale === 2
  .by(1, {scale: 2})      // node.scale === 4 (2 + 2)
  .by(1, {scale: 1})      // node.scale === 5
  .to(1, {scale: 2})      // node.scale === 2
  .start()

tween 可以支持任意对象的任意属性

let obj = { a: 0 }
cc.tween(obj).to(1, { a: 100 }).start()

修改缓动函数 Ease EaseType(动画曲线)
查看cocos的Easing类型

// 传入 easing 名字,直接使用内置 easing 函数
cc.tween().to(1, { scale: 2 }, { easing: 'sineOutIn'})

// 使用自定义 easing 函数
cc.tween().to(1, { scale: 2 }, { easing: t => t*t; })

// 只对单个属性使用 easing 函数
// value 必须与 easing 或者 progress 配合使用
cc.tween().to(1, { scale: 2, position: { value: cc.v3(100, 100, 100), easing: 'sineOutIn' } })

可以用自定义 progress 函数来控制缓动
可以复制当前缓动并且接受一个targettween.clone(cc.find('Canvas/cocos')).start()
可以不用.start(),而是创建一些固定缓动然后再组合

let scale = cc.tween().to(1, { scale: 2 })
let rotate = cc.tween().to(1, { rotation: 90})
let move = cc.tween().to(1, { position: cc.v3(100, 100, 100)})

// 先缩放再旋转
cc.tween(this.node).then(scale).then(rotate)

缓动可以借助 parallel实现并行,而不是按照原本的sequence的方式

let t = cc.tween;
t(this.node)
    // 同时执行两个 cc.tween
    .parallel(
        t().to(1, { scale: 2 }),
        t().to(2, { position: cc.v2(100, 100) })
    )
    .call(() => {
        console.log('All tweens finished.')
    })
    .start()

可以在动作执行中进行函数回调

cc.tween(this.node)
    .to(2, { rotation: 90})
    .to(1, { scale: 2})
    // 当前面的动作都执行完毕后才会调用这个回调函数
    .call(() => { cc.log('This is a callback') })
    .start()

重复执行 repeat repeatForever

cc.tween(this.node)
    .by(1, { scale: 1 })
    // 对前一个 by 重复执行 10次
    .repeat(10)
    // 最后 node.scale === 11
    .start()

// 也可以这样用
cc.tween(this.node)
    .repeat(10,
        cc.tween().by(1, { scale: 1 })
    )
    .start()

// 一直重复执行下去
cc.tween(this.node)
    .by(1, { scale: 1 })
    .repeatForever()
    .start()

延迟执行

cc.tween(this.node)
    // 延迟 1s
    .delay(1)
    .to(1, { scale: 2 })
    // 再延迟 1s
    .delay(1)
    .to(1, { scale: 3 })
    .start()

计时器

在 Cocos Creator 中,我们为组件提供了方便的计时器,这个计时器源自于 Cocos2d-x 中的 cc.Scheduler,我们将它保留在了 Cocos Creator 中并适配了基于组件的使用方式。

也许有人会认为 setTimeoutsetInterval 就足够了,开发者当然可以使用这两个函数,不过我们更推荐使用计时器,因为它更加强大灵活,和组件也结合得更好!

下面这个计时器将每隔 5s 执行一次。

 component.schedule(function() {
     this.doSomething();
 }, 5);

下面的计时器将在 10 秒后开始计时,每 5 秒执行一次回调,执行 3 + 1 次。

// 以秒为单位的时间间隔
 var interval = 5;
 // 重复次数
 var repeat = 3;
 // 开始延时
 var delay = 10;
 component.schedule(function() {
     // 这里的 this 指向 component
     this.doSomething();
 }, interval, repeat, delay);

只执行一次的计时器

 component.scheduleOnce(function() {
     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);

schedule:开始一个计时器
scheduleOnce:开始一个只执行一次的计时器
unschedule:取消一个计时器
unscheduleAllCallbacks:取消这个组件的所有计时器

控制脚本的执行顺序

使用统一的控制脚本来初始化其他脚本
其中在 Player.js、Enemy.js 和 Menu.js 中需要实现 init 方法,并将初始化逻辑放进去。这样我们就可以保证 Player、Enemy 和 Menu 的初始化顺序。

// Game.js

const Player = require('Player');
const Enemy = require('Enemy');
const Menu = require('Menu');

cc.Class({
    extends: cc.Component,
    properties: {
        player: Player,
        enemy: Enemy,
        menu: Menu
    },

    onLoad: function () {
        this.player.init();
        this.enemy.init();
        this.menu.init();
    }
});

在 Update 中用自定义方法控制更新顺序
同理如果要保证以上三个脚本的每帧更新顺序,我们也可以将分散在每个脚本里的 update 替换成自己定义的方法:updatePlayer: function (dt) { // do player update} ,然后在Game.js中脚本的update里调用这些

// Game.js
    update: function (dt) {
        this.player.updatePlayer(dt);
        this.enemy.updateEnemy(dt);
        this.menu.updateMenu(dt);
    }

控制同一个节点上的组件执行顺序
在同一个节点上的组件脚本执行顺序:

  1. 可以通过组件在 属性检查器 里的排列顺序来控制。排列在上的组件会先于排列在下的组件执行。我们可以通过组件右上角的齿轮按钮里的 Move Up 和 Move Down 菜单来调整组件的排列顺序和执行顺序。
  2. 可以设置组件执行优先级 ,设置组件的 executionOrderexecutionOrder 会影响组件的生命周期回调的执行优先级。 executionOrder 越小,相对于其他组件就会越优先执行。
cc.Class({
    extends: cc.Component,
    editor: {
        executionOrder: -1
    },

    onLoad: function () {
        cc.log('Player onLoad!');
    }
});

标准网络接口

在 Cocos Creator 中,我们支持 Web 平台上最广泛使用的标准网络接口:
XMLHttpRequest:用于短连接
WebSocket:用于长连接

这部分暂时不了解 ,参考文档 标准网络接口

对象池

cocos自带有cc.NodePool的对象池,使用可参考文档 使用对象池

模块化脚本

Cocos Creator 允许你将代码拆分成多个脚本文件,并且让它们相互调用。要实现这点,你需要了解如何在 Cocos Creator 中定义和使用模块,这个步骤简称为 模块化。感觉这个东西类似于引用变量,然后提供了一个public的引用进行代码之间的耦合。

如果你还不确定模块化究竟能做什么,模块化相当于:

  • Java 和 Python 中的 import
  • C# 中的using
  • C/C++ 中的 include
  • HTML 中的 <link>

模块化使你可以在 Cocos Creator 中引用其它脚本文件:

  • 访问其它文件导出的参数
  • 调用其它文件导出的方法
  • 使用其它文件导出的类型
  • 使用或继承其它 Component

Cocos Creator 中的 JavaScript 使用和 Node.js 几乎相同的 CommonJS 标准来实现模块化,简单来说:

  • 每一个单独的脚本文件就构成一个模块
  • 每个模块都是一个单独的作用域
  • 以 同步 的 require 方法来引用其它模块
  • 设置 module.exports 为导出的变量

在本文中,“模块”和“脚本”这两个术语是等价的。所有“备注”都属于进阶内容,一开始不需要了解。
不论模块如何定义,所有用户代码最终会由 Creator 编译为原生的 JavaScript,可直接在浏览器中运行。

引用模块

var Rotate = require("Rotate"); require 返回的就是被模块导出的对象,通常我们都会将结果立即存到一个变量(var Rotate)。传入 require 的字符串就是模块的文件名,这个名字不包含路径也不包含后缀,而且大小写敏感。

派生范例

var Rotate = require("Rotate");

var SinRotate = cc.Class({
    extends: Rotate,
    update: function (dt) {
        this.rotation += this.speed * Math.sin(dt);
    }
});

定义模块

如果你的模块是extends:cc.Component 的话,在脚本中声明了一个组件,Creator 会默认把它导出,其它脚本直接 require 这个模块就能使用这个组件。
但是模块导出的也可以是任意的JavaScript对象,只要通过 module.exports = xxx; 就可以导出
module.exports 默认是一个空对象({}),可以直接往里面增加新的字段。

  module.exports = {
      FOO: function () {
          this.type = "foo";
      },
      bar: "bar"
  };

在脚本内定义的变量可以通过封装来进行访问:

// foobar.js:
var dirty = false;
module.exports = {
    setDirty: function () {
        dirty = true;
    },
    isDirty: function () {
        return dirty;
    },
};

图像和渲染

基本图像渲染:Sprite 、 Label 、 Mask 组件
外部资源渲染:ParticleSystem、TiledMap、Spine、DragonBones、VideoPlayer、WebView组件

摄像机

创建场景时,Creator 会默认创建一个名为 Main Camera的摄像机,作为这个场景的主摄像机。
backgroundColor 当指定了摄像机需要清除颜色的时候,摄像机会使用设定的背景色来清除场景。
depth摄像机深度,用于决定摄像机的渲染顺序。值越大,则摄像机越晚被渲染。
cullingMask cullingMask 将决定这个摄像机用来渲染场景的哪些部分。在 属性检查器 中的摄像机组件中的 cullingMask 会列出当前可以选择的 mask 选项,你可以通过勾选这些选项来组合生成 cullingMask。
使用项目-项目设置-分组管理 来添加和修改分组。
clearFlags指定渲染摄像机时需要做的清除操作。
rect 决定摄像机绘制在屏幕上的哪个区域,便于实现类似小地图那样的 Viewport,值为 0~1。
zoomRatio指定摄像机的缩放比例,值越大显示的图像越大。
alignWithScreen当 alignWithScreen 为 true 的时候,摄像机会自动将视窗大小调整为整个屏幕的大小。
orthoSize摄像机在正交投影模式下的视窗大小。
targetTexture如果设置了 targetTexture,那么摄像机渲染的内容不会输出到屏幕上,而是会渲染到 targetTexture 上。如果你需要做一些屏幕的后期特效,可以先将屏幕渲染到 targetTexture,然后再对 targetTexture 做整体处理,最后再通过一个 sprite 将这个 targetTexture 显示出来。

坐标转换

// 将一个屏幕坐标系下的点转换到世界坐标系下
camera.getScreenToWorldPoint(point, out);
// 将一个世界坐标系下的点转换到屏幕坐标系下
camera.getWorldToScreenPoint(point, out);

// 获取屏幕坐标系到世界坐标系的矩阵,只适用于 2D 摄像机并且 alignWithScreen 为 true 的情况
camera.getScreenToWorldMatrix2D(out);
// 获取世界坐标系到屏幕坐标系的矩阵,只适用于 2D 摄像机并且 alignWithScreen 为 true 的情况
camera.getWorldToScreenMatrix2D(out);

Material 材质资源

材质资源可以用来控制渲染组件在场景中的视觉效果。简单来说材质就是用来指定物体表面的特性,如颜色、光亮程度、自发光度以及不透明度等。

创建 Effect

Sprite 组件参考

Sprite(精灵)是 2D 游戏中最常见的显示图像的方式
渲染方式:

  • 普通模式(Simple):根据原始图片资源渲染 Sprite,一般在这个模式下我们不会手动修改节点的尺寸,来保证场景中显示的图像和美术人员生产的图片比例一致。
  • 九宫格模式(Sliced):图像将被分割成九宫格,并按照一定规则进行缩放以适应可随意设置的尺寸(size)。通常用于 UI 元素,或将可以无限放大而不影响图像质量的图片制作成九宫格图来节省游戏资源空间。详细信息请阅读 使用 Sprite 编辑器制作九宫格图像 一节。
  • 平铺模式(Tiled):图像将会根据 Sprite 的尺寸重复平铺显示。如果 SpriteFrame 包含 九宫格配置,平铺时将保持周围宽度不变,而其余部分重复。
  • 填充模式(Filled):根据原点和填充模式的设置,按照一定的方向和比例绘制原始图片的一部分。经常用于进度条的动态展示。
  • 网格模式(Mesh):必须使用 TexturePacker 4.x 以上版本并且设置 ploygon 算法打包出的 plist 文件才能够使用该模式。

Label组件参考
Label 组件用来显示一段文字,文字可以是系统字体、TrueType 字体、BMFont 字体或艺术数字。另外,Label 还具有排版功能。

LabelOutline 描边
LabelShadow 投影
Mask 遮罩
MotionStreak 拖尾
Particle System 粒子系统
Tilemap 组件
Spine
DragonBones
VideoPlayer
WebView 组件参考:WebView 是一种显示网页的组件,该组件让你可以在游戏里面集成一个小的浏览器。
Graphics 组件提供了一系列绘画接口,这些接口参考了 canvas 的绘画接口来进行实现。

UI系统

多分辨率适配方案

Canvas(画布) 组件随时获得设备屏幕的实际分辨率并对场景中所有渲染元素进行适当的缩放。
Widget(对齐挂件) 放置在渲染元素上,能够根据需要将元素对齐父节点的不同参考位置。
Label(文字) 组件内置了提供各种动态文字排版模式的功能,当文字的约束框由于 Widget 对齐要求发生变化时,文字会根据需要呈现完美的排版效果。
Sliced Sprite(九宫格精灵图)则提供了可任意指定尺寸的图像,同样可以满足各式各样的对齐要求,在任何屏幕分辨率上都显示高精度的图像。

设计分辨率 / 屏幕分辨率

设计分辨率 是内容生产者在制作场景时使用的分辨率蓝本
屏幕分辨率 是游戏在设备上运行时的实际屏幕显示分辨率。

通常设计分辨率会采用市场目标群体中使用率最高的设备的屏幕分辨率,比如目前安卓设备中 800 x 480 和 1280 x 720 两种屏幕分辨率,或 iOS 设备中 1136 x 640 和 960 x 640 两种屏幕分辨率。这样当美术或策划使用设计分辨率设置好场景后,就可以自动适配最主要的目标人群设备。

Canvas带有适配宽度(Fit Width)还有适配高度(Fit Height)的模式,配合Widget(对齐挂件)进行调整,也存在ExactFit的适配模式,进行图像的拉伸变形。

对齐策略

要实现完美的多分辨率适配效果,UI 元素按照设计分辨率中规定的位置呈现是不够的,当屏幕宽度和高度发生变化时,UI 元素要能够智能感知屏幕边界的位置,才能保证出现在屏幕可见范围内,并且分布在合适的位置。我们通过 Widget(对齐挂件) 来实现这种效果。

  1. 需要贴边对齐的按钮和小元素:设置为Canvas的子节点,添加Widget组件,开启LeftButtom对齐
  2. 嵌套对齐元素:因为Widget是根据父节点进行对齐
  3. 根据对齐需要自动缩放节点尺寸:需要同时勾选 LeftRight对齐开关,就能够修改size
  4. 制作和屏幕大小保持一致的节点:那就进行全部的勾选
  5. 设置百分比对齐距离:直接输入百分比:可以按照需要混合像素单位和百分比单位的使用。比如在需要对齐屏幕中心线的 Left 方向输入 50%,在需要对齐屏幕边缘的 Right 方向输入 20px,最后计算子节点位置和尺寸时,所有的边距都会先根据父节点的尺寸换算成像素距离,然后再进行摆放。
Align Mode中的 设置为ON_WINDOW_RESIZE或者 ONCE,在进行一次对齐之后就会把Widget组件的enabled属性设置为false

制作可任意拉伸的UI图像

通过进行Sprite的编辑,切分九宫格,并且将Sprite的Type属性设置为Sliced

文字排版

Label 组件的排版也是基于节点尺寸(Size),也就是约束框(Bounding Box)所规定的范围。
Label 中以下的属性决定了文字在约束框中显示的位置:
Horizontal Align(水平对齐):文字在约束框中水平方向的对齐准线,可以从 Left、Right、Center 三种位置中选择。
Vertical Align(垂直对齐):文字在约束框中垂直方向的对齐准线,可以从 Top、Bottom、Center 三种位置中选择。

Font Size(文字尺寸)决定了文字的显示大小,单位是 Point(也称作“磅”)
Line Height(行高)决定了文字在多行显示时每行文字占据的空间高度,单位同样是 Point。
调整这两个参数的大小关系决定了文字现实的疏密与重叠

排版模式(Overflow)
Overflow(排版模式) 属性,决定了文字内容增加时,如何在约束框的范围内排布。共有NONECLAMPSHRINKRESIZE_HEIGHT 四种模式,而只有在后面三种模式下才能通过编辑器左上角的 矩形变换工具 或者修改 属性检查器 中的 Size 大小或者添加 Widget 组件 来调整约束框的大小。
截断(Clamp) 截断模式下,文字首先按照对齐模式和尺寸的要求进行渲染,而超出约束框的部分会被隐藏(截断)。
自动缩小(Shrink)自动缩小模式下,如果文字按照原定尺寸渲染会超出约束框时,会自动缩小文字尺寸以显示全部文字。
自动适应高度(Resize Height)自动适应高度模式会保证文字的约束框贴合文字的高度,不管文字有多少行。这个模式非常适合显示内容量不固定的大段文字,配合 ScrollView 组件 可以在任意 UI 区域中显示无限量的文字内容。

自动换行(Enable Wrap Text)
Label 组件中的 Enable Wrap Text(自动换行)属性,可以切换文字的自动换行开关。在自动换行开启的状态下,不需要在输入文字时手动输入回车或换行符,文字也会根据约束框的宽度自动换行。
注意:自动换行属性只有在文字排版模式的截断(Clamp)自动缩小(Shrink) 这两种模式下才有。自动适应高度(Resize Height)模式下,自动换行属性是强制开启的。
中文自动换行的行为和英文不同,英文是以单词为单位进行换行的,必须有空格才能作为换行调整的最小单位。中文是以字为单位进行换行,每个字都可以单独调整换行。

文字节点的锚点
文字节点的锚点和文字在约束框中的对齐模式是需要区分的两个概念。在需要靠文字内容将约束框撑大的排版模式中(如Resize Height),要正确设置锚点位置,才能让约束框向我们期望的方向调整。
如果希望文字约束框向下扩展,需要将锚点(Anchor)的 y 属性设为 1。
在 Label 组件所在节点上添加一个 Widget(对齐挂件) 组件,就可以让文字节点相对于父节点进行各式各样的排版。可以使用百分比来进行进行页面的分隔,并且显示出多列布局的文字。

自动布局容器 Layout

Layout(自动布局)组件可以挂载在任何节点上,将节点变成一个有自动布局功能的容器。所谓自动布局容器,就是能够自动将子节点按照一定规律排列,并可以根据节点内容的约束框总和调整自身尺寸的容器型节点。

布局模式(Layout Type)

  • 水平布局
    Layout Type 设为 Horizontal 时,所有子节点都会自动横向排列,并根据子节点的宽度(Width)总和设置 Layout 节点的宽度。上图中 Layout 包括的两个 Label 节点就自动被横向排列。
    水平布局模式下,Layout 组件不会干涉节点在 y 轴上的位置或高度属性,子节点甚至可以放置在 Layout 节点的约束框高度范围之外。如果需要子节点在 y 轴向上对齐,可以在子节点上添加 Widget 组件,并开启 Top 或 Bottom 的对齐模式。
  • 垂直布局
    Layout Type 设为 Vertical 时,所有子节点都会自动纵向排列,并根据子节点的高度(Height)总和设置 Layout 节点的高度。
    垂直布局模式下,Layout 组件也不会修改节点在 x 轴的位置或宽度属性,子节点需要添加 Widget 并开启 Left 或 Right 对齐模式才能规整的排列。

制作动态生成内容的列表的官方例子

UI组件

Canvas(画布) 组件能够随时获得设备屏幕的实际分辨率并对场景中所有渲染元素进行适当的缩放。场景中的 Canvas 同时只能有一个,建议所有 UI 和可渲染元素都设置为 Canvas 的子节点。

Widget (对齐挂件) 是一个很常用的 UI 布局组件。它能使当前节点自动对齐到父物体的任意位置,或者约束尺寸,让你的游戏可以方便地适配不同的分辨率。

Button 组件可以响应用户的点击操作,当用户点击 Button 时,Button 自身会有状态变化。另外,Button 还以让用户在完成点击操作后响应一个自定义的行为。

Button组件可以通过编辑器添加回调,也可以通过脚本添加
首先需要构造一个 cc.Component.EventHandler 对象,然后设置好对应的 targetcomponenthandlercustomEventData 参数,然后再通过button.clickEvents.push()方法把这个EventHandler对象推进去。

 // here is your component file, file name = MyComponent.js 
 cc.Class({
     extends: cc.Component,
     properties: {},

     onLoad: function () {
         var clickEventHandler = new cc.Component.EventHandler();
         clickEventHandler.target = this.node; // 这个 node 节点是你的事件处理代码组件所属的节点
         clickEventHandler.component = "MyComponent";// 这个是代码文件名
         clickEventHandler.handler = "callback";
         clickEventHandler.customEventData = "foobar";

         var button = this.node.getComponent(cc.Button);
         button.clickEvents.push(clickEventHandler);
     },

     callback: function (event, customEventData) {
         // 这里 event 是一个 Event 对象,你可以通过 event.target 取到事件的发送节点
         var node = event.target;
         var button = node.getComponent(cc.Button);
         // 这里的 customEventData 参数就等于你之前设置的 "foobar"
     }
 });

另外一种更简单的方式,但局限性在于在事件回调里面无法 获得当前点击按钮的屏幕坐标点,并且也无法传递按钮的屏幕坐标点。

 this.button.node.on('click', this.callback, this);

Layout 是一种容器组件,容器能够开启自动布局功能,自动按照规范排列所有子物体,方便用户制作列表、翻页等功能。

SafeArea 会将所在节点的布局适配到 iPhone X 等异形屏手机的安全区域内,可适配 Android 和 iOS 设备,通常用于 UI 交互区域的顶层节点。

EditBox 是一种文本输入组件,该组件让你可以轻松获取用户输入的文本。
Editing Did Began该事件在用户点击输入框获取焦点的时候被触发。
Text Changed该事件在用户每一次输入文字变化的时候被触发。
Editing Did Ended在单行模式下面,一般是在用户按下回车或者点击屏幕输入框以外的地方调用该函数。 如果是多行输入,一般是在用户点击屏幕输入框以外的地方调用该函数。
Editing Return在用户按下回车键或者在移动端上点击软键盘的完成按钮时,该事件会被触发。
如果是单行输入框,按回车键还会使输入框失去焦点。
同样可以通过cc.Component.EventHandler 或者是editbox.node.on('editing-did-began', ...)的方式

RichText 组件用来显示一段带有不同样式效果的文字,你可以通过一些简单的 BBCode 标签来设置文字的样式。标签与标签是支持嵌套的,且嵌套规则跟 HTML 是一样的。
富文本组件全部由 JS 层实现,采用底层的 Label 节点拼装而成,并且在上层做排版逻辑。这意味着,你新建一个复杂的富文本,底层可能有十几个 label 节点,而这些 label 节点都是采用系统字体渲染的。所以一般情况下,你不应该在游戏的主循环里面频繁地修改富文本的文本内容,这可能会导致性能比较低。另外,如果能不使用富文本组件,就尽量使用普通的文本组件,并且 BMFont 的效率是最高的。

ScrollView 是一种带滚动功能的容器,它提供一种方式可以在有限的显示区域内浏览更多的内容。通常 ScrollView 会与 Mask 组件配合使用,同时也可以添加 ScrollBar 组件来显示浏览内容的位置。

ScrollBar 允许用户通过拖动滑块来滚动一张图片,它与 Slider 组件有点类似,但是 ScrollBar 主要是用于滚动,而 Slider 则用来设置数值。ScrollBar 一般不会单独使用,它需要与 ScrollView 配合使用,另外 ScrollBar 需要指定一个 Sprite 组件,即属性面板里面的 Handle。通常我们还会给 ScrollBar 指定一张背景图片,用来指示整个 ScrollBar 的长度或者宽度。

ProgressBar(进度条)经常被用于在游戏中显示某个操作的进度,在节点上添加 ProgressBar 组件,然后给该组件关联一个 Bar Sprite 就可以在场景中控制 Bar Sprite 来显示进度了。

Toggle 是一个 CheckBox,当它和 ToggleGroup 一起使用的时候,可以变成 RadioButton。toggle.node.on('toggle', ...)

ToggleContainer 不是一个可见的 UI 组件,它可以用来修改一组 Toggle 组件的行为。当一组 Toggle 属于同一个 ToggleContainer 的时候,任何时候只能有一个 Toggle 处于选中状态。ToggleContainer 一般不会单独使用,它需要与 Toggle 配合使用来实现 RadioButton 的单选效果。

Slider 是一个滑动器组件。slider.node.on('slide', ...)

PageView 是一种页面视图容器 pageView.node.on('page-turning', ...) 添加回调
PageviewIndicator 用于显示 PageView 当前的页面数量和标记当前所在的页面。

BlockInputEvents 组件将拦截所属节点 bounding box 内的所有输入事件(鼠标和触摸),防止输入穿透到下层节点,一般用于上层 UI 的背景。

动画系统

可以参考 博客 Cocos Creator Animation 组件

Animation 也不例外,它也是节点上的一个组件。
AnimaitonClip动画剪辑就是一份动画的声明数据,我们将它挂载到 Animation 组件上,就能够将这份动画数据应用到节点上。

动画编辑器
left:向前移动一帧,如果已经在第 0 帧,则忽略当前操作
right:向后移动一帧
delete:删除当前所选中的关键帧
k:正向的播放动画,抬起后停止
j:反向播放动画,抬起后停止
ctrl / cmd + left:跳转到第 0 帧
ctrl / cmd + right:跳转到有效的最后一帧

时间轴上刻度的表示法是 01-05。
该数值由两部分组成,冒号前面的是表示当前秒数,冒号后面的表示在当前这一秒里的第几帧。
01-05 表示该刻度在时间轴上位于从动画开始经过了 1 秒又 5 帧的时间。

节点数据 动画剪辑通过节点的名字定义数据的位置,本身忽略了根节点,其余的子节点通过与根节点的 相对路径 索引找到对应的数据。

动画属性包括了节点自有的 positionrotation 等属性,也包含了组件 Component 中自定义的属性。 组件包含的属性前会加上组件的名字,比如 cc.Sprite.spriteFrame

添加关键帧 在属性列表中点击对应属性轨道右侧的选项按钮,在弹出的菜单中选择 插入关键帧 按钮。也可以直接在编辑模式下直接更改

双击关键帧的连接线,就可以打开时间曲线编辑器

选中某个位置,然后点击按钮区域最左侧的按钮(add event),这时候在时间轴上会出现一个白色的矩形,这就是我们添加的事件。双击刚刚出现的白色矩形,可以打开事件编辑器,在编辑器内,我们可以手动输入需要触发的 function 名字,触发的时候会根据这个函数名,去各个组件内匹配相应的方法。

用脚本控制动画

var anim = this.getComponent(cc.Animation);
// 如果没有指定播放哪个动画,并且有设置 defaultClip 的话,则会播放 defaultClip 动画
anim.play();

// 指定播放 test 动画
anim.play('test');

// 指定从 1s 开始播放 test 动画
anim.play('test', 1);

// 使用 play 接口播放一个动画时,如果还有其他的动画正在播放,则会先停止其他动画
anim.play('test2');

// 播放第一个动画
anim.playAdditive('position-anim');

// 播放第二个动画
// 使用 playAdditive 播放动画时,不会停止其他动画的播放。如果还有其他动画正在播放,则同时会有多个动画进行播放
anim.playAdditive('rotation-anim');

// 指定暂停 test 动画
anim.pause('test');

// 暂停所有动画
anim.pause();

// 指定恢复 test 动画
anim.resume('test');

// 恢复所有动画
anim.resume();

// 指定停止 test 动画
anim.stop('test');

// 停止所有动画
anim.stop();

// 设置 test 动画的当前播放时间为 1s
anim.setCurrentTime(1, 'test');

// 设置所有动画的当前播放时间为 1s
anim.setCurrentTime(1);

Animation State
如果说 AnimationClip 是作为动画数据的承载,那么 AnimationState 则是 AnimationClip 在运行时的实例,它将动画数据解析为方便程序中做计算的数值。
Animation 在播放一个 AnimationClip 的时候,会将 AnimationClip 解析成 AnimationState。
Animation 的播放状态实际都是由 AnimationState 来计算的,包括动画是否循环、怎么循环、播放速度等

获取

var anim = this.getComponent(cc.Animation);
// play 会返回关联的 AnimationState
var animState = anim.play('test');

// 或者直接获取
var animState = anim.getAnimationState('test');

// 获取动画关联的 clip
var clip = animState.clip;

// 获取动画的名字
var name = animState.name;

// 获取动画的播放速度
var speed = animState.speed;

// 使动画播放速度加速
animState.speed = 2;

// 使动画播放速度减速
animState.speed = 0.5;

// 获取动画的播放总时长
var duration = animState.duration;

// 获取动画的播放时间
var time = animState.time;

// 获取动画的重复次数
var repeatCount = animState.repeatCount;

// 获取动画的循环模式
var wrapMode = animState.wrapMode

// 设置循环模式为 Normal
animState.wrapMode = cc.WrapMode.Normal;

// 设置循环模式为 Loop
animState.wrapMode = cc.WrapMode.Loop;

// 设置动画循环次数为 2 次
animState.repeatCount = 2;

// 设置动画循环次数为无限次
animState.repeatCount = Infinity;

// 获取动画是否正在播放
var playing = animState.isPlaying;

// 获取动画是否已经暂停
var paused = animState.isPaused;

// 获取动画的帧率
var frameRate = animState.frameRate;

动画帧事件:

cc.Class({
    extends: cc.Component,
    onAnimCompleted: function (num, string) {
        console.log('onAnimCompleted: param1[%s], param2[%s]', num, string);
    }
});

将上面的组件加到动画的 根节点 上,当动画播放到结尾时,动画系统会自动调用脚本中的 onAnimCompleted 函数。动画系统会搜索动画根节点中的所有组件,如果组件中有实现动画事件中指定的函数的话,就会对它进行调用,并传入事件中填的参数。

并且可以提供注册动画回调以及 动态创建 Animation Clip

音乐与音效

audioSource: { type: cc.AudioSource,default: null }, AudioSource 播放
cc.audioEngine.play(audio, loop, volume); AudioEngine 播放

AudioEngine 与 AudioSource 都能播放音频,它们的区别在于 AudioSource 是组件,可以添加到场景中,由编辑器设置。而 AudioEngine 是引擎提供的纯 API,只能在脚本中进行调用。

一些移动端的浏览器或 WebView 不允许自动播放音频,用户需要在触摸事件中手动播放音频。

cc.Class({
    extends: cc.Component,
    properties: {
       audioSource: cc.AudioSource
    },

    start () {
       let canvas = cc.find('Canvas');
       canvas.on('touchstart', this.playAudio, this);
    },

    playAudio () {
      this.audioSource.play();
    }
});

微信自动音乐播放,并在引擎启动之后,使用其他方式播放音频的时候停止这个音频的播放。

document.addEventListener('WeixinJSBridgeReady', function () {
   cc.resources.load('audio/music_logo', cc.AudioClip, (err, audioClip) => {
       var audioSource = this.addComponent(cc.AudioSource);
       audioSource.clip = audioClip;
       audioSource.play();
   });
});

物理与碰撞系统

碰撞系统

Cocos Creator 内置了一个简单易用的碰撞检测系统,支持 圆形、矩形 以及 多边形 相互间的碰撞检测

点击组件的edit来修改碰撞盒的大小

分组:通过 菜单栏 – 项目 – 项目设置 :设立新的分组,在 分组列表 下面可以进行 碰撞分组配对 表的管理

碰撞系统的脚本控制

当一个碰撞组件被启用时,这个碰撞组件会被自动添加到碰撞检测系统中,并搜索能与之进行碰撞的其他已添加的碰撞组件来生成一个碰撞对。需要注意的是,一个节点上的碰撞组件,无论如何都是不会相互进行碰撞检测的。

获取碰撞检测系统

var manager = cc.director.getCollisionManager();
manager.enabled = true;
manager.enabledDebugDraw = true;
manager.enabledDrawBoundingBox = true;

碰撞系统回调
当碰撞系统检测到有碰撞产生时,将会以回调的方式通知使用者,如果产生碰撞的碰撞组件依附的节点下挂的脚本中有实现以下函数,则会自动调用以下函数,并传入相关的参数。

/*
 * @param  {Collider} other 产生碰撞的另一个碰撞组件
 * @param  {Collider} self  产生碰撞的自身的碰撞组件
 */
onCollisionEnter: function (other, self) {
    console.log('on collision enter');
    // 碰撞系统会计算出碰撞组件在世界坐标系下的相关的值,并放到 world 这个属性里面
    var world = self.world;
    // 碰撞组件的 aabb 碰撞框
    var aabb = world.aabb;
    // 节点碰撞前上一帧 aabb 碰撞框的位置
    var preAabb = world.preAabb;
    // 碰撞框的世界矩阵
    var t = world.transform;
    // 以下属性为圆形碰撞组件特有属性
    var r = world.radius;
    var p = world.position;
    // 以下属性为 矩形 和 多边形 碰撞组件特有属性
    var ps = world.points;
},

onCollisionStay: function (other, self) {
    console.log('on collision stay');
},

onCollisionExit: function (other, self) {
    console.log('on collision exit');
}

碰撞组件
一个节点上可以挂多个碰撞组件,这些碰撞组件之间可以是不同类型的碰撞组件。
碰撞组件目前包括了 Polygon(多边形),Circle(圆形),Box(矩形) 这几种碰撞组件,这些组件都继承自 Collider 组件,所以 Collider 组件的属性它们也都享有。

物理系统

Cocos-creator 的默认物理范例 这个范例里面有不少很不错的代码方法,包括切割、弹性、速度,引力等。

Box2D

物理系统将 box2d 作为内部物理系统,并且隐藏了大部分 box2d 实现细节(比如创建刚体,同步刚体信息到节点中等)。 你可以通过物理系统访问一些 box2d 常用的功能,比如点击测试,射线测试,设置测试信息等。

开启物理系统

cc.director.getPhysicsManager().enabled = true;

开启绘制

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
    ;

设置物理重力,默认的重力加速度是 (0, -320) 世界单位/秒^2,按照上面描述的转换规则,即 (0, -10) 米/秒^2

cc.director.getPhysicsManager().gravity = cc.v2();
cc.director.getPhysicsManager().gravity = cc.v2(0, -640);

物理刷新率/步长,可以进行修改,或许类似FixedUpdate()

var manager = cc.director.getPhysicsManager();

// 开启物理步长的设置
manager.enabledAccumulator = true;

// 物理步长,默认 FIXED_TIME_STEP 是 1/60
manager.FIXED_TIME_STEP = 1/30;

// 每次更新物理系统处理速度的迭代次数,默认为 10
manager.VELOCITY_ITERATIONS = 8;

// 每次更新物理系统处理位置的迭代次数,默认为 10
manager.POSITION_ITERATIONS = 8;

查询物体

物理系统提供了几个方法来高效快速地查找某个区域中有哪些物体,每种方法通过不同的方式来检测物体,基本满足游戏所需。
点测试

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;
}

最后一个参数指定了检测的类型:
cc.RayCastType.Any 检测射线路径上任意的碰撞体,一旦检测到任何碰撞体,将立刻结束检测其他的碰撞体,最快。
cc.RayCastType.Closest 检测射线路径上最近的碰撞体,这是射线检测的默认值,稍慢。
cc.RayCastType.All 检测射线路径上的所有碰撞体,检测到的结果顺序不是固定的。在这种检测类型下,一个碰撞体可能会返回多个结果,这是因为 box2d 是通过检测夹具(fixture)来进行物体检测的,而一个碰撞体中可能由多个夹具(fixture)组成的,慢。
cc.RayCastType.AllClosest 检测射线路径上所有碰撞体,但是会对返回值进行删选,只返回每一个碰撞体距离射线起始点最近的那个点的相关信息,最慢。

射线检测的结果

射线检测的结果包含了许多有用的信息,你可以根据实际情况来选择如何使用这些信息。

collider 指定射线穿过的是哪一个碰撞体。
point指定射线与穿过的碰撞体在哪一点相交。
normal 指定碰撞体在相交点的表面的法线向量。
fraction指定相交点在射线上的分数。

刚体 RigidBody

刚体是组成物理世界的基本对象,你可以将刚体想象成一个你不能看到(绘制)也不能摸到(碰撞)的带有属性的物体(没有渲染也没有碰撞)。

刚体属性:
质量 var mass = rigidbody.getMass(); 刚体的质量是通过 碰撞组件密度大小 自动计算得到的。当你需要计算物体应该受到多大的力时可能需要使用到这个属性。
移动速度 rigidbody.linearVelocity = velocity;
移动速度衰减系数,模拟空气摩擦力 rigidbody.linearDamping = damping;
刚体上某个点的移动速度,
可以传入一个 cc.Vec2 对象作为第二个参数来接收返回值,
刚体的get方法都提供了 out 参数来接收函数返回值。

var velocity = cc.v2();
rigidbody.getLinearVelocityFromWorldPoint(worldPoint, velocity);

旋转速度:rigidbody.angularVelocity = velocity;
旋转衰减速度:var velocity = rigidbody.angularDamping;
旋转固定:rigidbody.fixedRotation = true;

刚体碰撞监听 rigidbody.enabledContactListener = true;

box2d 中只有旋转和位移,并没有缩放,所以如果设置节点的缩放属性时,会重新构建这个刚体依赖的全部碰撞体。一个有效避免这种情况发生的方式是将渲染的节点作为刚体节点的子节点,缩放只对这个渲染节点作缩放,尽量避免对刚体节点进行直接缩放。

每个物理时间步之后会把所有刚体信息同步到对应节点上去,而处于性能考虑,节点的信息只有在用户对节点相关属性进行显示设置时才会同步到刚体上,并且刚体只会监视他所在的节点,即如果修改了节点的父节点的旋转位移是不会同步这些信息的。

刚体类型:
cc.RigidBodyType.Static
静态刚体,零质量,零速度,即不会受到重力或速度影响,但是可以设置他的位置来进行移动。

cc.RigidBodyType.Dynamic
动态刚体,有质量,可以设置速度,会受到重力影响。

cc.RigidBodyType.Kinematic
运动刚体,零质量,可以设置速度,不会受到重力的影响,但是可以设置速度来进行移动。

cc.RigidBodyType.Animated
动画刚体,Animated 是从 Kinematic 类型衍生出来的,一般的刚体类型修改 旋转 或 位移 属性时,都是直接设置的属性,而 Animated 会根据当前旋转或位移属性,与目标旋转或位移属性计算出所需的速度,并且赋值到对应的移动或旋转速度上。添加 Animated 类型主要是防止对刚体做动画时可能出现的奇怪现象,例如穿透。

刚体方法

获取或转换旋转位移属性,使用这些 API 来获取世界坐标系下的旋转位移会比通过节点来获取相关属性更快,因为节点中还需要通过矩阵运算来得到结果,而这些 api 是直接得到结果的。

获取世界坐标

// 直接获取返回值
var out = rigidbody.getWorldPosition();

// 或者通过参数来接收返回值
out = cc.v2();
rigidbody.getWorldPosition(out);

获取世界旋转值 var rotation = rigidbody.getWorldRotation();

世界坐标转换到局部坐标 var localPoint = rigidbody.getLocalPoint(worldPoint);
局部转世界坐标var worldPoint = rigidbody.getWorldPoint(localPoint);
向量转换var worldVector = rigidbody.getWorldVector(localVector);
向量转换var localVector = rigidbody.getLocalVector(worldVector);

获取刚体质心
当对一个刚体进行力的施加时,一般会选择刚体的质心作为施加力的作用点,这样能保证力不会影响到旋转值。
var localCenter = rigidbody.getLocalCenter();var worldCenter = rigidbody.getWorldCenter();

力与冲量
移动一个物体有两种方式,可以施加一个力或者冲量到这个物体上。力会随着时间慢慢修改物体的速度,而冲量会立即修改物体的速度。 当然你也可以直接修改物体的位置,只是这看起来不像真实的物理,你应该尽量去使用力或者冲量来移动刚体,这会减少可能带来的奇怪问题。

// 施加一个力到刚体上指定的点上,这个点是世界坐标系下的一个点
rigidbody.applyForce(force, point);

// 或者直接施加力到刚体的质心上
rigidbody.applyForceToCenter(force);

// 施加一个冲量到刚体上指定的点上,这个点是世界坐标系下的一个点
rigidbody.applyLinearImpulse(impulse, point);

力与冲量也可以只对旋转轴产生影响,这样的力叫做扭矩。

// 施加扭矩到刚体上,因为只影响旋转轴,所以不再需要指定一个点
rigidbody.applyTorque(torque);

// 施加旋转轴上的冲量到刚体上
rigidbody.applyAngularImpulse(impulse);

有些时候需要获取刚体在某一点上的速度时,可以通过 getLinearVelocityFromWorldPoint 来获取,比如当物体碰撞到一个平台时,需要根据物体碰撞点的速度来判断物体相对于平台是从上方碰撞的还是下方碰撞的。

碰撞组件

物理碰撞组件属性
sensor – 指明碰撞体是否为传感器类型,传感器类型的碰撞体会产生碰撞回调,但是不会发生物理碰撞效果。
density – 碰撞体的密度,用于刚体的质量计算
friction – 碰撞体摩擦力,碰撞体接触时的运动会受到摩擦力影响
restitution – 碰撞体的弹性系数,指明碰撞体碰撞时是否会受到弹力影响

内部细节

物理碰撞组件内部是由 box2d 的 b2Fixture 组成的,由于 box2d 内部的一些限制,一个多边形物理碰撞组件可能会由多个 b2Fixture 组成。
当多边形物理碰撞组件的顶点组成的形状为凹边形时,物理系统会自动将这些顶点分割为多个凸边形。
当多边形物理碰撞组件的顶点数多于 b2.maxPolygonVertices(一般为 8) 时,物理系统会自动将这些顶点分割为多个凸边形。
一般情况下这些细节是不需要关心的,但是当使用射线检测并且检测类型为 cc.RayCastType.All 时,一个碰撞体就可能会检测到多个碰撞点,原因即是检测到了多个 b2Fixture。

碰撞回调

类似Unity的Oncollision,onTriggerEnter 这两个处理的不一样

  1. 需要先在rigidbody中开启碰撞监听 rigidbody.enabledContactListener = true;,才会有相应的回调产生。
  2. 回调中的信息在物理引擎都是以缓存的形式存在的,所以信息只有在这个回调中才是有用的,不要在你的脚本里直接缓存这些信息,但可以缓存这些信息的副本。
  3. 在回调中创建的物理物体,比如刚体,关节等,这些不会立刻就创建出 box2d 对应的物体,会在整个物理系统更新完成后再进行这些物体的创建。

定义回调函数

cc.Class({
    extends: cc.Component,

    // 只在两个碰撞体开始接触时被调用一次
    onBeginContact: function (contact, selfCollider, otherCollider) {
    },

    // 只在两个碰撞体结束接触时被调用一次
    onEndContact: function (contact, selfCollider, otherCollider) {
    },

    // 每次将要处理碰撞体接触逻辑时被调用
    onPreSolve: function (contact, selfCollider, otherCollider) {
    },

    // 每次处理完碰撞体接触逻辑时被调用
    onPostSolve: function (contact, selfCollider, otherCollider) {
    }
});

回调的顺序

当两个碰撞体相互覆盖时,box2d 默认的行为是给每个碰撞体一个冲量去把它们分开,但是这个行为不一定能在一个时间步内完成。 像这里显示的一样,示例中的碰撞体会在三个时间步内相互覆盖直到“反弹”完成并且它们相互分离。

在这个时间里我们可以定制我们想要的行为,onPreSolve 会在每次物理引擎处理碰撞前回调,我们 可以在这个回调里修改碰撞信息,而 onPostSolve 会在处理完成这次碰撞后回调,我们可以在这个回调中获取到物理引擎计算出的碰撞的冲量信息。

下面给出的输出信息能使我们更清楚回调的顺序

 ...
Step
Step
BeginContact
PreSolve
PostSolve
Step
PreSolve
PostSolve
Step
PreSolve
PostSolve
Step
EndContact
Step
Step
...

回调的参数 contact

回调的参数包含了所有的碰撞接触信息,每个回调函数都提供了三个参数:contactselfColliderotherCollider

selfColliderotherCollider 很容易理解,如名字所示,selfCollider 指的是回调脚本的节点上的碰撞体,ohterCollider 指的是发生碰撞的另一个碰撞体。

最主要的信息都包含在 contact 中,这是一个 cc.PhysicsContact 类型的实例,可以在 api 文档中找到相关的 API。contact 中比较常用的信息就是碰撞的位置和法向量,contact 内部是按照刚体的本地坐标来存储信息的,而我们一般需要的是世界坐标系下的信息,我们可以通过 contact.getWorldManifold 来获取这些信息。

var worldManifold = contact.getWorldManifold();
var points = worldManifold.points;
var normal = worldManifold.normal;

points碰撞点数组,它们不一定会精确的在碰撞体碰撞的地方上,如下图所示(除非你将刚体设置为子弹类型,但是会比较耗性能),但实际上这些点在使用上一般都是够用的。
normal 碰撞点上的法向量,由自身碰撞体指向对方碰撞体,指明解决碰撞最快的方向。

碰撞法向量并不是碰撞体碰撞的角度,他只会指明可以解决两个碰撞体相互覆盖这一问题最短的方向。

如果你希望知道碰撞的真正的方向,可以使用下面的方式:

var vel1 = triangleBody.getLinearVelocityFromWorldPoint(worldManifold.points[0]);
var vel2 = squareBody.getLinearVelocityFromWorldPoint(worldManifold.points[0]);
var relativeVelocity = vel1.sub(vel2);

修改contact

//禁用
contact.disabled = true;
contact.disabledOnce = true;

onPreSolve 中修改 contact 的信息,因为 onPreSolve 是在物理引擎处理碰撞信息前回调的,所以对碰撞信息的修改会影响到后面的碰撞计算。

// 修改碰撞体间的摩擦力
contact.setFriction(friction);

// 修改碰撞体间的弹性系数
contact.setRestitution(restitution);

关节组件

物理系统包含了一系列用于链接两个刚体的关节组件。关节组件可以用来模拟真实世界物体间的交互,比如铰链,活塞,绳子,轮子,滑轮,机动车,链条等。

目前物理系统中提供了以下可用的关节组件:
Revolute Joint – 旋转关节,可以看做一个铰链或者钉,刚体会围绕一个共同点来旋转。
Distance Joint – 距离关节,关节两端的刚体的锚点会保持在一个固定的距离。
Prismatic Joint – 棱柱关节,两个刚体位置间的角度是固定的,它们只能在一个指定的轴上滑动。
Weld Joint – 焊接关节,根据两个物体的初始角度将两个物体上的两个点绑定在一起。
Wheel Joint – 轮子关节,由 Revolute 和 Prismatic 组合成的关节,用于模拟机动车车轮。
Rope Joint – 绳子关节,将关节两端的刚体约束在一个最大范围内。
Motor Joint – 马达关节,控制两个刚体间的相对运动。

属性
connectedBody – 关节链接的另一端的刚体
anchor – 关节本端链接的刚体的锚点
connectedAnchor – 关节另一端链接的刚体的锚点
collideConnected – 关节两端的刚体是否能够互相碰撞

0 条评论