cocos creator 3.x 手搓背包拖拽装备

 项目背景:

游戏背包 需要手动 拖拽游戏装备到 装备卡槽中,看了下网上资料很少。手搓了一个下午搞定,现在来记录下实现步骤;

功能拆分:

一个完整需求,我们一般会把它拆分成 几个小步骤分别造零件。等都造好了我们就能 装配到一起 形成一个完成功能。以下是对上面功能的 步骤拆分:

  1. 背包详情的展示,点击背包中的物品 展示一个详情页面(因为 是在 ScrollView 中拖拽 ScrollView 的元素 会造成 ScrollView 滚动)所有我们直接拖拽 详情页面中的元素就能 避免对 ScrollView 的影响。(如果没有详情可以 先生成一个一样的元素放置的 ScrollView 外层)
  2. 实现 拖拽元素,本身是对 拖拽事件的监听 来完成对元素位置的设置。这里有个知识点 触点坐标 到 世界坐标的转换
  3. 能拖拽元素了 那么我们就需要把 元素放在对应的位置, 实际上就是在拖拽的时候 判断两个元素的位置,根据需求我们这里是直接判断的 元素的 x坐标;来判断拖拽元素 是否到了 对应的卡槽;
  4. 既然能判断到那个具体卡槽了,我们停止拖拽,那就装备到对应的卡槽,要是没有到卡槽位置,我们隐藏拖拽元素 返回 (假装什么也没发生)

1.背包装备详情

点击装备展示,对应详情信息,应为装备位置不固定,

  1. 展示的时候就需要 通过 背包元素的 位置 来计算详情页面展示的位置
  2. 并且如果是 在边缘位置还要修改要展示的位置,然后修正位置信息后 在做展示

这里有一个技术点就是 坐标位置的转换

想要对比两个节点元素 或者 参照当前节点 设置另一个节点位置信息(不在同一个父节点的时候) 需要把他们转换在 同一个坐标系及(参照同一个父节点)

 

问题: 由于图中的 粉色ScrollView item  和 详情不是在用一个坐标参照中, 如果我们直接获取 item 位置 赋值给 详情元素 那肯定无法让他们两位置一直;

 

解决问题思路:

  先计 算出 item 世界坐标,然后再把这个坐标转化到 详情页面的局部坐标他们世界坐标一直,局部坐标不一致

同为 cocos creator 坐标转换需要使用 使用节点父元素计算:

https://docs.cocos.com/creator/3.8/api/zh/class/UITransform?id=convertToWorldSpaceARicon-default.png?t=N7T8http://convertToWorldSpaceAR

ItemworldPositon = item.parent.getComponent(UITransform).convertToWorldSpaceAR(item.getPosition());

详情面板位置 =  详情面板.parent.getComponent(UITransform).convertToNodeSpaceAR(                  ItemworldPositon)
  • convertToNodeSpaceAR
    /**
     * @en
     * Converts a Point to node (local) space coordinates.
     *
     * @zh
     * 将一个 UI 节点世界坐标系下点转换到另一个 UI 节点 (局部) 空间坐标系,这个坐标系以锚点为原点。
     * 非 UI 节点转换到 UI 节点(局部) 空间坐标系,请走 Camera 的 `convertToUINode`。
     *
     * @param worldPoint @en Point in world space.
     *                   @zh 世界坐标点。
     * @param out @en Point in local space.
     *            @zh 转换后坐标。
     * @returns @en Return the relative position to the target node.
     *          @zh 返回与目标节点的相对位置。
     * @example
     * ```ts
     * const newVec3 = uiTransform.convertToNodeSpaceAR(cc.v3(100, 100, 0));
     * ```
     */
    convertToNodeSpaceAR(worldPoint: math.Vec3, out?: math.Vec3): math.Vec3;
  • convertToWorldSpaceAR
    /**
     * @en
     * Converts a Point in node coordinates to world space coordinates.
     *
     * @zh
     * 将距当前节点坐标系下的一个点转换到世界坐标系。
     *
     * @param nodePoint @en Point in local space.
     *                  @zh 节点坐标。
     * @param out @en Point in world space.
     *            @zh 转换后坐标。
     * @returns @en Returns the coordinates in the UI world coordinate system.
     *          @zh 返回 UI 世界坐标系。
     * @example
     * ```ts
     * const newVec3 = uiTransform.convertToWorldSpaceAR(3(100, 100, 0));
     * ```
     */
    convertToWorldSpaceAR(nodePoint: math.Vec3, out?: math.Vec3): math.Vec3;
 /**
 * 坐标计算
 * @param target ScrollView 中被点击的 item 元素 
 * @param item  数据
 */
