cocos学习心得

1,004 阅读16分钟

UI学习

组件

1、Sprite组件

Atlas Sprite:显示图片资源所属的Atlas图集资源

Sprite Frame:渲染Sprite使用的SpriteFrame图片资源

Type模式:普通模式(Simple,按照原始图片进行渲染)

九宫格模式(Sliced,常用于UI元素)

平铺模式(Tiled,好像css中的repeat)

填充模式(Filled,常用于进度条的动态展示)

2、Label组件

排版(Overflow)

文本缓存类型(Cache Mode)

3、LabelOutline组件,用于给Label组件添加描边效果。

4、LabelShadow组件,为Label组件添加阴影效果

5、Mask组件,遮罩组件,用于规定子节点可渲染的范围

脚本学习

  • 生命周期函数

    • onLoad

onLoad 回调会在节点首次激活时触发,比如所在的场景被载入,或者所在节点被激活的情况下。在 onLoad 阶段,保证了你可以获取到场景中的其他节点,以及节点关联的资源数据。onLoad 总是会在任何 start 方法调用前执行,这能用于安排脚本的初始化顺序。通常我们会在 onLoad 阶段去做一些初始化相关的操作。

  • start

start 回调函数会在组件第一次激活前,也就是第一次执行 update 之前触发。start 通常用于初始化一些需要经常修改的数据,这些数据可能在 update 时会发生改变。

  • update

每一帧调用一次update函数,在所有动画之前进行调用,**update**中的**dt**是距离**上次**刷新的时间

  • lateUpdate

如果我们要在动效(如动画、粒子、物理等)更新之后才进行一些额外操作,或者希望在所有组件的 update 都执行完之后才进行其它操作,那就需要用到 lateUpdate 回调。

  • onDestroy

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

  • onEnable

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

  • onDisable

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

sometips:

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

其中,onLoadstart 常常用于组件的初始化,只有在节点 activeInHierarchy 的情况下才能调用,并且最多只会被调用一次。除了上文提到的内容以及调用顺序的不同,它们还有以下区别:

无法复制加载中的内容

  • 创建和销毁节点

1、创建新节点

通过cc.Node创建

cc.Class({
  extends: cc.Component,

  properties: {
    sprite: {
      default: null,
      type: cc.SpriteFrame,
    },
  },

  start: function () {
    var node = new cc.Node('Sprite');//创建一个新的节点
    var sp = node.addComponent(cc.Sprite);//给新节点添加一个Sprite组件

    sp.spriteFrame = this.sprite;//更改组件的图片
    node.parent = this.node;//将新节点作为当前节点的子节点
  },
});

2、克隆已有节点

通过 cc.instantiate 方法完成

cc.Class({
  extends: cc.Component,

  properties: {
    target: {//关联编辑器,换一个名字也可以
      default: null,
      type: cc.Node,
    },
  },

  start: function () {
    var scene = cc.director.getScene();//获得当前的场景
    var node = cc.instantiate(this.target);//克隆target节点

    node.parent = scene;//将克隆节点加到场景中
    node.setPosition(0, 0);//设置节点位置
  },
});

3、创建一个预制节点

你可以设置一个预制(Prefab)并通过 cc.instantiate 生成节点。

cc.Class({
  extends: cc.Component,

  properties: {
    target: {
      default: null,
      type: cc.Prefab,
    },
  },

  start: function () {
    var scene = cc.director.getScene();
    var node = cc.instantiate(this.target);

    node.parent = scene;
    node.setPosition(0, 0);
  },
});

4、销毁节点

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

  • 访问节点和组件

1、访问组件所在的节点

在组件方法里访问this.node变量

2、查找子节点

可以使用this.node.children,为一个数组

也可以使用this.node.getChildByName("");

或者如果子节点的层次比较深,可以使用cc.find

3、获得其他组件

可以使用getComponent这个API获得同一个节点上的其它组件

getComponent()里面传递的参数可以是类型(如cc.Label)或者类名

4、利用属性检查器设置节点

  • 事件

一、事件的监听和关闭

  • 事件监听:使用on函数

    cc.Class({ extends: cc.Component,

    properties: { },

    onLoad: function () { //使用函数绑定 this.node.on('mousedown', function ( event ) { console.log('Hello!');//如果要使用this,可以用bind或者箭头函数改变this的指向 }); //使用第三个参数,可以传第三个参数 target,用于绑定响应函数的调用者,与上面功能一样 this.node.on('mousedown', function( event ){ this.enabled = false; },this}; }, });

once 监听在监听函数响应后就会关闭监听事件

  • 关闭监听,使用off方法,注意的是off方法的参数必须与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); }, });

