[cocos creator] - 触摸节点跟随鼠标移动

Cocos Creator 触摸节点跟随鼠标移动

使用鼠标或者手指(手机屏幕),按住节点并且可以随意拖动,节点不会被拖出屏幕外面,松开鼠标左键/手指,节点停止移动。

准备工作

创建 scene 文件夹用来保存场景,script 文件夹用来保存脚本文件,Canvas 大小为 960 * 640,创建一个 Background 单色节点作为背景,一个单色节点 Player 作为需要触摸移动的角色,在 script 文件夹下创建脚本文件 player.js

image.png

忽略此处 Player 节点的 group,这个是碰撞分组,本例不需要用到

触摸事件

编辑 player.js 文件,输入如下内容:

cc.Class({
    extends: cc.Component,

    properties: {
    },

    start() {
        // 绑定触摸移动事件
        this.node.on(cc.Node.EventType.TOUCH_MOVE, this.touchMoveEvent, this);
    },

    /**
     * 拖动节点事件
     * @param {*} event 
     */
    touchMoveEvent(event) {
        cc.log(event)
    },

});

运行游戏,鼠标拖动节点,查看控制台输出结果:

image.png

bubbles

表示该事件是否冒泡。

currentTarget

当前触摸对象节点。

currentTouch

当前触摸信息。

target

最初事件触发的目标。

touch

触摸事件。

eventPhase

事件阶段。

  • Event.NONE = 0
  • Event.CAPTURING_PHASE = 1
  • Event.AT_TARGET = 2
  • Event.BUBBLING_PHASE = 3

参照官方文档:Event Touch - v2.1 版

以下内容引用官方文档说明:

触摸事件(cc.Event.EventTouch)的重要 API 如下(cc.Event 标准事件 API 之外):

API 类型 描述
touch cc.Touch 与当前事件关联的触点对象
getID Number 获取触点的 ID,用于多点触摸的逻辑判断
getLocation Object 获取触点位置对象,对象包含 x 和 y 属性
getLocationX Number 获取触点的 X 轴位置
getLocationY Number 获取触点的 Y 轴位置
getPreviousLocation Object 获取触点上一次触发事件时的位置对象,对象包含 x 和 y 属性
getStartLocation Object 获取触点初始时的位置对象,对象包含 x 和 y 属性
getDelta Object 获取触点距离上一次事件移动的距离对象,对象包含 x 和 y 属性

添加触摸移动脚本

getLocationgetLocationXgetLocationY,这三个方法都是用来获取触摸位置的,返回的数据是世界坐标,我们需要把坐标转换成当前节点的坐标,编辑 player.js 脚本,输入以下内容:

cc.Class({
    extends: cc.Component,

    properties: {
    },

    start() {
        // 绑定触摸移动事件
        this.node.on(cc.Node.EventType.TOUCH_MOVE, this.touchMoveEvent, this);
    },

    /**
     * 拖动节点事件
     * @param {*} event 
     */
    touchMoveEvent(event) {
        // 获取触摸坐标
        let touchPosition = event.getLocation();
        // 转换为节点所在坐标系的坐标
        let nodePosition = this.node.parent.convertToNodeSpaceAR(touchPosition);
        // 设置节点坐标为触摸的坐标
        this.node.position = nodePosition;
    },
});

坐标系

世界坐标是以左下角为 (0,0),向右 x 轴自增,向上 y 轴自增的坐标系,所有的节点都有世界坐标,世界坐标是将所有的节点都放在同一个平面的坐标系,可以用来做节点位置的比较。

本地坐标是一个节点以父节点作为坐标系,在层级管理器中 Player 节点放置在 Background 节点下,所以 Player 节点的本地坐标是以父节点 Background 来计算的。

image.png

如果两个节点隶属于不同的父节点,那么就要将它们转化成世界坐标再进行比较,如果以各自的本地坐标来比较,结果是不准确的(除非将它们放在同一个父节点下)。

以物理学的角度来说,描述一个物体是否运动需要选择一个参照物,没有参照物就无法描述物体是运动还是相对静止。同理,坐标系也必须选择一个参考系,否则你不知道它相对于谁的位置

