egret 拖尾的实现 MotionStreak

news2025/2/24 16:13:27

背景: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;
      }
    }
  }
}

测试

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

请添加图片描述

对调一下纹理:

请添加图片描述

完结。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2051010.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

VS2019开发跨平台(Linux)程序时,怎么配置第三方库的路径

一、问题描述&#xff1a; 使用跨平台编译时&#xff0c;VS2019总是提示链接openssl库有问题&#xff1b; 二、错误时的配置&#xff1a; 1、前提 openssl在Linux系统默认下是1.0.0版本&#xff0c;而自己准备好的是1.1.1版本&#xff0c;并且路径完全不在一个地方&#xf…

【Linux-进程】系统初识:冯诺依曼体系结构

系列文章&#xff1a;《Linux入门》 目录 冯诺依曼体系结构 1&#xff09;硬件上 &#x1f337;1.什么是冯诺依曼体系结构&#xff1f; &#x1f337;2.冯诺依曼结构的五个主要组成部分 1.运算器 2.控制器 3.存储器 4.输入输出 设备 ⁉️3.为什么还需要内存呢&#xf…

c++数据结构算法复习基础-- 4 -- 线性表-单向循环链表-常用操作接口-复杂度分析

1、单向循环链表一 1&#xff09;特点 每一个节点除了数据域&#xff0c;还有一个next指针域指向下一个节点(存储了下一个节点的地址) 末尾节点的指针域指向了头节点 析构函数思路图 2&#xff09;代码实现 //定义结点 //单向循环链表 class CircleLink { public://构造函数…

使用python基于fastapi发布接口(一)

FastAPI官网地址 FastAPI基于Python 3.6+和Starlette框架,天生就带着高性能和异步的基因。 FastAPI的文档生成功能简直是开发者的福音! 你不再需要手动编写API文档,FastAPI能自动帮你搞定。 FastAPI还超级灵活,支持各种数据库和认证方式,无论是SQLite、PostgreSQL还是M…

【xilinx】TPM可信平台模块与 Zynq UltraScale+ PS SPI 接口

本博客&#xff08;Venu Inaganti&#xff09;介绍了可信平台模块 (TPM) 与 Zynq UltraScale PS SPI 控制器的连接。 目前唯一具有 TPM 的评估板是 KR260/KV260 SOM&#xff0c;因此为了帮助正在试验 Zynq UltraScale 设备的用户&#xff0c;本文介绍了如何通过 PMOD 连接器与…

【MongoDB】Java连接MongoDB

连接URI 连接 URI提供驱动程序用于连接到 MongoDB 部署的指令集。该指令集指示驱动程序应如何连接到 MongoDB&#xff0c;以及在连接时应如何运行。下图解释了示例连接 URI 的各个部分&#xff1a; 连接的URI 主要分为 以下四个部分 第一部分 连接协议 示例中使用的 连接到具有…

计算机视觉中的上采样与下采样:深入浅出实例代码解析

文章目录 一、引言二、下采样&#xff08;Downsampling&#xff09;三、上采样&#xff08;Upsampling&#xff09;1. 最近邻插值2.双线性插值3.转置卷积&#xff08;Deconvolution&#xff09;4.代码部分 四、总结 在计算机视觉领域&#xff0c;尤其是在深度学习和卷积神经网络…

宝塔面板部署webman项目+nginx反向代理

新建站点 新建一个站点&#xff0c;php版本选择纯净态即可&#xff0c;反正都是用不上的&#xff0c;域名填写你申请得到的域名 拉取代码 新建一个目录&#xff0c;然后将代码部署到本地 启动项目 推荐使用宝塔面板的进程守护管理器启动项目&#xff0c;其实就是用superviso…

ATT格式与Intel格式x86汇编指令的区别

AT&T公司 这个公司的创始人就是发明电话的贝尔&#xff0c;而Unix和C语言都是出自贝尔实验室的产物。 Intel公司 世界上第一片CPU是1971年发明的&#xff0c;型号是Intel生产的4004微处理器。 两种格式的区别 AT&T格式Intel格式目的操作数d、源操作数s op s, d 注…

vue2中使用i18n配置elementUi切换语言

1、下载插件 npm i vue-i18n8.22.2 2、新建文件夹i18n 3、编写index.js文件 import Vue from "vue"; import VueI18n from "vue-i18n"; import locale from element-ui/lib/locale; // 引入 elementui 的多语言 import enLocale from element-ui/lib/l…

