Cocos Creator组件化开发之——地图类缩放拖动点击组件

2023-10-26

“好记性不如烂笔头,记录开发过程中的点点滴滴”——xshu

“书写是对思维的缓存” ——佚名

进入正题

       在游戏开发过程中经常会遇到渲染元素的尺寸大于你所能展示的区域,需要玩家自行进行操作、拖动以及点击,比如地图通常会超出玩家所示区域,这个时候需要一个能够缩放、拖动以及点击的组件来完成这个需求。

  1.效果展示

        先来看看实际效果(由于电脑无法操作双指缩放,所以是先在手机上录屏然后再在电脑上做成gif的,文末有demo地址,大家可以下载下来自行体验一下)

  2.属性配置

      1.组件的一些配置参数,可以直接在编辑器里面填写数值来调试效果。

       2.再来看看组件的节点以及属性定义块

      下图是编辑器里面的设置

    3.在start生命周期接口里面会先对map和mapContainer进行判断,如果为空则取默认值。

  3.代码逻辑的实现细节

private addEvent(): void {
    this.map.on(cc.Node.EventType.TOUCH_MOVE, (event: any) => {
        if (this.operLock) return; // 如果触摸操作暂时锁定则不响应
        let touches: any[] = event['getTouches'](); // 获取所有触摸点
        // 1.x当多点触摸的时候 第二个触摸点即使不在node上也能进来 而且target也是当前node
        // 通过rect是否包含当前触摸点来过滤无效的触摸点
        touches
            .filter(v => {
                let startPos: cc.Vec2 = v.getStartLocation(); // 触摸点最初的位置
                let worldPos: cc.Vec2 = this.mapContainer.convertToWorldSpaceAR(cc.Vec2.ZERO);
                let worldRect: cc.Rect = cc.rect(
                    worldPos.x - this.mapContainer.width / 2,
                    worldPos.y - this.mapContainer.height / 2,
                    this.mapContainer.width,
                    this.mapContainer.height
                );
                return worldRect.contains(startPos);
            })
            .forEach(v => { // 将有效的触摸点放在容器里自行管理
                let temp: any[] = this.mapTouchList.filter(v1 => v1.id === v.getID());
                if (temp.length === 0) {
                    this.mapTouchList.push({ id: v.getID(), touch: v });
                }
            })
            ;
        if (this.mapTouchList.length >= 2) { // 如果容器内触摸点数量超过1则为多点触摸,此处暂时不处理三点及以上的触摸点,可以根据需求来处理
            this.isMoving = true;
            this.dealTouchData(this.mapTouchList, this.map);
        } else if (this.mapTouchList.length === 1) {
            // sigle touch
            let touch: any = this.mapTouchList[0].touch;
            let startPos: cc.Vec2 = touch.getStartLocation();
            let nowPos: cc.Vec2 = touch.getLocation();
            // 有些设备单点过于灵敏,单点操作会触发TOUCH_MOVE回调,在这里作误差值判断
            if ((Math.abs(nowPos.x - startPos.x) <= MOVE_OFFSET ||
                Math.abs(nowPos.y - startPos.y) <= MOVE_OFFSET) &&
                !this.isMoving) {
                return cc.log('sigle touch is not move');
            }
            let dir: cc.Vec2 = touch.getDelta();
            this.isMoving = true;
            this.dealMove(dir, this.map, this.mapContainer);
        }
    }, this);

    this.map.on(cc.Node.EventType.TOUCH_END, (event) => {
        if (this.operLock) return cc.log('operate is lock');
        // 需要自行管理touches队列, cocos 的多点触控并不可靠
        if (this.mapTouchList.length < 2) {
            if (!this.isMoving) {
                let worldPos: cc.Vec2 = event['getLocation']();
                let nodePos: cc.Vec2 = this.map.convertToNodeSpaceAR(worldPos);
                this.dealSelect(nodePos);
            }
            this.isMoving = false; // 当容器中仅剩最后一个触摸点时讲移动flag还原
        };
        this.removeTouchFromContent(event, this.mapTouchList);
    }, this);

    this.map.on(cc.Node.EventType.TOUCH_CANCEL, (event) => {
        if (this.operLock) return;
        if (this.mapTouchList.length < 2) { // 当容器中仅剩最后一个触摸点时讲移动flag还原
            this.isMoving = false;
        };
        this.removeTouchFromContent(event, this.mapTouchList);
    }, this);

    this.map.on(cc.Node.EventType.MOUSE_WHEEL, (event) => {
        if (this.operLock) return;
        cc.log('==== MOUSE WHEEL ===');
            
        let location: any = event['getLocation']();
        let worldPos: cc.Vec2 = cc.v2(location.x, location.y);
        let scrollDelta: number = event['getScrollY']();
        let scale: number = (this.map.scale - (scrollDelta / 10000 * -1));

        let target: cc.Node = this.map;
        let pos: cc.Vec2 = target.convertToNodeSpaceAR(worldPos);
        this.smoothOperate(target, pos, scale);
    }, this);
}