public setData(target: Node, item: CoachCharacterItem) {

    //console.log('target.getPosition():', target.getPosition());
    //当前节点本地坐标转世界坐标、
    //世界坐标转到 对应的 节点坐标
    let vec: Vec3 = this.rootParemt.getComponent(UITransform).convertToNodeSpaceAR(target.parent.getComponent(UITransform).convertToWorldSpaceAR(target.getPosition()));
    console.log(vec)

    //修正位置
    if (vec.x < 400) {
        vec.x += 250;
    } else {
        vec.x -= 250;
    }
    if (vec.y > 140) {
        vec.y = 140;
    } else if (vec.y < -100) {
        vec.y = -100;
    }

    this.node.setPosition(vec);
    this.node.active = true;
}

2.拖拽元素

我们给详情页面添加 拖拽事件的监听:

这里有个技术点 tuochMove 中获取到的坐标实际上是 触点坐标, 我们需要将这个坐标 转化到 UI坐标才能对 UI元素赋值

//添加监听事件
onLoad() {
    this.node.on(Node.EventType.TOUCH_START, this.touchStart, this);
    this.node.on(Node.EventType.TOUCH_MOVE, this.touchMove, this);
    this.node.on(Node.EventType.TOUCH_END, this.touchEnd, this)
    this.node.on(Node.EventType.TOUCH_CANCEL, this.touchCancel, this)
}

//展示只有 在拖拽情况下才显示的 元素 equipmentFly
touchStart(event: EventTouch) {
    //Log.trace('touchStart');
    //展示 拖拽 元素 equipmentFly
    this.equipmentFly.node.active = true;
}

//获取 触点坐标位置
touchMove(event: EventTouch) {
    //更新 拖拽元素位置
    this.equipmentFly.updatePosition(event.getLocation());
}

//结束拖拽 隐藏拖拽元素
touchCancel(event: EventTouch) {
    //console.log('touchCancel');
    //隐藏 拖拽 元素 equipmentFly
    this.equipmentFly.node.active = false;
    //执行 拖拽完毕的判断逻辑
    this.equipmentFly.onConfirm();
    //重置拖拽元素的位置
    this.equipmentFly.resetPostion(new Vec3(0, 50));

}
//结束拖拽
touchEnd(event: EventTouch) {
    //console.log('touchEnd');
    //隐藏 拖拽 元素 equipmentFly
    this.equipmentFly.node.active = false;
    //执行 拖拽完毕的判断逻辑
    this.equipmentFly.onConfirm();
    //重置拖拽元素的位置
    this.equipmentFly.resetPostion(new Vec3(0, 50));
}
//隐藏详情面板
public hideNoticePanel() {
    this.equipmentFly.node.active = false;
    this.equipmentFly.resetPostion(new Vec3(0, 50));
    this.node.active = false;
}
触点坐标转换UI坐标的实现:

触点坐标转 UI 坐标需要通过  camera.screenToWorld(触点坐标)

Cocos Creator APIDescriptionicon-default.png?t=N7T8https://docs.cocos.com/creator/3.8/api/zh/class/renderer.scene.Camera?id=screenToWorld大概逻辑整理如下:

  1. 拿到 拖拽 事件传递的 触点坐标
  2. 触点坐标 通过 camera 转 世界坐标
  3. 世界坐标转 UI局部坐标赋值 拖拽元素坐标    这样就能实现 拖拽元素随着手指滑动而移动

