PIXI.JS源码解析:Ticker.js

2020/12/24 5:38:22

本文主要是介绍PIXI.JS源码解析:Ticker.js,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

  • 一个渲染引擎如何实现loop方法?
  • 一个监听回调队列如何实现添加和删除?
  • 监听回调队列先后关系如何实现?
  • 如果在监听回调函数中更新队列,本轮回调如何正确地执行?

今天我们就借用PIXI.JS的源码来说说以上功能的实现(本文不打算讲解TS,为了方(tou)便(lan)就是用v4版本的代码)

本文要讲解的是core/ticker目录中的Ticker和TickerListener他们用来处理画面的动态更新,以及执行每次更新时的回调。

从Application进入Ticker

在阅读PIXI.JS的上手文档时,你第一个接触的就是一下这行代码:

var app = new PIXI.Application();

这里我们实例化的app,后续会用来添加精灵图,制作动画。

我们都知道,依赖canvas的动画是会不断循环执行loop函数,把图像绘制在画布上,以此实现动画。那么Application是如何实现的?

在构造函数中,我们可以看到一个内部变量this._ticker变量,这个变量就是实现loop的核心对象,它通过this.ticker对外暴露。

通过set方法,我们知道,对ticker赋值其实是更新了this._ticker(用新值覆盖了前一次的this._ticker)。而start方法也是执行了this._ticker.start。

而通过ticker.add方法,application将render函数添加到了渲染队列中,此后每次执行loop是都会促发一个render函数的执行:

那么这里的add,start,remove三个方法都干了什么?这个UPDATE_PRIORITY.LOW又是什么?

记住这几个问题本篇我们会一一解答。

那我们就看看Ticker.js都实现了什么逻辑。

ps:这里的shared其实也是Ticker的实例:

Ticker.js

Ticker.js在core/ticker文件夹下:

core文件夹是用来存放核心代码的,比如diaplay中存放处理显示盒的代码,sprites中是处理精灵图的代码,renderer中存放处理渲染的代码。这些我们日后都会说到。上一节我们说的new Application()的逻辑代码就来源于core根目录的Application.js。

core/ticker文件夹下除了Ticker外,还有TickerListener和index。上一节说的shared就是在index中定义的:

而TickerListener是专门用来处理Ticker中的监听回调队列的(后面会讲到)

话不多说让我们看看Ticker(这里我删除一些代码,只留下核型逻辑):

这里最重要的就是内部变量this._head和this._tick;

  • this._head

我们之前说过Ticker内部实现了一个渲染回调队列,在每次loop是都需要执行一遍,而这个this._head就是这个这个队列的源头,至于为什么需要单独制作一个源头,等我们说完TickerListener你就能理解其中精妙了。而我们在appliction中传进来的render函数,也会作为TickerListener插入到这个队列之中:

而这个显然目前这个队列中除了head外只有一个Listener。这些回调会在每次loop后执行一边以更新画布上的图像。而这个loop就由this._tick开启。

this._tick会不断被requestAnimationFrame调用执行,以实现loop的逻辑。但是循环开启有三个条件:this.started为真,this._requestId = null以及this._head.next存在,这三个条件是否都成立?

  • this.started为真

还记得我们在Appliction.js中有调用this.start方法?

Application.js

this.start其实是对_ticker.start的封装。

这里的start是一个单例,未开始时会执行一次循环,并把this.started设为真,这样即便重复执行start方法也不会在促发更新。这样我们解答了上文的第一个问题,start函数的逻辑(还剩下addremove逻辑,和UPDATE_PRIORITY.LOW的意义)

  • this._requestId = null;

每次this._tick 执行时都会把他设为null,所以loop的第二个条件也满足,

  • this._head.next

这里的next也是一个TickerListener,我们前面说过TickerListener是一个回调函数链,不断通过next找到下一个回调知道执行完回调函数链。这个我们在说到TickerListener会详细说明。当我们在Application.js中调用add方法时,我们就已经往插入了TickerListener,所以this._head.next也存在。那么这个loop就会一直循环下去,直到有逻辑触发关闭。

那么this.update(time);处理了什么逻辑?

这里通过next遍历回调函数链来执行emit方法。这些逻辑都被写在TickerListener中,不过在进入TickerListener之前我们还需要弄懂add和remove函数逻辑以及UPDATE_PRIORITY.LOW的意义。

add函数

add函数和它的姐妹函数addOnce底层都调用了内部方法this._addListener。

这里TickerListener可以接受4个参数,第四个参数用于控制回调是执行一次还是能够反复执行。

而此前传入UPDATE_PRIORITY.LOW其实是可以控制回调函数权重的:

一共有5档,默认情况下回调是按顺序添加,先添加的回调先执行,但也可以通过控制传入的权重大小影响回调函数插入的位置(下面会解释),但如果PRIORITY一致时,就是先添加的先执行。

而TickerListener具体逻辑在TickerListener源码解析一篇中会详细解释。那么我们就来看看内部方法_addListener:

在前面分析update逻辑时,我们可以看到TickerListener回调队列是不断通过找寻下一个next来完成链式执行。而具体TickerListener的位置由previous和next决定,除了_head外TickerListener可以没有next却不能没有previous。(甚至两个TickerListener的previous有可能是同一个)。

而执行完插入逻辑后,会执行this._startIfPossible();

这保证了,如果autoStart为真,当我们往_header后添加了TickerListener后,但即便我们没有执行start函数(this.started为false),逻辑上也会开启loop。

但如果我们已经执行完start后(this.started为true),此时再执行_requestIfNeeded;

因为每次执行完_tick后this._requestId = requestAnimationFrame(this._tick);所以_requestIfNeeded的条件语句不会执行。

remove

最后让我们来看看remove函数都干了什么:

简而言之就是删除fn,而这些逻辑都是由TickerListener完成的。那么下一节我们就来看看TickerListener是如何工作的。



这篇关于PIXI.JS源码解析:Ticker.js的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程