// 删除无用的触摸点
public removeTouchFromContent(event: any, content: any[]): void {
    let eventToucheIDs: number[] = event['getTouches']().map(v => v.getID());
    for (let len = content.length, i = len - 1; i >= 0; --i) {
        if (eventToucheIDs.indexOf(content[i].id) > -1)
            content.splice(i, 1); // 删除触摸
    }
}

2.处理触摸过程中的缩放动作

private dealTouchData(touches: any[], target: cc.Node): void {
    let touch1: any = touches[0].touch;
    let touch2: any = touches[1].touch;
    let delta1: any = touch1.getDelta();
    let delta2: any = touch2.getDelta();
    let touchPoint1: cc.Vec2 = target.convertToNodeSpaceAR(touch1.getLocation());
    let touchPoint2: cc.Vec2 = target.convertToNodeSpaceAR(touch2.getLocation());
    let distance: cc.Vec2 = touchPoint1.sub(touchPoint2);
    let delta: cc.Vec2 = delta1.sub(cc.v2(delta2.x, delta2.y));
    let scale: number = 1;
    if (Math.abs(distance.x) > Math.abs(distance.y)) {
        scale = (distance.x + delta.x) / distance.x * target.scaleX;
    } else {
        scale = (distance.y + delta.y) / distance.y * target.scaleY;
    }
    let pos: cc.Vec2 = touchPoint2.add(cc.v2(distance.x / 2, distance.y / 2));
    this.smoothOperate(target, pos, scale);
}
private smoothOperate(target: cc.Node, pos: cc.Vec2, scale: number): void {
    let scX: number = scale;
    // 当前缩放值与原来缩放值之差
    let disScale: number = scX - target.scaleX;
    // 当前点击的坐标与缩放值差像乘 
    let gapPos: cc.Vec2 = pos.scale(cc.v2(disScale, disScale));
    // 当前node坐标位置减去点击 点击坐标和缩放值的值
    let mapPos: cc.Vec2 = target.getPosition().sub(cc.v2(gapPos.x, gapPos.y));
    // 放大缩小
    if (!this.isOutRangeScale(scale)) {
        scale = (scale * 100 | 0) / 100;
        target.scale = scale;
        this.dealScalePos(mapPos, target);
    }
    // 更新 label 显示
    scale = this.dealScaleRange(scale);
    this.scaleTime.string = `${scale * 100 | 0}%`;
}
// 处理地图边缘缩放,移动地图
private dealScalePos(pos: cc.Vec2, target: cc.Node): void {
    if (target.scale === 1) {
        pos = cc.Vec2.ZERO;
    }
    else {
        let worldPos: cc.Vec2 = this.node.convertToWorldSpaceAR(pos);
        let nodePos: cc.Vec2 = this.node.convertToNodeSpaceAR(worldPos);
        let edge: any = this.calculateEdge(target, this.node, nodePos);
        if (edge.left > 0) {
            pos.x -= edge.left;
        }
        if (edge.right > 0) {
            pos.x += edge.right;
        }
        if (edge.top > 0) {
            pos.y += edge.top;
        }
        if (edge.bottom > 0) {
            pos.y -= edge.bottom;
        }
    }
    target.position = pos;
}

3.处理玩家拖拽地图的操作

private dealMove(dir: cc.Vec2, map: cc.Node, container: cc.Node): void {
    let worldPos: cc.Vec2 = map.convertToWorldSpaceAR(cc.Vec2.ZERO);
    let nodePos: cc.Vec2 = container.convertToNodeSpaceAR(worldPos);
    nodePos.x += dir.x;
    nodePos.y += dir.y;
    let edge: any = this.calculateEdge(map, container, nodePos);
    if (edge.lBorderDelta <= 0 && edge.rBorderDelta <= 0) {
        map.x += dir.x;
    }
    if (edge.uBorderDelta <= 0 && edge.dBorderDelta <= 0) {
        map.y += dir.y;
    }
}