节点在坐标系中的点是根据锚点 Anchor 来定位的,也就是默认情况下最中心的那个点

上面的例子中:

// 转换为节点所在坐标系的坐标
let nodePosition = this.node.parent.convertToNodeSpaceAR(touchPosition);
// 设置节点坐标为触摸的坐标
this.node.position = nodePosition;

把触摸位置的世界坐标转化为 Background 节点下的坐标,也就是 Player 节点所在的坐标系,然后再设置为 Player 节点的位置,这样就没问题了。

现在我们已经可以用鼠标拖动节点进行移动了。

限制节点不能拖出屏幕外

上面的代码还存在一个问题,如果拖动节点到角落,会让节点的一部分消失在屏幕外面:

image.png

注!本例中没有修改任何节点的锚点,统一采用默认的锚点位置,如果修改了锚点,则需要修改计算方式,不推荐这么做。

我们希望限制节点只能在屏幕可见范围里,如果拖到屏幕外面应该阻止它继续移动,以节点移动到左侧为例,x 坐标应该为屏幕宽度的一半减去节点的宽度:

image.png

player.js 中修改:

cc.Class({
    extends: cc.Component,

    properties: {
    },

    start() {
        // 绑定触摸移动事件
        this.node.on(cc.Node.EventType.TOUCH_MOVE, this.touchMoveEvent, this);
    },

    /**
     * 拖动节点事件
     * @param {*} event 
     */
    touchMoveEvent(event) {
        // 当前节点
        let node = this.node;
        // 获取屏幕大小
        let windowSize = cc.view.getVisibleSize();
        // 获取触摸坐标
        let touchPosition = event.getLocation();
        // 转换为节点所在坐标系的坐标
        let nodePosition = this.node.parent.convertToNodeSpaceAR(touchPosition);
        // 水平方向最大距离
        let xPos = windowSize.width / 2;
        if (nodePosition.x - node.width / 2 <= -xPos) {
            nodePosition.x = node.width / 2 - xPos
        } else if (nodePosition.x + node.width / 2 >= xPos) {
            nodePosition.x = xPos - node.width / 2;
        }
        // 竖直方向最大距离
        let yPos = windowSize.height / 2;
        if (nodePosition.y - node.height / 2 <= -yPos) {
            nodePosition.y = node.height / 2 - yPos
        } else if (nodePosition.y + node.height / 2 >= yPos) {
            nodePosition.y = yPos - node.height / 2;
        }
        node.position = nodePosition;

    },
});

现在,节点已经不会被拖到屏幕外面了。

然而,代码虽然能跑通,写的却不够规范,核心的逻辑是怎么处理的也没加注释,别说以后的接盘侠一脸懵逼,自己看着就很蛋疼,写完代码连自己都不想看系列

代码规范

不能抱着写完功能就了事的想法,通过观察可以发现 touchMoveEvent 方法写得很长,通常来说,一个方法如果超过几十行肯定是包含了过多的业务逻辑,我们可以把里面的业务逻辑抽离出来,单独作为一个方法处理。

首先是 if-else 结构,这是写代码最为致命的一个地方,过多的 if-else 辣眼睛!抽抽抽出来!

原来的代码:

// 水平方向最大距离
let xPos = windowSize.width / 2;
if (nodePosition.x - node.width / 2 <= -xPos) {
    nodePosition.x = node.width / 2 - xPos
} else if (nodePosition.x + node.width / 2 >= xPos) {
    nodePosition.x = xPos - node.width / 2;
}
// 竖直方向最大距离
let yPos = windowSize.height / 2;
if (nodePosition.y - node.height / 2 <= -yPos) {
    nodePosition.y = node.height / 2 - yPos
} else if (nodePosition.y + node.height / 2 >= yPos) {
    nodePosition.y = yPos - node.height / 2;
}

将业务逻辑抽离出来,分成两个方法:

/**
 * 获取x坐标
 * @param {integer} nodePosition 
 */