二、触摸事件,在移动端和PC端都会触发,所以只要监听触摸事件可以同时响应移动平台的触摸事件和桌面端的鼠标事件。

事件的事件名有touchstart、touchmove、touchend、touchcancel

触摸事件(cc.Event.EventTouch)的一些API:touch、getID、getLocation等等

this.node.on('touchmove', function ( event ) {
   this.node.color=cc.Color.RED;
   console.log(event.getLocation());  // 获得触摸位置
   console.log(event.getDelta());  // 获得X Y位置与上次变化了多少
   console.log(event.getID); //  在多点触控时获得ID号
   event.stopPropagation();  //停止事件冒泡
}.bind(this));

三、鼠标事件,只在桌面平台才会触发,事件名有mousedown、mouseenter等等

相关的API有getLocation、getDelta等等

四、键盘事件与重力传感事件,都是使用on进行注册

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

五、自定义事件

可以通过emit和dispatchEvent。emit只能传递给自己,dispatchEvent可以向上冒泡。

  • 动作相关

动作系统应用

1、动作系统API

  • 动作系统的API都是相对节点而言的,是用来操作节点的,而非组件

    // 创建一个动作 var action = cc.moveTo(1, 100, 100);// 参数:维持时间、X坐标、y坐标 // 节点执action行动作 node.runAction(action); // 停止一个动作 node.stopAction(action); // 停止所有动作 node.stopAllActions();

可以给动作设置tag,通过tag来控制动作

2、动作类型

  • 基础动作,实现各种形变,位移动画的动作,如cc.moveTo,cc.rotateBy。。。大致分为时间间隔动作和即时动作:
    • 时间间隔动作是在一定时间间隔内完成的渐变动作
    • 即时动作立即发生,比如用来调用回调函数的cc.callFunc;用来隐藏节点的cc.hide
  • 容器动作

对已有的一些动作进行管理和修饰

  • 顺序动作cc.sequence

    // 让节点在两个点之间来回移动 var seq = cc.sequence(cc.moveBy(1, 200, 0), cc.moveBy(1, -200, 0)); node.runAction(seq);

  • 同步动作cc.spawn,同步执行一系列子动作

  • 重复动作cc.repeat,多次重复一个动作,指定次数

  • 永远重复动作cc.repeatForever,一直重复一个动作

  • 速度动作cc.speed,可以改变目标动作的执行速率,让动作更快或更慢完成

  • 缓动动作,用来修改基础动作的时间曲线,让动作有快入、缓入等等特效,只有时间间隔动作支持缓动

  • 回调动作

第一个参数是执行时函数,第二个参数指定了处理回调方法的context(也就是绑定this),第三个参数向处理回调方法的传参。

// 动作回调函数的声明:两种
var finished = cc.callFunc(this.myMethod, this, opt);// 方法一

var finished = cc.callFunc(function(target, score) {  // 方法二
    this.score += score;
}, this, 100);//动作完成后会给玩家加100分


// 在声明了回调动作  finished  后,您可以配合  cc.sequence  来执行一整串动作并触发回调:
var myAction = cc.sequence(cc.moveBy(1, cc.v2(0, 100)), cc.fadeOut(1), finished);


// 在同一个 sequence 里也可以多次插入回调:
var myAction = cc.sequence(cc.moveTo(1, cc.v2(0, 0)), finished1, cc.fadeOut(1), finished2); 
// 注意:finished1, finished2 都是使用 cc.callFunc 定义的回调动作

注意: 在 cc.callFunc 中不应该停止自身动作,由于动作是不能被立即删除,
如果在动作回调中暂停自身动作会引发一系列遍历问题,导致更严重的 bug。

动作汇总(见官网API)

缓动系统(cc.tween)

比cc.Action更加简洁易用,cc.tween提供了链式创建的方法,可以对任何对象进行操作,并且可以对对象的任意属性进行缓动

// 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()

1、链式API,在调用start之前生成的action队列重新组合生成一个cc.sequence队列,所以cc.tween的链式结构是依次执行每一个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

2、设置缓动属性

两个设置属性的API:

to:对属性进行绝对值计算,最终结果即是设置的属性值

by:对属性进行相对值计算,最终结果是设置的属性值加上开始运行时节点的属性值

3、easing,可以使用easing使得缓动更生动

4、插入其他的缓动到队列中,可以先创建一些固定的缓动,然后通过组合这些缓动形成新的缓动

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)
// 先缩放再移动
cc.tween(this.node).then(scale).then(move)