4.处理玩家单击地图的操作,可以在组件外调用setSingleTouchCb来传入响应玩家点击操作的回调函数

public setSinglTouchCb(cb: Function): void {
    this.singleTouchCb = cb;
}

private dealSelect(nodePos: cc.Vec2): void {
    cc.log(`click map on cc.v2(${nodePos.x}, ${nodePos.y})`);
    // do sth
    if (this.singleTouchCb) this.singleTouchCb(nodePos);
}

5.处理移动和缩放用到的计算地图边距与容器距离的api

// 计算map的四条边距离容器的距离
public calculateEdge(target: cc.Node, container: cc.Node, nodePos: cc.Vec2): any {
    let realWidth: number = target.width * target.scaleX;
    let realHeight: number = target.height * target.scaleY;
    let lBorderDelta: number = (nodePos.x - realWidth / 2) + container.width / 2;
    let rBorderDelta: number = container.width / 2 - (realWidth / 2 + nodePos.x); // <= 0 safe
    let uBorderDelta: number = container.height / 2 - (realHeight / 2 + nodePos.y);
    let dBorderDelta: number = (nodePos.y - realHeight / 2) + container.height / 2;
    return { lBorderDelta, rBorderDelta, uBorderDelta, dBorderDelta };
}

4.文章结尾

       这也是我第一次尝试封装组件然后分享出来,文笔很差请大家见谅,希望能够帮助到大家,同时也是对自己平日工作里点点滴滴的一种记录以及对自身文笔一种锻炼。感谢大家能够阅读到最后,有什么不足之处或疑惑的地方欢迎大家留言讨论。

最后附上demo地址:GitHub - xshu1996/MapControl-master

// update 2020/04/02 新增鼠标滚轮放大缩小功能 

