背景:egret项目中需要用到拖尾效果,引擎原生没有提供,参考cocos2dx 的 MotionStreak实现拖尾效果。
原理
拖尾的原理很简单,定时记录节点的位置,根据运行的轨迹和指定的拖尾宽度生成拖尾网格,然后将纹理绘制在拖尾网格上。
1.记录运行轨迹。若记拖尾的长度为4,则只保留4个路径点,即下次记录的新点E,并且需要删除点A.
2.拖尾宽度就按照轨迹点线性减小,注意每个路径点的拖尾宽度不是固定不变的,当A点被删除时 B点就成为了最尾端,此时B点的拖尾宽度就为0。
3.计算网格点,由于每个路径点对应拖尾宽度是不断变化的,所以每次绘制时都要重新计算网格点。我们可以根据相邻路径点来计算网格点相对于路径的坐标偏移系数,这个值可以保存下来,因为对于确定的路径点,系数是不会变的。
4.将拖尾纹理按照节点数划分,绘制到网格上即可。
代码
在BitMap类上做扩展,使用 MeshNode作为$renderNode。
namespace egret {
class StreakData { //移动路径数据
x:number; //global x
y:number; //global y
xCoefficient:number = 0; //与位置、拖尾宽度结合计算顶点x坐标
yCoefficient:number = 0; //与位置、拖尾宽度结合计算顶点y坐标
public constructor(_x:number,_y:number) {
this.x = _x;
this.y = _y;
}
}
export class MotionStreak extends Bitmap {
private stroke:number; //拖尾的宽度
private tail:number; //拖尾的节点数
private streakDatas: StreakData[]; //拖尾的数据
private uvs: number[];
private indices: number[];
/**
* @param value egret Texture
* @param stroke 拖尾的宽度 单位:像素 默认值:10
* @param tail 拖尾的节点数 默认值:11
*/
public constructor(value: Texture, stroke:number = 10, tail: number = 11) {
super(value);
this.stroke = stroke;
this.tail = tail;
this.$renderNode = new sys.MeshNode();
}
protected createNativeDisplayObject(): void {
this.$nativeDisplayObject = new egret_native.NativeDisplayObject(egret_native.NativeObjectType.SPRITE);
}
//没有必要
$hitTest(stageX:number, stageY:number):DisplayObject {
return null;
}
$updateRenderNode(): void {
let image = this.$bitmapData;
if (!image) {
return;
}
let node = <sys.MeshNode>this.$renderNode;
node.smoothing = this.$smoothing;
node.image = image;
node.imageWidth = this.$sourceWidth;
node.imageHeight = this.$sourceHeight;
let destW: number = !isNaN(this.$explicitBitmapWidth) ? this.$explicitBitmapWidth : this.$textureWidth;
let destH: number = !isNaN(this.$explicitBitmapHeight) ? this.$explicitBitmapHeight : this.$textureHeight;
let tsX: number = destW / this.$textureWidth;
let tsY: number = destH / this.$textureHeight;
let bitmapWidth: number = this.$bitmapWidth;
let bitmapHeight: number = this.$bitmapHeight;
node.drawMesh(
this.$bitmapX, this.$bitmapY,
bitmapWidth, bitmapHeight,
this.$offsetX * tsX, this.$offsetY * tsY,
tsX * bitmapWidth, tsY * bitmapHeight
);
}
$onAddToStage(stage: Stage, nestLevel: number): void {
super.$onAddToStage(stage, nestLevel);
this.addEventListener(Event.ENTER_FRAME, this.parseData, this);
this.streakDatas = [];
this.uvs = [];
this.indices = [];
//需要渲染 this.tail-1个四边形
let deltaU = 1/(this.tail-1);
let gPos = this.localToGlobal(this.x, this.y);
for (let i = 0, datas = this.streakDatas = [];; i++) {
datas[i] = new StreakData(gPos.x,gPos.y);
//纹理uv
this.uvs.push(i * deltaU);
this.uvs.push(0);
this.uvs.push(i * deltaU);
this.uvs.push(1);
if (i == this.tail-1)
break;
/**顶点索引方式
* 0 2 4 ...
* | | |
* >>> xy 历史路径点 >>> ...
* | | |
* 1 3 5 ...
*/
let ii = i*6, iv = i*2;
this.indices[ii++] = iv + 0;
this.indices[ii++] = iv + 1;
this.indices[ii++] = iv + 2;
this.indices[ii++] = iv + 2;
this.indices[ii++] = iv + 1;
this.indices[ii++] = iv + 3;
}
}
$onRemoveFromStage(): void {
super.$onRemoveFromStage();
this.removeEventListener(Event.ENTER_FRAME,this.parseData, this);
}
private parseData(): void {
let first = this.streakDatas.shift();
this.streakDatas.push(first);
//记录 当前位置
let cur = this.streakDatas[this.tail-1];
let gPos = this.localToGlobal(0, 0);
cur.x = gPos.x;
cur.y = gPos.y
let last = this.streakDatas[this.tail-2];
let dX = cur.x - last.x;
let dY = cur.y - last.y;
let dis = Math.sqrt(dX*dX+dY*dY);
if (dis > 0) {
cur.xCoefficient = last.xCoefficient = dY / dis;
cur.yCoefficient = last.yCoefficient = dX / dis;
} else {
cur.xCoefficient = 0;
cur.yCoefficient = 0;
}
let node: egret.sys.MeshNode = <egret.sys.MeshNode>this.$renderNode;
node.uvs = this.uvs;
node.indices = this.indices;
node.vertices.length = this.uvs.length;
let widthStride = this.stroke/2/(this.tail-1);
let tempPoint = new egret.Point();
for (let i = 0, datas = this.streakDatas; i < this.tail; i++) {
let data = datas[i];
let iv = i * 4;
this.globalToLocal(data.x + widthStride * i * data.xCoefficient,data.y - widthStride * i * data.yCoefficient, tempPoint)
node.vertices[iv] = tempPoint.x;
node.vertices[iv+1] = tempPoint.y;
this.globalToLocal(data.x - widthStride * i * data.xCoefficient,data.y + widthStride * i * data.yCoefficient, tempPoint)
node.vertices[iv+2] = tempPoint.x;
node.vertices[iv+3] = tempPoint.y;
}
}
}
}
测试
测试的纹理:
效果如下:
对调一下纹理:
完结。