CocosCreator-Schedule计时器-设定及触发原理_cocos creator scheduleonce delay-程序员宅基地

技术标签: cocos-creator  CocosCreator  javascript  

计时器

JavaScript自带的定时任务

setTimeout

作用:设置一个定时器,在指定时间(毫秒)后触发,调用传入的回调函数。
参数类型:(function/code, delayTime, args…)
function: 回调函数
code: 字符串形式的代码,会使用eval()执行,因此不推荐使用
delayTime: (可选) 延迟的时间(毫秒),默认0,尽快执行。
args: (可选) 不定长参数列表,会传递给回调函数

setInterval

作用:设置一个定时器,隔一段时间触发一次,调用传入的回调函数。
参数类型:(function/code, delayTime, args…)
和sertTimeout一致,区别是setInterval会一直执行回调函数,但setTimeout**仅执行一次。**delayTime就是每次间隔的时间。

注意事项
  • 回调函数中的this可能和你期望的不太一样,所以一般会使用箭头函数/bind的方式修改this的指向
// UserInfoView.js
cc.Class({
    
    extends: cc.Component,
    ctor () {
    
      // 定义测试变量
      this.testInfo = 222;
    },
    start () {
    
        // 1. 箭头函数
        setTimeout(() => {
    
          // do something
        }, 10000);

        // 2. bind方式
        setTimeout(function() {
    
          // do something
          cc.log(this);								// 指向当前组件 UserInfoView{xxxxx}
          cc.log(this.testInfo);			// 222
        }.bind(this), 1000);

        // 3. 直接传递
        setTimeout(function() {
    
          // do something
          cc.log(this);								// 指向Window对象
          cc.log(this.testInfo);			// undefined
        }, 1000);
  }

Cocos Creator中的定时任务(计时器)

schedule

作用:结合了setTimeout和setInterval,如官方文档所说,提供了更大的灵活性,可以设置执行次数、第一次的延迟以及触发间隔时间。注:该方法定义于cc.Component,故只能在继承cc.Component的对象上调用(从编辑器创建的js类会默认继承cc.Component)。
参数列表:(callback, interval, repeat, delay)
callback: 回调函数
interval: 触发间隔(秒)
repeat: 重复次数,可设置cc.macro.REPEAT_FOREVER让它一直重复。注:实际执行次数是重复次数+1
delay: 延迟时间(秒)

设置定时器
// CCComponent.js		cocos2d\core\components\CCComponent.js
schedule (callback, interval, repeat, delay) {
    
  // 1619: callback function must be non-null 回调函数不能为空
  cc.assertID(callback, 1619);
	
  // 默认触发间隔为0
  interval = interval || 0;
  // 1620:interval must be positive 间隔必须是有效的(>=0)
  cc.assertID(interval >= 0, 1620);
	
  // repeat不是数字时, 默认为无限重复
  repeat = isNaN(repeat) ? cc.macro.REPEAT_FOREVER : repeat;
  // 默认延迟时间为0
  delay = delay || 0;
	
  // 获得获取系统定时器(是唯一的,cc.director是单例的,在init方法中创建了一个Scheduler定时器)
  // scheduler: cc.Scheduler类型 定义于engine/cocos2d/core/CCScheduler.js
  var scheduler = cc.director.getScheduler();
	
  // scheduler内部维护了一个叫_hashForTimers的键值对,通过传入的this可以获得一个HashTimerEntry对象(定义于CCScheduler)
  // HashTimerEntry中保存了paused属性(boolean),当组件_onDisabled/_onEnabled时,paused会被设置为true/false
  // 也有可能是_hashForUpdates。_hashForTimers保存的是自定义定时器的信息,_hashForUpdates保存的是update定时器的信息
  var paused = scheduler.isTargetPaused(this);
	
  // 调用CCScheduler中的方法,这里传递的target为this,所以回调函数不需要bind/使用箭头函数
  scheduler.schedule(callback, this, interval, repeat, delay, paused);
}
// CCScheduler.js		cocos2d\core\CCScheduler.js
schedule: function (callback, target, interval, repeat, delay, paused) {
    
  'use strict';
  if (typeof callback !== 'function') {
    
  	// 交换callback和target的值
    var tmp = callback;
    callback = target;
    target = tmp;
  }
  
  // 适配不同参数长度
  //selector, target, interval, repeat, delay, paused
  //selector, target, interval, paused
  if (arguments.length === 4 || arguments.length === 5) {
    
    paused = !!repeat;
    repeat = cc.macro.REPEAT_FOREVER;
    delay = 0;
  }
	
  // 1502:cc.scheduler.scheduleCallbackForTarget(): target should be non-null. target不能为空
  cc.assertID(target, 1502);
	
  // 获得组件的id
  var targetId = target._id;
  if (!targetId) {
    
    if (target.__instanceId) {
    
      // 1513:cc.Scheduler: scheduler stopped using `__instanceId` as id since v2.0, you should do scheduler.enableForTarget(target) before all scheduler API usage on target
      // 提示2.0版本后不再使用__instanceId属性 这里应该是为了保持兼容
      cc.warnID(1513);		
      targetId = target._id = target.__instanceId;
    }
    else {
    
      // 1510:cc.Scheduler: Illegal target which doesn't have uuid or instanceId.
      // target非法,没有uuid和instanceId
      cc.errorID(1510);
    }
  }
  
  // 通过targetId获得对应的定时器实例(HashTimerEntry),有点拗口的感觉,里面保存了timers target paused等属性 
  var element = this._hashForTimers[targetId];
  if (!element) {
    
    // 没有取到实例 使用get方法从对象池(_hashTimerEntries)中获得一个实例
    // Is this the 1st element ? Then set the pause level to all the callback_fns of this target
    element = HashTimerEntry.get(null, target, 0, null, null, paused);
    this._arrayForTimers.push(element);
    this._hashForTimers[targetId] = element;
  } else if (element.paused !== paused) {
    
    // 1511:cc.Scheduler: pause state of the scheduled task doesn't match the element pause state in Scheduler, the given paused state will be ignored.
    // 定时器实例中的paused属性和传入的paused不匹配,传入的值会被忽略(可能在创建的过程中,组件disable/enable状态改变了)
    cc.warnID(1511);
  }

  var timer, i;
  // 定时器列表(timers)为空 初始化为空数组
  if (element.timers == null) {
    
    element.timers = [];
  }
  else {
    
    // 遍历列表 判断是否有相同的回调函数
    for (i = 0; i < element.timers.length; ++i) {
    
      timer = element.timers[i];
      if (timer && callback === timer._callback) {
    
        // 1507:CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: %s to %s"
        // 回调已存在,将更新interval属性
        cc.logID(1507, timer.getInterval(), interval);
        timer._interval = interval;
        return;
      }
    }
  }
	
  // 获得一个定时器实例(CallbackTimer),这里也有一个对象池(_timers)
  timer = CallbackTimer.get();
  // 初始化 里面的代码基本类似 _callback = callback 这样的赋值
  timer.initWithCallback(this, callback, target, interval, repeat, delay);
  // 加到定时器列表中
  element.timers.push(timer);
	
  // 修改_currentTargetSalvaged防止当前的HashTimerEntry被删除 这个在update函数中会有相关解释
  if (this._currentTarget === element && this._currentTargetSalvaged) {
    
    this._currentTargetSalvaged = false;
  }
}

以上就是创建计时器的代码,感觉许多代码量都在做一些验证、兼容之类的操作,最后创建计时器相关对象储存起来,而触发回调的代码则是在update函数中

Scheduler的update触发逻辑
// scheduler的update函数在cc.director.mainLoop()中触发
// CCDirector.js		cocos2d\core\CCDirector.js
this._scheduler.update(this._deltaTime);

// 而director的mainLoop函数在cc.game._runMainLoop()中触发
// CCGame.js				cocos2d\core\CCGame.js
_runMainLoop: function () {
    
  // 省略部分代码
  callback = function (now) {
    
    if (!self._paused) {
    
      self._intervalId = window.requestAnimFrame(callback);
      if (!CC_JSB && !CC_RUNTIME && frameRate === 30) {
    
        if (skip = !skip) {
    
          return;
        }
      }
      // 在这~
      director.mainLoop(now);
    }
  };
	
  // 使用requestAnimFrame设置一个每帧触发的回调,会在下一次的重绘之前被调用,一般来说60次/秒。
  // 详情可参考MDN文档https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame
  self._intervalId = window.requestAnimFrame(callback);
  self._paused = false;
}
Scheduler中的update函数
// CCScheduler.js		cocos2d\core\CCScheduler.js
/**
     * !#en 'update' the scheduler. (You should NEVER call this method, unless you know what you are doing.)
     * !#zh update 调度函数。(不应该直接调用这个方法,除非完全了解这么做的结果)
     * @method update
     * @param {Number} dt delta time
     */
update: function (dt) {
    
  // boolean属性 加锁 避免执行update定时器过程中出现删除操作
  this._updateHashLocked = true;
  // 时间缩放 可以实现慢/快动作
  if(this._timeScale !== 1)
    dt *= this._timeScale;

  var i, list, len, entry;
	
  // 此处省略触发update定时器的代码

  // Iterate over all the custom selectors
  // 遍历自定义定时器
  var elt, arr = this._arrayForTimers;
  for(i=0; i<arr.length; i++){
    
    elt = arr[i];
    this._currentTarget = elt;
    this._currentTargetSalvaged = false;
		
    // 目标没有被停止则执行
    if (!elt.paused){
    
      // The 'timers' array may change while inside this loop
      // 遍历定时器列表。列表有可能在循环过程中被改变(repeat次数执行完了)
      for (elt.timerIndex = 0; elt.timerIndex < elt.timers.length; ++(elt.timerIndex)){
    
        elt.currentTimer = elt.timers[elt.timerIndex];
        elt.currentTimerSalvaged = false;
				
        // 调用定时器的update函数
        elt.currentTimer.update(dt);
        elt.currentTimer = null;
      }
    }

    // only delete currentTarget if no actions were scheduled during the cycle (issue #481)
    // _currentTargetSalvaged在上面被设为false,但仍有可能在update函数执行后被改变(repeat次数执行完了)
    // 但是不会直接回收,而是通过设置_currentTargetSalvaged,在这里统一回收。(避免了重复回收?)
    if (this._currentTargetSalvaged && this._currentTarget.timers.length === 0) {
    
      this._removeHashElement(this._currentTarget);
      --i;
    }
  }

  // 此处省略清理update定时器的代码
	
  // 解除锁
  this._updateHashLocked = false;
  this._currentTarget = null;
}
CallbackTimer中的update函数
// CallbackTimer		定义于cocos2d\core\CCScheduler.js
/**
 * triggers the timer
 * @param {Number} dt delta time
 */
proto.update = function (dt) {
    
  	// _elapsed: boolean 离上一次触发的事件 调用CallbackTimer.initWithCallback()的时候会被设为-1
    if (this._elapsed === -1) {
    
      	// 第一次触发的时候 重置_elapsed和_timesExecuted,(所以定时器是在下一帧开始才正式生效的?)
        this._elapsed = 0;
        this._timesExecuted = 0;
    } else {
    
      	// 累加_elapsed 实现delay和interval的效果
        this._elapsed += dt;
     		// 当前计时器是一直循环的且不需要延迟
        if (this._runForever && !this._useDelay) {
    //standard timer usage
          	// 标准触发流程
          	// _elapsed达到interval间隔时间,触发事件,重置_elapsed
            if (this._elapsed >= this._interval) {
    
                this.trigger();
                this._elapsed = 0;
            }
        } else {
    //advanced usage
          	// 高级用法(?)
            if (this._useDelay) {
    
          			// 需要延迟 达到延迟时间的时候触发
                if (this._elapsed >= this._delay) {
    
                    this.trigger();
										
                  	// 扣除延迟的时间、已执行次数+1、重置延迟状态为false
                    this._elapsed -= this._delay;
                    this._timesExecuted += 1;
                    this._useDelay = false;
                }
            } else {
    
              	// 不需要延迟(或延迟已经触发过了) 达到间隔时间触发
                if (this._elapsed >= this._interval) {
    
                    this.trigger();
										
                  	// 重置_elapsed、已执行次数+1
                    this._elapsed = 0;
                    this._timesExecuted += 1;
                }
            }

          	// 设置了repeat次数的定时器,执行次数大于_repeat次数的时候 取消这个定时器
          	// 这就是上面的update中提到的定时器列表有可能被改变
            if (this._callback && !this._runForever && this._timesExecuted > this._repeat)
                this.cancel();
        }
    }
};
// 触发回调
proto.trigger = function () {
    
    if (this._target && this._callback) {
    
      	// 加锁 放回对象池中的时候会判断_lock是否为true
        this._lock = true;
      	// 调用回调函数
        this._callback.call(this._target, this._elapsed);
        this._lock = false;
    }
};
使用方法
start () {
    
    this.testInfo = 222;
		
    this.schedule((dt)=>{
    
      // 每0.1s执行一次
      cc.log("回调1", dt);
    }, 0.1);

    this.schedule((dt)=>{
    
      // 每0.1s执行一次,重复一次(总共执行两次)
      cc.log("回调2", dt);
    }, 0.1, 1);

    this.schedule((dt)=>{
    
      // 每0.1s执行一次,不重复,延迟0.2秒后触发
      cc.log("回调3", dt);
      cc.log("测试this", this.testInfo);
    }, 0.1, 0, 0.2);
		
    this.schedule((dt)=>{
    
      // 0.5s后触发,测试active对计时器的影响
      cc.log("回调4", dt);
      this.node.active = false;
    }, 0.5);
}

// 输出1
// 回调1 0.10002300000000003
// 回调2 0.10002300000000003
// 回调1 0.10004099999999994
// 回调2 0.10004099999999994
// 回调3 0.200064
// 测试this 222
// 回调1 0.11671000000000005
// 回调1 0.10003199999999993
// 回调4 0.5000120000000001

// 输出2
// 回调1 0.10002899999999999
// 回调2 0.10002899999999999
// 回调3 0.20002500000000004
// 测试this 222
// 回调1 0.11670800000000003
// 回调2 0.11670800000000003
// 回调1 0.116745
// 回调1 0.11657899999999996
// 回调4 0.5001940000000001
一些结论
  • 相对于js自带的定时任务,schedule实现了重复固定次数、延迟执行等功能,也解决了this指向的问题。
  • 因为引擎底层做了参数兼容性处理,所以可以传入任意长度的参数。如当只传入一个参数的时候,定时器会被一直触发。
  • 定时器执行结果不一定是一致的,在多次测试中,出现了上面两种输出结果,获得的dt也可能是不一致的,何况update计时器会优先于自定义计时器触发,所以cocos creator提供的计时器也不是准确的。
  • 节点的active会影响计时器的执行(这不是显然的吗…),即源码中的_paused属性。
参考链接
  1. MDN Window.setTimeout Window.setInterval
  2. MDN 实际延时比设定值更久的原因:最小延迟时间
  3. Cocos Creator Api文档 Component.schedule
一些后话

秋招挺凄惨的,想去的都挂了。主要原因还是自己以前对基础知识不够重视,这个结局算是还债吧。
因为一些原因,开始复习的也比较晚。复(yu)习的时候就想顺便写点东西记录一下。
文章是真的难写啊淦(x 很怕写错。
应该是第一次相对深入的读cocos creator的源码,还挺有意思的。但是毕竟还是小菜鸡,有纰漏还望大家多多包涵2333。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_47879201/article/details/111876970

智能推荐

el-input 键盘按下弹起事件无效 加.native_el-input上使用键盘事件.native被废弃了-程序员宅基地

文章浏览阅读1.1k次。<el-input size="small" @keyup.enter.native="handleInputConfirm"></el-input>_el-input上使用键盘事件.native被废弃了

sql语句_"7.sql 语句\"show database like “%name’\",能显示出 数据库。"-程序员宅基地

文章浏览阅读94次。Structured Query Language:结构化查询语言数据库的注释:单行注释:-- 注释内容 #注释内容(mysql特有)多行注释:/* 注释内容 */1、DDL(Data Definition Language)数据定义语言数据库,表,列等。关键字:create, drop,alter 等create database-- 查询所有数据库show database;-- 查询某个数据库的创建语句show create database databa_"7.sql 语句\"show database like “%name’\",能显示出 数据库。"

Integer和Int_integer -1-程序员宅基地

文章浏览阅读166次。参考博客1参考博客21、Integer和int的区别Integer是int的包装类,int是八大基本数据类型之一Integer是类,默认值为null,int默认值为0Integer 表示的是对象,用一个引用指向这个对象,而int是基本数据类型,直接存储数值2 、Integer的自动拆箱和装箱自动拆箱和装箱是jdk1.5之后的功能装箱就是由基本数据类型封装成类的过程,拆箱反之。装箱:正常创建类的对象是new一个出来,但是Integer类可以直接Integer a=11;通过反编_integer -1

input上传图片并且实现预览_input上传图片并预览-程序员宅基地

文章浏览阅读6.4k次,点赞5次,收藏20次。文章目录前言一、确定思路二、书写代码1.HTML部分2.CSS部分3.JS部分(重点)3.1.点击选择图片按钮,调用input文件框事件的的代码3.2.转换格式3.3.发送图片给后端前言在网站中,上传图片这个功能,这个还是挺常见的。比如说:在填写信息中,上传头像中。提示:以下是本篇文章正文内容,下面案例可供参考一、确定思路整个功能,大致可以分为三个步骤。点击选择图片的按钮,从本地选择一张图片。(选图片)选择好之后,会进行预览。就好比自己微信要换头像,选择好之后,都会有预览的步骤。(预_input上传图片并预览

成都信息工程大学计算机考研调剂,网络空间安全学院2020年硕士研究生招生复试调剂公告...-程序员宅基地

文章浏览阅读530次。网络空间安全学院2020年硕士研究生招生复试调剂公告一、学院简况网络空间安全学院前身为2004年成立的网络工程系,2008年更名为网络工程学院,2013年更名为信息安全工程学院,2017年改为现名。学院建有新一代密码技术与系统安全四川省重点实验室(筹)、网络空间安全四川省高校重点实验室、成都市信息安全保密重点实验室和成都商用密码技术研究院;设有信息安全、信息对抗技术、网络工程、物联网工程四个本科专..._成都有计算机专业调剂的研究生

Ubuntu14.04安装MySQL可视化管理工具MySQL-Workbench_ubantu mysql 可视化-程序员宅基地

文章浏览阅读470次。1、建议先更新列表sudo apt-get update2、安装MySQL-Workbenchsudo apt-get install mysql-workbench中间提示“您希望继续执行吗”,输入y,按回车继续3、安装成功,输入以下代码查看,可以看到已安装的相关服务及版本。dpkg -l|grep mysql-workbench5、在搜索栏找到已安装的mysql-wor..._ubantu mysql 可视化

随便推点

7-3 计算分段函数[1] (10 分)-程序员宅基地

文章浏览阅读375次。本题目要求计算下列分段函数f(x)的值:输入格式:输入在一行中给出实数x。输出格式:在一行中按“f(x) = result”的格式输出,其中x与result都保留一位小数。输入样例1:10输出样例1:f(10.0) = 0.1输入样例2:0输出样例2:f(0.0) = 0.0代码:import java.util.Scanner;public class Main { public static void main(S._7-3 计算分段函数[1]

python爬虫——lxml的使用_importrequestsfromlxmlimportetree headers={"user-a-程序员宅基地

文章浏览阅读928次,点赞3次,收藏7次。为了更好的学习scrapy库,我决定先其前驱内容lxml库此次我们爬取豆瓣电影Top250代码如下:import requests from lxml import etreeimport timeimport csvheaders={ 'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/53..._importrequestsfromlxmlimportetree headers={"user-agent":"mozilla/5.0 (wi

nodejs中的promise基本操作_nodejs new promise-程序员宅基地

文章浏览阅读1.6k次。1.为什么要使用promise/*需求:异步的按顺序去读取1.txt、2.txt、3.txt文件的内容假设 1.txt内容为111 、2.txt内容为222、3.txt内容为333*/var fs = require('fs');、 fs.readFile('./files/1.txt','utf8',function(err,data){ if(err){ ..._nodejs new promise

基于SSM框架的水果商城设计与实现_基于ssm框架的妙橙水果屋的设计与实现-程序员宅基地

文章浏览阅读740次。开发工具(eclipse/idea/vscode等):数据库(sqlite/mysql/sqlserver等):功能模块(请用文字描述,至少200字):_基于ssm框架的妙橙水果屋的设计与实现

boost::function的用法(一)_boost::function0 function1-程序员宅基地

文章浏览阅读1.5k次,点赞2次,收藏2次。boost::function的用法本片文章主要介绍boost::function的用法。 boost::function 就是一个函数的包装器(function wrapper),用来定义函数对象。1. 介绍 Boost.Function 库包含了一个类族的函数对象的包装。它的概念很像广义上的回调函数。其有着和函数指针相同的特性但是又包含了一个调用的接口。一个_boost::function0 function1

K8S:使用Filebeat收集K8S内Pod应用日志_filebeat收集pod日志-程序员宅基地

文章浏览阅读7k次。K8S:使用Filebeat收集K8S内Pod应用日志一、环境描述没有K8S集群时应用日志通过filebeat–>redis–>logstash–>es–>kibana进行收集展示,上K8S集群后也需要考虑收集日志的问题,此处仅考虑 pod 中java应用生产的 info 和error文本日志。Kubernetes集群中的日志收集解决方案我们采用sidecar方案,将filebeat与应用部署到同一个pod,将应用日志挂载到主机目录,filebeat将日志挂载到其容器内。_filebeat收集pod日志

推荐文章

热门文章

相关标签