在这里感谢 渡鸦 大佬的公众号,大家可以关注 “CocosCreator笔记” 微信公众号获取更多游戏开发过程中的一些知识分享,谢谢大家~

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Cocos Creator组件化开发之——地图类缩放拖动点击组件 的相关文章

  • cocos creator入门教程实现简化版贪吃蛇

    开发工具 Cocos Creator和VS Code 开发语言 TS 简化版贪吃蛇的实现主要涉及的功能就是在吃到场景中随机产生产生的物体后 物体会到蛇头的后面并且跟随移动路径 其原理主要是通过数组来存储相关的坐标数据
  • Android 解决“Could not resolve all artifacts for configuration ‘:classpath‘”报错问题

    在开发的过程中 同步代码的时候 突然出现这个报错信息 Caused by org gradle api internal artifacts ivyservice DefaultLenientConfiguration ArtifactRe
  • vue3使用Element-plus与TS(TypeScript)

    如果你有一个困扰就是为什么直接CV Element plus文档里的代码总是报各种错误 那你看这篇就对啦 针对Vue3哦 1 项目导入vscode后 安装Element plus npm install element plus save
  • cocos creator action之jumpTo、jumpBy

    cocos creator中 jumpTo jumpBy的使用方法例子 cc Class extends cc Component properties move cc Node start this moveT moveT this mo
  • 软件外包公司到底干啥的?要不要去外包公司?

    一 什么是外包 软件外包分为 人力外包和项目外包两个方向 1 劳务派遣 指的是把员工外派到对应的用工企业打 短工 比如很多工程师虽然签约了中软国际 东软 文思海辉 软通动力 润和等软件公司 但实际工作地点是在华为 接受华为员相关负责人的工作
  • cocos creator 中读取Excel表格中的数据

    一 使用相应工具将Excel文件转化成JSON文件导入到cocos creator资源文件 二 在VS中对Excel文本中的数据进行转换 Excel文本中各项数据的名称对应代码中的data export default class Task
  • C语言实现url解析小实例

    一 前言 前面一口君写了一篇关于url的文章 一文带你理解URI 和 URL 有什么区别 本篇在此基础上 编写一个简单的用于解析url的小例子 最终目标是解析出URL中所有的数据信息 二 库函数 用到的几个库函数如下 1 strncasec
  • python读取数据库PostgreSQL导出excel表格

    1 现有数据和目标成果 1 1现有数据 源数据保存在数据库中 使用的数据库管理软件是PostgreSQL 本质上来说 数据存储在数据库中是以记录存储在表上实现的 在excel表格中也是以记录的形式存在 所以数据库中表的列 字段 可以与exc
  • presto函数和hive函数的使用

    最近做大数据分析工作比较多 主要与presto和hive查询引擎打交道 presto在实时计算上真的很快 个人感觉比hive要快10倍吧 但是hive在面向海量数据的分析计算上也是很牛逼的 这里不得不记录下两者在使用上的一些区别 粘贴一段二
  • 解决tomcat 启动超过45秒时间限制

    当在eclipse运行一个javaweb项目时 出现了如下图片中的问题 解决方法 1在如下页面中找到Servers 找不到的话可以通过Window gt gt Show View放到下方 2 双击Servers进到如下页面 3 打开箭头所指
  • svn服务器搭建

    1 首先下载svn sudo apt get install subversion 如果不能安装先更新库再试 sudo apt get update 2 添加svn管理用户及subversion组 sudo adduser svnuser
  • C语言/实现MD5加密

    本文详细视频讲解 已经发布到B站 https www bilibili com video BV1uy4y1p7on 更多仔细 请关注公众号 一口Linux 一 摘要算法 摘要算法又称哈希算法 它表示输入任意长度的数据 输出固定长度的数据
  • 开发中遇到不好解决的问题记录

    1 本地和测试和真实模拟数据 本地连接生产环境且登录相关用户token 都重现不了 后面管理员账号转交成功了 重现不了用户报的错
  • SQL复习要点

    1 数据库系统阶段的数据管理特点 1 数据结构化2 数据共享性高 冗余度小 易扩充 3 数据独立性高4 统一的数据管理和控制功能 2 数据库 database DB 是存放数据的仓库 3 数据库管理系统 data base manageme
  • git修改仓库名次之后,本地仓库重定向问题

    在github网页中更改了项目的名次 再次推送的时候报这样的错误fatal repository https xxx git not founds 使用下面的命令将推送的远程仓库重定向 git remote set url origin u
  • DOM之获取标签元素、属性和属性值

    1 获取标签元素 docunment getElementById id 只能获取一个id标签 docunment getElementByClassName class 获取class标签 结果是一个为数组 不能用forEach docu
  • 如何使用随机数实现自动发扑克牌?

    学习不止 问答不止 一 粉丝问题 二 相关函数说明 1 函数说明 产生随机数的方法很多 常用的是rand srand 来看一下这2个函数的定义 SYNOPSIS include
  • 谷贱伤农,薪贱伤码农!

    最近被东方甄选刷屏了 截止6月21日 粉丝已经达到1749万 飞瓜数据显示 东方甄选已经是抖音带货第一名 东方甄选火起来也就是从上周那个双语带货视频在各个社群里到处转发 走到今天 也不过才一周多点的时间 从初火到大火 东方甄选 一周封神 一
  • 链表【2】

    文章目录 24 两两交换链表中的节点 题目 算法原理 代码实现 143 重排链表
  • 分治-归并排序

    文章目录 315 计算右侧小于当前元素的个数 1 题目 2 算法原理 3 代码实现 493 翻转对