//拿到摄像机
onLoad() {
    //获取摄像机
    this.camera = find('Canvas/Camera').getComponent(Camera);
    //获取卡槽1 的 世界坐标
    this.vec3WorldBattle = this.nodeWearBattle.parent.getComponent(UITransform).convertToWorldSpaceAR(this.nodeWearBattle.getPosition());
   //获取卡槽1 的 世界坐标
    this.vec3WorldReward = this.nodeWearReward.parent.getComponent(UITransform).convertToWorldSpaceAR(this.nodeWearReward.getPosition());

}



/**
 * 触点坐标转换 
 * 最后使用UI坐标赋值
 * @param vec 
 */
updatePosition(vec: Vec2) {
    this.vec3Pos.x = vec.x;
    this.vec3Pos.y = vec.y;
    //触点坐标转 世界坐标
    this.vec3WorldNode = this.camera.screenToWorld(this.vec3Pos);
    // 世界坐标转 UI局部坐标
    this.vec3new = this.node.parent.getComponent(UITransform).convertToNodeSpaceAR(this.vec3WorldNode);
    this.node.position = this.vec3new;
    
    //计算 拖拽元素 和 两个卡槽的 x轴距离 这里  distance 为 50个单位
    if (Math.abs(this.vec3WorldNode.x - this.vec3WorldBattle.x) < this.distance) {
        //距离到了 通知界面展示 卡槽1 选中框
        this.coachCharacterEquipmentView.onPreview(this.equipment, 0);
    } else if (Math.abs(this.vec3WorldNode.x - this.vec3WorldReward.x) < this.distance) {       //距离到了 通知界面展示 卡槽2 选中框
        this.coachCharacterEquipmentView.onPreview(this.equipment, 1);
    } else {
        //这里处理 距离都没到的逻辑 移除选中框 以及 从 已经选中状态到 没选中状态的切换
        this.coachCharacterEquipmentView.onPreview(null, -1);
    }

}

3.卡槽判定

通过上面的代码我们已经可以实现,拖转元素到 卡槽函数的调用:

//计算 拖拽元素 和 两个卡槽的 x轴距离 这里  distance 为 50个单位
if (Math.abs(this.vec3WorldNode.x - this.vec3WorldBattle.x) < this.distance) {
    //距离到了 通知界面展示 卡槽1 选中框
    this.coachCharacterEquipmentView.onPreview(this.equipment, 0);
} else if (Math.abs(this.vec3WorldNode.x - this.vec3WorldReward.x) < this.distance) {
    //距离到了 通知界面展示 卡槽2 选中框
    this.coachCharacterEquipmentView.onPreview(this.equipment, 1);
} else {
    //这里处理 距离都没到的逻辑 移除选中框 以及 从 已经选中状态到 没选中状态的切换
    this.coachCharacterEquipmentView.onPreview(null, -1);
}
因为我们一个卡槽有多个 装备位置,需要响应的装备展示 对应的卡槽;
this.coachCharacterEquipmentView.onPreview(data,soltIndex);

函数onPreview 当到对应的卡槽就把数据传递过去,具体不够就传 null,过去

卡槽现实的我们就略过去;

 

卡槽数据的确认:

这个环节就对应的是 到对应的卡槽,并且松手:

上面的代码中我们已经加过监听了;通过实践发现 即便我同时监听了 TOUCH_END  和 TOUCH_CANCEL 他也只有一个会执行;所有我两个都加了

所以一来逻辑就清晰了:

  1. 当拖拽元素到卡槽中的时候,我们已经传递参数过去了,并且 我们当拖出 卡槽的时候还把数据 赋值为  null,
  2. 当我们停止拖拽的时候 判断下 传递过去的 数据是否为 null,
  3. 如果为 null  则说明 没有在卡槽中停止 拖拽,不为空则为 在卡槽中停止的拖拽
touchCancel(event: EventTouch) {
    //console.log('touchCancel');
    this.equipmentFly.node.active = false;
    this.equipmentFly.onConfirm();
    this.equipmentFly.resetPostion(new Vec3(0, 50));

}
touchEnd(event: EventTouch) {
    //console.log('touchEnd');
    this.equipmentFly.node.active = false;
    this.equipmentFly.onConfirm();
    this.equipmentFly.resetPostion(new Vec3(0, 50));
}