getNodePositionX(nodePositionX) {
    let node = this.node;
    // 获取屏幕大小
    let windowSize = cc.view.getVisibleSize();
    // 节点一半的宽度
    let nodeHalfWidth = node.width / 2;
    // x坐标值,屏幕宽度的一半,左侧为负数,右侧为正数
    let xPos = windowSize.width / 2;
    if (nodePositionX - nodeHalfWidth <= -xPos) {
        nodePositionX = nodeHalfWidth - xPos
    } else if (nodePositionX + nodeHalfWidth >= xPos) {
        nodePositionX = xPos - nodeHalfWidth;
    }

    return nodePositionX;
},

/**
 * 获取y坐标
 * @param {integer} nodePositionY 
 */
getNodePositionY(nodePositionY) {
    let node = this.node;
    // 获取屏幕大小
    let windowSize = cc.view.getVisibleSize();
    // 节点一半的高度
    let nodeHalfHeight = node.height / 2;
    // 竖直方向的值,屏幕高度的一半,上方是正数,下方是负数
    let yPos = windowSize.height / 2;
    if (nodePositionY - nodeHalfHeight <= -yPos) {
        nodePositionY = nodeHalfHeight - yPos
    } else if (nodePositionY + nodeHalfHeight >= yPos) {
        nodePositionY = yPos - nodeHalfHeight;
    }

    return nodePositionY;
}

然后在 touchMoveEvent 中直接调用这两个方法:

/**
 * 拖动节点事件
 * @param {*} event 
 */
touchMoveEvent(event) {
    // 当前节点
    let node = this.node;
    // 获取触摸坐标
    let touchPosition = event.getLocation();
    // 转换为节点所在坐标系的坐标
    let nodePosition = this.node.parent.convertToNodeSpaceAR(touchPosition);
    // 水平方向最大距离
    nodePosition.x = this.getNodePositionX(nodePosition.x);
    // 竖直方向最大距离
    nodePosition.y = this.getNodePositionY(nodePosition.y);
    // 设置节点位置
    node.position = nodePosition;
},

完整版:

cc.Class({
    extends: cc.Component,

    properties: {
    },

    start() {
        // 绑定触摸移动事件
        this.node.on(cc.Node.EventType.TOUCH_MOVE, this.touchMoveEvent, this);
    },

    /**
     * 拖动节点事件
     * @param {*} event 
     */
    touchMoveEvent(event) {
        // 当前节点
        let node = this.node;
        // 获取触摸坐标
        let touchPosition = event.getLocation();
        // 转换为节点所在坐标系的坐标
        let nodePosition = this.node.parent.convertToNodeSpaceAR(touchPosition);
        // 水平方向最大距离
        nodePosition.x = this.getNodePositionX(nodePosition.x);
        // 竖直方向最大距离
        nodePosition.y = this.getNodePositionY(nodePosition.y);
        // 设置节点位置
        node.position = nodePosition;
    },

    /**
     * 获取x坐标
     * @param {integer} nodePosition 
     */
    getNodePositionX(nodePositionX) {
        let node = this.node;
        // 获取屏幕大小
        let windowSize = cc.view.getVisibleSize();
        // 节点一半的宽度
        let nodeHalfWidth = node.width / 2;
        // x坐标值,屏幕宽度的一半,左侧为负数,右侧为正数
        let xPos = windowSize.width / 2;
        if (nodePositionX - nodeHalfWidth <= -xPos) {
            nodePositionX = nodeHalfWidth - xPos
        } else if (nodePositionX + nodeHalfWidth >= xPos) {
            nodePositionX = xPos - nodeHalfWidth;
        }

        return nodePositionX;
    },

    /**
     * 获取y坐标
     * @param {integer} nodePositionY 
     */
    getNodePositionY(nodePositionY) {
        let node = this.node;
        // 获取屏幕大小
        let windowSize = cc.view.getVisibleSize();
        // 节点一半的高度
        let nodeHalfHeight = node.height / 2;
        let yPos = windowSize.height / 2;
        if (nodePositionY - nodeHalfHeight <= -yPos) {
            nodePositionY = nodeHalfHeight - yPos
        } else if (nodePositionY + nodeHalfHeight >= yPos) {
            nodePositionY = yPos - nodeHalfHeight;
        }

        return nodePositionY;
    },
});

至此才全部 ok!

讨论

还没有人评论~