egret 拖尾的实现 MotionStreak

背景:egret项目中需要用到拖尾效果,引擎原生没有提供,参考cocos2dx 的 MotionStreak实现拖尾效果。

原理

拖尾的原理很简单,定时记录节点的位置,根据运行的轨迹和指定的拖尾宽度生成拖尾网格,然后将纹理绘制在拖尾网格上。

1.记录运行轨迹。若记拖尾的长度为4,则只保留4个路径点,即下次记录的新点E,并且需要删除点A.
在这里插入图片描述

2.拖尾宽度就按照轨迹点线性减小,注意每个路径点的拖尾宽度不是固定不变的,当A点被删除时 B点就成为了最尾端,此时B点的拖尾宽度就为0。
在这里插入图片描述
3.计算网格点,由于每个路径点对应拖尾宽度是不断变化的,所以每次绘制时都要重新计算网格点。我们可以根据相邻路径点来计算网格点相对于路径的坐标偏移系数,这个值可以保存下来,因为对于确定的路径点,系数是不会变的。
在这里插入图片描述
4.将拖尾纹理按照节点数划分,绘制到网格上即可。
在这里插入图片描述

代码

在BitMap类上做扩展,使用 MeshNode作为$renderNode。
修改引擎源码后,进入到源码根目录执行./tools/bin/egret make 即可。

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

测试

测试的纹理:
在这里插入图片描述
效果如下:

请添加图片描述

对调一下纹理:

请添加图片描述

完结。

在 Egret 中,可以使用 RenderTexture 和 BlendMode 来实现渲染批次。 渲染批次是指将多个绘制操作合并为一个批次,从而减少绘制次数,提高游戏性能。 具体实现步骤如下: 1. 创建一个 RenderTexture 对象,将要渲染的显示对象添加到 RenderTexture 中。 2. 使用 BlendMode 设置渲染模式,将多个 RenderTexture 合并为一个批次。 示例代码如下: ``` // 创建一个 RenderTexture 对象 var renderTexture: egret.RenderTexture = new egret.RenderTexture(); renderTexture.drawToTexture(displayObject); // 设置 BlendMode renderTexture.blendMode = egret.BlendMode.ADD; ``` 在使用 RenderTexture 进行渲染时,可以将多个 RenderTexture 合并为一个批次,从而减少绘制次数,提高游戏性能。例如,可以将多个 RenderTexture 合并到一个 Bitmap 中进行渲染,代码如下: ``` // 创建一个 Bitmap 对象 var bitmap: egret.Bitmap = new egret.Bitmap(); bitmap.width = 800; bitmap.height = 600; // 创建多个 RenderTexture 对象 var renderTexture1: egret.RenderTexture = new egret.RenderTexture(); var renderTexture2: egret.RenderTexture = new egret.RenderTexture(); renderTexture1.drawToTexture(displayObject1); renderTexture2.drawToTexture(displayObject2); // 合并 RenderTexture bitmap.texture = new egret.RenderTexture(); bitmap.texture.drawToTexture(renderTexture1); bitmap.texture.drawToTexture(renderTexture2); // 设置 BlendMode bitmap.blendMode = egret.BlendMode.ADD; ``` 注意,使用渲染批次时需要注意渲染顺序和深度问题,以避免出现渲染错误的情况。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值