随机推荐

  • Python读取pdf表格写入excel

    背景 今天突然想到之前被要求做同性质银行的数据分析 妈耶 十几个银行 每个银行近5年的财务数据 而且财务报表一般都是 pdf 的 我们将 pdf 中表的数据一个个的拷贝到 excel 中 再借助 excel 去进行求和求平均等聚合函数操作
  • windows下redis配置密码

    转载 https www cnblogs com GuoJunwen p 9238624 html redis安装后目录如下 最简单的启动方式是直接双击redis server exe 如果要设置密码 首先打开配置文件 要注意的是这两个都是
  • 数据库设计中常见表结构的设计技巧

    一 树型关系的数据表 不少程序员在进行数据库设计的时候都遇到过树型关系的数据 例如常见的类别表 即一个大类 下面有若干个子类 某些子类又有子类这样的情况 当类别不确定 用户希望可以在任意类别下添加新的子类 或者删除某个类别和其下的所有子类
  • 使用MQTT.fx向ThingsBoard发布遥测数据

    一 在ThingsBoard平台新建设备 复制访问令牌 二 打开MQTT fx进行连接 填写服务地址及端口以及设备访问令牌 特别注意 这里踩了个深坑 这个端口一定要对应thingsboard服务thingsboard yml中的配置 这个端
  • c语言回文数

    回文数 include
  • 微信小程序:云开发·初探

    Good days give you happiness and bad days give you experience 顺境带来快乐 逆境带来成长 云开发 quickstart 这是云开发的快速启动指引 其中演示了如何上手使用云开发的三
  • VSCode集成PlantUML

    VSCode集成PlantUML 哈喽大海豚 前端 2018 01 23 前端 UML PlantUML VSCode PlantUML介绍 PlantUML是一个允许快速编写以下图类的组件 序列图 Sequence diagram 用例图
  • 常见Windows硬件故障

    电脑主机滴滴滴响是什么原因 不同的响声代表不同的硬件问题 一下是几种主板设置的提示声音代表的具体问题 1 AWARD的BIOS设定为 长声不断响 内存条未插紧 2短 系统正常启动 2短 CMOS设置错误 需重新设置 1长1短 内存或主板错误
  • CollAFL: Path Sensitive Fuzzing 模糊测试论文阅读

    CollAFL Path Sensitive Fuzzing 会议 S P2018 这是一篇内容十分饱满的Fuzz文章 受益匪浅 1 Abstract and Introduction 对于覆盖率引导的模糊测试来说 跟踪覆盖率是至关重要的
  • IT风投案例分析——facebook

    Facebook 虽然Facebook对于中国人来说是一个不存在的网站 但这并不能妨碍它成为世界前列的互联网公司 Facebook是很特殊的 它的创始人扎克伯格1984年出生 在2004年就开始创建Facebook 当时他只有仅仅二十岁 那
  • Vivado软件的一些报错总结

    1 Synth 8 2543 port connections cannot be mixed ordered and named E FPGA project Xilinx ZYNQ three days sobel 032 face o
  • 渗透测试概述与流程

    渗透测试概述 渗透测试是一种通过模拟攻击的技术与方法 挫败目标系统的安全控制措施并获得控制访问权的安全测试方法 网络渗透测试主要依据CVE已经发现的安全漏洞 模拟入侵者的攻击方法对网站应用 服务器系统和网络设备进行非破坏性质的攻击性测试 C
  • 华为od 安全测试岗 简谈机试面试【更新完】

    PS 准备慢慢更新下最近我在od的机试题以及一二轮面试题和hr面 主管面 最后成功拿到offer 但不打算去了 然后成功让对接人破防 od懂得都懂 流程是 机试 gt 一面 gt 中间穿插了性格测试考试 gt 二面 gt HR面 gt 综面
  • 关于微信小程序的生命周期

    关于微信小程序的生命周期 onLaunch 官网App vue App uvue uni app官网 问题描述 我现在有个小程序 取名为a 有个用户b 从来没有打开过小程序 那么他第一次打开小程序的时候会触发onLaunch 然后用户b退出
  • flask+mysql+ECharts+ajax+百度地图实现数据可视化

    思路 1 后台连接数据库创建session对象 2 创建表关系映射 3 查询数据 4 将数据封装成特定格式 json 5 前台通过ajax请求指定路由异步加载数据并在地图上展示 先来看一下效果 地图参考 https gallery echa
  • uniapp tabbar底部栏 子组件页面不刷新解决方案

    场景 uniapp 来回切换底部栏tabbar 页面初始化数据 当前子组件页面会发送数据请求 再次切换进入 当前页面的子组件不发送请求 解决方案 1 父组件在onShow钩子里面中向子组件传递随机数 2 子组件接收数据 并进行watch监听
  • Android 系统设置中显示设置之休眠和屏保设置篇

    Android 系统设置中显示设置之休眠和屏保设置篇 在上一篇中我们学习了Android系统设置中字体大小和屏幕旋转设置基本内容 在这一篇中我们继续学习显示设置中的休眠和屏保设置 1 休眠设置 首先我们来看一下休眠设置在界面中的定义 1
  • js--for循环99乘法表的四种样式

  • Eclipse/Code blocks/PyCharm连接MySQL数据库初尝试

    第一次使用MySQL 在此罗列我搭配环境的一些路程 我最终Code blocks和Eclipse成功了 Pycharm一直因为版本不合适未成功 我也把我试过的未成功的方法罗列在此 希望可以得到最终的解决 Code blocks 我在用cod
  • Cocos Creator组件化开发之——地图类缩放拖动点击组件

    好记性不如烂笔头 记录开发过程中的点点滴滴 xshu 书写是对思维的缓存 佚名 进入正题 在游戏开发过程中经常会遇到渲染元素的尺寸大于你所能展示的区域 需要玩家自行进行操作 拖动以及点击 比如地图通常会超出玩家所示区域 这个时候需要一个能够