5、并行执行缓动,通过.parallel()接口

6、回调,通过.call()接口

7、重复执行,通过.repeat()接口或者.repeatForever()接口

8、延迟执行,通过.delay()接口。

  • 加载与切换场景

一、加载与切换场景

代码:cc.director.loadScene("MyScene");

  • 1、通过常驻节点来进行场景资源管理和参数传递

引擎同时只会运行一个场景,当切换场景时,默认会将场景内所有节点和其他实例销毁。如果我们需要用一个组件控制所有场景的加载,或在场景之间传递参数数据,就需要将该组件所在节点标记为「常驻节点」,使它在场景切换时不被自动销毁,常驻内存。

标记常驻节点:**cc.game.addPersistRootNode(myNode);**

取消常驻节点 :**cc.game.removePersistRootNode(myNode);**

需要注意的是上面的 API 并不会立即销毁指定节点,只是将节点还原为可在场景切换时销毁的节点。

  • 2、场景加载回调

代码:cc.director.loadScene("MyScene", onSceneLaunched);

上一行里 onSceneLaunched 就是声明在本脚本中的一个回调函数,在场景加载后可以用来进一步的进行初始化或数据传递的操作。

由于回调函数只能写在本脚本中,所以场景加载回调通常用来配合常驻节点,在常驻节点上挂载的脚本中使用。

  • 预加载场景

后台静默加载新场景,并在加载完成后手动进行切换。

cc.director.preloadScene("table", function () {
   cc.log("Next scene preloaded");
});

之后在合适的时间调用 loadScene , 就可以真正切换场景。

cc.director.loadScene("table");

就算预加载还没完成,你也可以直接调用 cc.director.loadScene ,预加载完成后场景就会启动。

计时器可以使用schedule、scheduleOnce,取消计时器使用unshedule,unscheduleAllCallbacks;

  • 获取与加载资源

一、动态加载

tips:

  1. 是所有需要通过脚本动态加载的资源,都必须放置在resources 文件夹它的子文件夹下。 resources 需要在 assets 文件夹中手工创建,并且必须位于 assets 的根目录。
  2. 要注意的是 Creator 相比之前的 Cocos2d-JS,资源动态加载的时候都是异步的,**需要在回调函数中获得载入的资源** 。这么做是因为 Creator 除了场景关联的资源,没有另外的资源预加载列表,动态加载的资源是真正的动态加载。

1、动态加载Asset

Creator 提供了 cc.loader.loadRes 这个 API 来专门加载那些位于 resources 目录下的 Asset。

和 cc.loader.load不同的是,loadRes 一次只能加载单个 Asset。调用时,你只要传入相对 resources 的路径即可,并且路径的结尾处不能包含文件扩展名。

2、加载 SpriteFrame

图片设置为 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;
});

3、加载图集中的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;
});

4、资源释放

调用cc.loader.releaseRes,传入与loadRes相同的路径和类型参数

cc.loader.releaseRes("test assets/image", cc.SpriteFrame);
cc.loader.releaseRes("test assets/anim");

// 此外,你也可以使用  cc.loader.releaseAsset  来释放特定的 Asset 实例。
cc.loader.releaseAsset(spriteFrame);

5、资源批量加载

调用cc.loader.loadResDir可以加载相同路径下的多个资源:

// 加载 test assets 目录下所有资源
cc.loader.loadResDir("test assets", function (err, assets) {
   // ...
});

// 加载 test assets 目录下所有 SpriteFrame,并且获取它们的路径
cc.loader.loadResDir("test assets", cc.SpriteFrame, function (err, assets, urls) {
   // ...
});

二、加载远程资源和设备资源

需要调用cc.loader.load,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.NodePool的实例来初始化一种节点的对象池。当我们需要创建节点时,向对象池申请一个节点,如果对象池里有空闲的可用节点,就会把节点返回给用户,用户通过node.addChild将这个新节点加入到场景节点树中。

当我们需要销毁节点时,调用对象池实例的put(node) 方法,传入需要销毁的节点实例,对象池会自动完成把节点从场景节点树中移除的操作,然后返回给对象池。

这样就实现了少数节点的循环利用。

具体操作如下:

第一步:准备好Prefab

第二步:初始化对象池

将需要数量的节点创建出来,并放进对象池

