Pixi.js中线段需要使用 PIXI.Graphics 进行绘制。而由于某些因素,Pixi.js在使用Graphics绘制线段时并未对线段添加hitArea,因此即使启用该Graphics对象interactive属性并添加事件监听器也无法起作用。
如果想为线段添加事件则需要为该线段所属Graphics对象添加hitArea,而一些比较简单的使用场景下只需要使用一个PIXI.Rectangle将整个线段覆盖即可,但对于一些比较精细的事件监听则需要使用线段所属PIXI.Graphics.geometry 中的points构建一个polygon,而后hitArea指向该多边形。
PIXI.Graphics.geometry是Graphics进行图形绘制时存储的几何数据模型,Grapphics在渲染时通过该模型进行点计算和渲染。而渲染计算后的图形顶点集则存储在geometry.points中(geometry.points只有在经过renderer渲染后才会存在)。
我们的目标是为线段添加交互能力。理所应当的,线段所属的Graphics仅用于该线段的绘制,而不包含其他图形绘制。这样Graphics.geometry中就只包含了绘制该线段所需要的几何模型,而渲染出来的geometry.points中也只包含了线段顶点。
我们需要知道的是,在计算机图形渲染中,由于效率等原因,几乎所有的图形都是又由一个个小三角形构成,无论是圆、直线还是方形。有了这个基础我们就可以理解为什么geometry.points中的点并不是沿着线段边沿顶点围绕一周分布,而是从起始点开始左右交替直到线段终点。我们可以简单写一个辅助线验证一下:
可以看到points列表中点分布是线段左右依次分布,而我们需要的实际上是:
因此我们只需要写一个转换points列表的函数即可,函数也很简单:
function lineToPolygon(points) { //the number of points const numPoints = points.length / 2; //the last points' array index const pointsLastIndex = points.length-1; //how many pairs in the geometry const pair = numPoints/2; //result const output = new Array(points.length); for (let x = 0; x < pair; x++) { const i = x*4; //fulfill start side's points output[x*2] = points[i]; output[x*2+1] = points[i+1]; //fulfill opposite side's points output[pointsLastIndex-x*2-1]=points[i+2]; output[pointsLastIndex-x*2]=points[i+3]; } //close path output.push(output[0], output[1]); //gen new polygon return new PIXI.Polygon(output); }
而使用也很简单,在进行线段绘制的时候调用Graphics的render并传入Stage的renderer以立即渲染获得渲染后的points列表,然后将该函数创建的polygon设置为graphics的hitArea即可:
graphics.current.render(r as PIXI.Renderer); graphics.current.interactive = true; const shape = lineToPolygon(hitAreaWith,graphics.current.geometry.points); graphics.current.hitArea = shape;
此处在我的应用场景下有一个优化点,我的线段使用GreenSock库制作了一些动画效果,如果在每个Graphics每次图形绘制中都单独调用render会有一定性能亏损,而我并不需要在执行动画的过程中对该线段进行交互,因此我设置了一个isAnimating状态,标志是否Graphics处于动画执行状态。只有isAnimating变更为false时,才执行hitArea填充(代码使用React):
useEffect(()=>{ if(!isAnimating){ graphics.current.render(r as PIXI.Renderer); graphics.current.interactive = true; const shape = lineToPolygon(hitAreaWith,graphics.current.geometry.points); graphics.current.hitArea = shape; } else{ graphics.current.interactive=false; } },[isAnimating]);
这样就可以减少计算和设置hitArea带来的性能损耗。
v1.0 wep 2022/01/12 文章基本内容