【MySQL】C/C++连接MySQL客户端,MySQL函数接口认知,图形化界面进行连接

【MySQL】C/C引入MySQL客户端 安装mysqlclient库mysql接口介绍初始化mysql_init链接数据库mysql_real_connect下发mysql命令mysql_query获取出错信息mysql_error获取执行结果mysql_store_result获取结果行数mysql_num_rows获取结果列数mysql_num_fields判断结果列数mysql_field…

域自适应,你适应了嘛?

“最难的深度学习是谁&#xff1f;” “嗯&#xff0c;是迁徙学习吧&#xff1f;” “要分情况&#xff0c;不过&#xff0c;应该是迁徙学习吧&#xff1f; ” “不是迁徙学习嘛&#xff1f;” 目录 域自适应是啥&#xff1f; 域自适应的方法&#xff1f; 基于差异的方法…

Kafka系列之:Kafka Connect深入探讨 - 错误处理和死信队列

Kafka系列之&#xff1a;Kafka Connect深入探讨 - 错误处理和死信队列 一、快速失败二、YOLO&#xff1a;默默忽略坏消息三、如果一条消息掉在树林里&#xff0c;会发出声音吗&#xff1f;四、将消息路由到死信队列五、记录消息失败原因&#xff1a;消息头六、记录消息失败原因…

k8s Pod生命周期详解

文章目录 一、创建Pod二、启动Pod三、销毁Pod 共分为三步&#xff1a;创建Pod、启动Pod、销毁Pod 一、创建Pod K8S创建Pod的过程 二、启动Pod 1、kubelet调用容器运行时创建Pause容器&#xff0c;准备一个容器环境 2、创建初始化容器init container。如果有多个&#xff0c;…

打印网页使内容包含有效网络连接Print webpage with workable hyperlinks

小虎想打印网页&#xff0c;并且将里面有链接的文字带文字一起打印保存。 解决方法 利用谷歌浏览器的打印功能即可&#xff1a; Use print options in chrome.

构建一个Markdown编辑器:Fyne综合案例

在本文中&#xff0c;我们将通过一个完整的案例来介绍如何使用Go语言的Fyne库来构建一个简单的Markdown编辑器。Fyne是一个易于使用的库&#xff0c;它允许开发者使用Go语言来创建跨平台的GUI应用程序。 1. 项目结构 首先&#xff0c;我们需要创建一个Go项目&#xff0c;并引…

基于vllm部署大模型

VLLM&#xff08;非常大的语言模型&#xff09;在中文中通常指的是经过大量文本数据训练的神经网络模型&#xff0c;能够理解和生成类似人类语言的文本。这类模型是许多先进AI系统的核心&#xff0c;比如ChatGPT、GPT-4等。 VLLM 可以执行各种任务&#xff0c;如自然语言处理、…

【实用工具】使用Chrome插件搭建第二大脑!SuperMemory大语言模型登场,开源、免费、保存你需要的所有网站!——含入门安装教程

文章目录 项目简介项目搭建主要功能How do I use this?本地部署 项目简介 最近&#xff0c;有一款Github项目十分火爆&#xff0c;它专注于用超级内存打造自己的第二大脑。它是书签的 ChatGPT&#xff0c;基于Chrome 浏览器扩展导入推文或保存网站和内容&#xff0c;你可以访…

RTD2739 8K60Hz/4K 240Hz

RTD2739支持4K240Hz/80K60Hz。 RTD2739 supports input format up to 3840 x 2160 240Hz RTD2739 supports 3 ports of Ultra-High Speed Receiver can support DisplayPort1.4 In DisplayPort mode, four link layer speed HBR3 (8.1GHz), HBR2 (5.4GHz), HBR(2.7GHz), R…

理解Linux中的作业控制:详解fg、bg和jobs命令

理解Linux中的作业控制&#xff1a;详解fg、bg和jobs命令 文章目录 理解Linux中的作业控制&#xff1a;详解fg、bg和jobs命令1. 前言2. jobs 命令3. bg 命令4. fg 命令5. 简要总结6. 一图概览 1. 前言 ​ Linux系统中&#xff0c;作业控制是一个非常重要的概念&#xff0c;尤其…