properties: {
    enemyPrefab: cc.Prefab  // 所需要的预制体
},
onLoad: function () {
        //  创建对象池
    this.enemyPool = new cc.NodePool();
    
    let initCount = 5;
    for (let i = 0; i < initCount; ++i) {
        let enemy = cc.instantiate(this.enemyPrefab); // 创建节点
        this.enemyPool.put(enemy); // 通过 put 接口放入对象池
    }
}

第三步:从对象池请求对象

createEnemy: function (parentNode) {
    let enemy = null;
    if (this.enemyPool.size() > 0) { // 通过 size 接口判断对象池中是否有空闲的对象
            // get()获取对象
        enemy = this.enemyPool.get();
        
    } else { // 如果没有空闲对象,也就是对象池中备用对象不够时,我们就用 cc.instantiate 重新创建
        enemy = cc.instantiate(this.enemyPrefab);
    }
    enemy.parent = parentNode; // 将生成的敌人加入节点树
    enemy.getComponent('Enemy').init(); //接下来就可以调用 enemy 身上的脚本进行初始化
}

第四步:将对象返回对象池

使用put方法

onEnemyKilled: function (enemy) {
    // enemy 应该是一个 cc.Node
    this.enemyPool.put(enemy); // 和初始化时的方法一样,将节点放进对象池,这个方法会同时调用节点的 removeFromParent
}

第五步:使用组件来处理回收和复用的事件

使用构造函数创建对象池时,可以指定一个组件类型或名称,作为挂载在节点上用于处理节点回收和复用事件的组件。

在创建对象池时可以用:

let menuItemPool = new cc.NodePool('MenuItem'); // 指定一个组件类型

这样当使用 menuItemPool.get() 获取节点后,就会调用 MenuItem 里的 reuse方法,完成点击事件的注册。

当使用menuItemPool.put(menuItemNode)回收节点后,会调用 MenuItem 里的 unuse方法,完成点击事件的反注册。

cc.Class({
    extends: cc.Component,
    onLoad: function () {
        this.node.selected = false;
        this.node.on(cc.Node.EventType.TOUCH_END, this.onSelect, this.node);
    },
        // put() 收回对象池时会调用
    unuse: function () {
        this.node.off(cc.Node.EventType.TOUCH_END, this.onSelect, this.node);
    },
        // get()获取对象池内对象时会调用
    reuse: function () {
        this.node.on(cc.Node.EventType.TOUCH_END, this.onSelect, this.node);
    }
});

第六步:清除对象池

调用myPool.clear()

  • 无死角Camera

Camera属性介绍:

  • depth:摄像机深度,用于决定摄像机的渲染顺序。值越大,则摄像机越晚被渲染。这样就可以制作出层级关系的画面
  • cullingMask:将决定这个摄像机用来渲染场景的哪些部分。
  • clearFlags:指定渲染摄像机需要做的清除操作
  • rect:决定摄像机绘制在屏幕上的哪个区域
  • zoomRatio:指定摄像机的缩放比例,值越大显示的图像越大
  • alignWithScreen:当该值为true时,摄像机会自动将视窗大小调整为整个屏幕的大小。

摄像机相关方法:

1、cc.Camera.findCamera,通过查找当前所有摄像机的cullingMask是否包含节点的group来获取第一个匹配的摄像机

2、containsNode,检测节点是否被此摄像机影响

3、render,调用这个方法来手动渲染摄像机

坐标转换

一个常见的问题是,当摄像机被移动、旋转或者缩放后,这时候用点击事件获取到的坐标去测试节点的坐标,这样往往是获取不到正确结果的。

因为这时候获取到的点击坐标是屏幕坐标系下的坐标了,我们需要将这个坐标转换到世界坐标系下,才能继续与节点的世界坐标进行运算。

下面是一些坐标系转换的函数:

// 将一个屏幕坐标系下的点转换到世界坐标系下

camera.getScreenToWorldPoint(point, out);

// 将一个世界坐标系下的点转换到屏幕坐标系下

camera.getWorldToScreenPoint(point, out);

// 获取屏幕坐标系到世界坐标系的矩阵,只适用于 2D 摄像机并且 alignWithScreen 为 true 的情况

camera.getScreenToWorldMatrix2D(out);

// 获取世界坐标系到屏幕坐标系的矩阵,只适用于 2D 摄像机并且 alignWithScreen 为 true 的情况

camera.getWorldToScreenMatrix2D(out);

Animation动画

一、动画制作的流程

创建节点 ----> 挂载Animation组件 ----> 创建Animation Clip文件 ----> 将Animation Clip文件挂载到相应的节点上 ----> 编辑Animation Clip文件 ----> 最后代码控制动画

碰撞