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 简化版贪吃蛇的实现主要涉及的功能就是在吃到场景中随机产生产生的物体后 物体会到蛇头的后面并且跟随移动路径 其原理主要是通过数组来存储相关的坐标数据
  • CocosCreator中TiledMap简单使用

    在开发游戏过程中 有时候会用到TiledMap 瓦片地图 我这边使用的是1 4 3版本的tiledmap 2 3 4版本的CocosCreator 其他版本的tiledmap会有做不了动画的问题 后面会说到 视频参考 B站UP主 阿信OL
  • C语言实现url解析小实例

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

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

    server port 8086 spring profiles active dev application name my springsecurity plus datasource driver driver class name
  • 作为一个江苏人,我眼中的苏宁

    江苏人 人人都在苏宁电器的地面商店里买过东西 我也不例外 听说张近东要转让苏宁易购的股份给一家疑似国企或国有基金 作为一个地道的江苏人 感慨良多 中国有4个电商平台 天猫 京东 拼多多和苏宁易购 还有大商 国美什么 已经彻底掉队 暂且不提
  • svn服务器搭建

    1 首先下载svn sudo apt get install subversion 如果不能安装先更新库再试 sudo apt get update 2 添加svn管理用户及subversion组 sudo adduser svnuser
  • 稳压二极管及特性介绍

    稳压二极管及特性介绍 稳压二极管是一种特殊工艺制造的面结型硅半导体二极管 此类二极管杂质浓度比较高 空间电荷区的电荷密度比较大 该区域狭窄 容易形成强电场 当反向电压来临时 反向电流急剧增加 产生反向击穿 稳压管核心理论 稳压管未工作时 处
  • CHAR与VARCHAR字段类型的适用场景

    在知乎看到的 特意做个笔记 CHAR CHAR适合存储很短的字符串 或者所有值都接近同一个长度 例如 CHAR非常适合存储密码的MD5值 因为这是一个定长的值 对于经常变更的数据 CHAR也比VARCHAR更好 因为定长的CHAR类型不容易
  • 移动电源/充电管理设计

    移动电源 充电管理设计 简述 伴随着智能手机的兴起 智能手机的续航成了较大的问题 因此移动电源 充电宝 成为不时之需 而近来的各类无线耳机的兴起 无线耳机的续航又成为新的问题 为此针对无线耳机充电的各大厂商又一次成为热门 而这些都需要类似于
  • Makefile实例,利用Makefile给多文件、多目录C源码建立工程

    0 前言 粉丝留言 想知道如何使用Makefile给多个文件和多级目录建立一个工程 必须安排 关于Makefile的入门参考文章 可以先看这篇文章 Makefile入门教程 为了让大家有个更加直观的感受 一口君将之前写的一个小项目 本篇在该
  • 基于Vue的Excel文件预览(使用LuckyExcel、LuckySheet实现)

    目录 一 介绍 二 基本思路 三 安装 Luckyexcel Luckysheet CDN引入 本地引入 四 具体实现 1 首先需要一个页面 用于excel的预览展示 2 编写Excel解析渲染函数 openExcel url链接 文件名
  • 一文带你理解URI 和 URL 有什么区别?

    当我们打开浏览器 要访问一个网站或者一个ftp服务器的时候 一定要输入一串字符串 比如 https blog csdn net 或者 ftp 192 168 0 111 这样我们就可以得到一个html格式的页面或者一个文件 那么这个地址是什
  • Cocos Creator搞心态小游戏-闯关类 项目展示+完整项目源码

    游戏录像 Cocos Creator搞心态小游戏 闯关类 项目展示 游戏玩法 玩家通过按方向键控制角色移动 跳跃并躲避场景中的陷阱 角色触碰到陷阱会往后摔 直到角色到达摔到安全位置 功能 角色的基本移动前后移动 跳跃 角色触碰陷阱会往后摔
  • 聊一聊基础的CPU寄存器~

    寄存器 CPU内部的存储单元 用于存放从内存读取而来的数据 包括指令 和CPU运算的中间结果 使用寄存器来临时存放数据而不直接操作内存原因如下 CPU的工作原理决定了有些操作只能在CPU内部进行 CPU读写寄存器的速度比读写内存的速度要快很
  • Javascript组件化开发设计思想

    一 引言 项目中经常用web弹层组件 layer 其常见的代码如下 使用的时候很方便 弹窗的宽高 内容 标题 关闭按钮等弹窗的状态我们都可以通过配置参数配置 layer弹层组件用同一套代码来满足不同的弹窗层表现的需求 这便是组件开发的强大之
  • DOM之获取标签元素、属性和属性值

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

    学习不止 问答不止 一 粉丝问题 二 相关函数说明 1 函数说明 产生随机数的方法很多 常用的是rand srand 来看一下这2个函数的定义 SYNOPSIS include
  • 【问答21】C语言:位域和字节序

    1 粉丝问题 自己编写的一个协议相关代码 位域的值解析和自己想象的有出入 结构体的头 解析代码和测试结果 就是说通过函数hexdump 解析出的内存是十六进制是 81 83 20 3B 从数据帧解析出的 opcode 0x8 该粉丝不明白为
  • 【基于Cocos Creator实现的赛车游戏】9.实现汽车节点的控制逻辑

    转载知识星球 深度连接铁杆粉丝 运营高品质社群 知识变现的工具 项目地址 赛车小游戏 基于Cocos Creator 3 5版本实现 课程的源码 基于Cocos Creator 3 5版本实现 在上一节的课程中 您已经实现了通过触控给刚体施

随机推荐

  • 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 书写是对思维的缓存 佚名 进入正题 在游戏开发过程中经常会遇到渲染元素的尺寸大于你所能展示的区域 需要玩家自行进行操作 拖动以及点击 比如地图通常会超出玩家所示区域 这个时候需要一个能够