到这里基本上级功能都实现了。而且我们的 距离判断逻辑只有在 拖拽时候才会执行,应该说性能还不错!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

C语言笔记第10篇:内存函数

上一篇的字符串函数只是针对字符串的函数&#xff0c;而内存函数是针对内存块的&#xff0c;不在乎内存中存储的数据&#xff01;这就是字符串函数和内存函数的区别。 准备好爆米花&#xff0c;正片开始 1、memcpy的使用和模拟实现 memcpy库函数的功能&#xff1a;任意类型数…

数据结构严蔚敏版精简版-绪论

1.基本概念和术语 下列概念和术语将在以后各章节中多次出现&#xff0c;本节先对这些概念和术语赋予确定的含义。 数据(Data)&#xff1a;数据是客观事物的符号表示&#xff0c;是所有能输入到计算机中并被计算机程序处理的符号 的总称。 数据元素(DataElement)&#xff1a;…

【CC2530-操作外部flash】

zigbee cc2530操作flash&#xff0c;以cc2530读flash_id为例子&#xff1b; void InitIO() {CLKCONCMD & ~0x40; //设置系统时钟源为32MHZ晶振 while(CLKCONSTA & 0x40); //等待晶振稳定为32M CLKCONCMD & ~0x47; //设置系统主时钟频率为32MHZ…

【自动驾驶】针对低速无人车的线控底盘技术

目录 术语定义 一般要求 操纵装置 防护等级 识别代号 技术要求 通过性要求 直线行驶稳定性 环境适应性要求 功能安全要求 信息安全要求 故障处理要求 通信接口 在线升级(OTA) 线控驱动 动力性能 驱动控制响应能力 线控制动 行车制动 制动响应能力 线控转向 总体要求 线控…

防火墙技术基础篇:基于NSP配置L2TP VPN

防火墙技术基础篇&#xff1a;基于eNSP配置L2TP VPN 一、L2TP VPN概念 L2TP&#xff08;Layer 2 Tunneling Protocol&#xff09;&#xff0c;即第二层隧道协议&#xff0c;是一种基于点对点协议&#xff08;PPP&#xff09;的二层隧道协议。它结合了PPTP&#xff08;Point-t…

Docker的安装、启动和配置镜像加速

前言&#xff1a; Docker 分为 CE 和 EE 两大版本。CE 即社区版&#xff08;免费&#xff0c;支持周期 7 个月&#xff09;&#xff0c;EE 即企业版&#xff0c;强调安全&#xff0c;付费使用&#xff0c;支持周期 24 个月。 而企业部署一般都是采用Linux操作系统&#xff0c;而…

Android关闭硬件加速对PorterDuffXfermode的影响

Android关闭硬件加速对PorterDuffXfermode的影响 跑的版本minSdk33 编译SDK34 import android.content.Context import android.graphics.Bitmap import android.graphics.Canvas import android.graphics.Color import android.graphics.Paint import android.graphics.Port…

Nginx企业级负载均衡:技术详解系列(16)—— Nginx的try_files指令,你知道这个指令是干什么的吗?

你好&#xff0c;我是赵兴晨&#xff0c;97年文科程序员。 今天咱们来聊一聊Nginx的try_files指令&#xff0c;你知道这个指令是干什么的吗&#xff1f; 如果你对Web服务器配置有所了解&#xff0c;那么你可能会对try_files指令感到好奇。这个指令实际上是Nginx配置中的一项强…

专业渗透测试 Phpsploit-Framework(PSF)框架软件小白入门教程(十四)

本系列课程&#xff0c;将重点讲解Phpsploit-Framework框架软件的基础使用&#xff01; 本文章仅提供学习&#xff0c;切勿将其用于不法手段&#xff01; 接上一篇文章内容&#xff0c;讲述如何进行Phpsploit-Framework软件的基础使用和二次开发。 今天&#xff0c;我们来介…

19.4-STM32接收数据-状态显示在屏幕 openMV寻迹与小车控制 Openmv+STM32F103C8T6视觉巡线小车

这个是全网最详细的STM32项目教学视频。 第一篇在这里: 视频在这里 STM32智能小车V3-STM32入门教程-openmv与STM32循迹小车-stm32f103c8t6-电赛 嵌入式学习 PID控制算法 编码器电机 跟随 19.4-STM32接收数据-状态显示在屏幕 先通过串口上位机模拟发送、 STM32有视觉循迹模式、…

SwiftUI知识点(一)

前言&#xff1a; Swift知识点&#xff0c;大至看完了&#xff0c;公司项目是Swift语言写的&#xff0c;后续苹果新出的SwiftUI&#xff0c;也需要学习一下 不知觉间&#xff0c;SwiftUI是19年出的&#xff0c;现在24年&#xff0c;5年前的东西了 学习的几个原因&#xff1a; …

探索DIYGW可视化开发工具:提升UniApp项目效率与质量的新途径

一、引言 在快速迭代和不断创新的移动应用开发领域中&#xff0c;开发者们常常面临着一个共同的挑战&#xff1a;如何在保证开发质量的同时&#xff0c;缩短开发周期。近期&#xff0c;一款名为DIYGW的可视化开发工具进入了我们的视野&#xff0c;它以其独特的拖拽式开发方式和…

Linux共享内存创建和删除

最近项目中使用到了共享内存记录下 创建共享内存: 删除共享内存: 代码: #include <stdio.h> #include <stdlib.h> #include <string.h> #include <fcntl.h> #include <sys/mman.h> #include <sys/stat.h> #include <u…

计算机科技的飞跃:从机械到量子的革命之旅

计算机科技的历史长河中&#xff0c;涌现出了许多划时代的事件和发明&#xff0c;它们不仅标志着技术的飞跃&#xff0c;也深刻地改变了人类生活的方方面面。 ENIAC的诞生 在第二次世界大战的硝烟中&#xff0c;美国军方迫切需要一种能够迅速解决复杂计算问题的工具&#xff0…

深入理解文件系统和日志分析

文件是存储在硬盘上的&#xff0c;硬盘上的最小存储单位是扇区&#xff0c;每个扇区的大小是512字节。 inode&#xff1a;存储元信息&#xff08;包括文件的属性&#xff0c;权限&#xff0c;创建者&#xff0c;创建日期等等&#xff09; block&#xff1a;块&#xff0c;连续…

SpringMVC:转发和重定向

1. 请求转发和重定向简介 参考该链接第9点 2. forward 返回下一个资源路径&#xff0c;请求转发固定格式&#xff1a;return "forward:资源路径"如 return "forward:/b" 此时为一次请求返回逻辑视图名称 返回逻辑视图不指定方式时都会默认使用请求转发in…

2024年06月数据库流行度最新排名

点击查看最新数据库流行度最新排名&#xff08;每月更新&#xff09; 2024年06月数据库流行度最新排名 TOP DB顶级数据库索引是通过分析在谷歌上搜索数据库名称的频率来创建的 一个数据库被搜索的次数越多&#xff0c;这个数据库就被认为越受欢迎。这是一个领先指标。原始数…

模板-初阶

引言&#xff1a; 在C&#xff0c;我们已经学过了函数重载&#xff0c;这使得同名函数具有多个功能。但是还有一种更省力的方法&#xff1a;采用模板。 本文主要介绍以下内容 1. 泛型编程 2. 函数模板 3. 类模板 1.泛型编程 在将这一部分之前&#xff0c;通过一个故事引…

语言模型的校准技术:增强概率评估

​ 使用 DALLE-3 模型生成的图像 目录 一、说明 二、为什么校准对 LLM 模型至关重要 三、校准 LLM 概率的挑战 四、LLM 的高级校准方法 4.1 语言置信度 4.2 增强语言自信的先进技术 4.3 基于自一致性的置信度 4.4 基于 Logit 的方法 五、代理模型或微调方法 5.1 使用代…

Python 网络爬虫:深入解析 Scrapy

大家好&#xff0c;在当今数字化时代&#xff0c;获取和分析网络数据是许多项目的关键步骤。从市场竞争情报到学术研究&#xff0c;网络数据的重要性越来越被人们所认识和重视。然而&#xff0c;手动获取和处理大量的网络数据是一项繁琐且耗时的任务。幸运的是&#xff0c;Pyth…