Cocos Creator 触摸节点跟随鼠标移动
使用鼠标或者手指(手机屏幕),按住节点并且可以随意拖动,节点不会被拖出屏幕外面,松开鼠标左键/手指,节点停止移动。
准备工作
创建 scene
文件夹用来保存场景,script
文件夹用来保存脚本文件,Canvas
大小为 960 * 640
,创建一个 Background
单色节点作为背景,一个单色节点 Player
作为需要触摸移动的角色,在 script
文件夹下创建脚本文件 player.js
:
忽略此处 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)
},
});
运行游戏,鼠标拖动节点,查看控制台输出结果:
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 属性 |
添加触摸移动脚本
getLocation
、getLocationX
、getLocationY
,这三个方法都是用来获取触摸位置的,返回的数据是世界坐标,我们需要把坐标转换成当前节点的坐标,编辑 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
来计算的。
如果两个节点隶属于不同的父节点,那么就要将它们转化成世界坐标再进行比较,如果以各自的本地坐标来比较,结果是不准确的(除非将它们放在同一个父节点下)。
以物理学的角度来说,描述一个物体是否运动需要选择一个参照物,没有参照物就无法描述物体是运动还是相对静止。同理,坐标系也必须选择一个参考系,否则你不知道它相对于谁的位置。
节点在坐标系中的点是根据锚点 Anchor 来定位的,也就是默认情况下最中心的那个点
上面的例子中:
// 转换为节点所在坐标系的坐标
let nodePosition = this.node.parent.convertToNodeSpaceAR(touchPosition);
// 设置节点坐标为触摸的坐标
this.node.position = nodePosition;
把触摸位置的世界坐标转化为 Background
节点下的坐标,也就是 Player
节点所在的坐标系,然后再设置为 Player
节点的位置,这样就没问题了。
现在我们已经可以用鼠标拖动节点进行移动了。
限制节点不能拖出屏幕外
上面的代码还存在一个问题,如果拖动节点到角落,会让节点的一部分消失在屏幕外面:
注!本例中没有修改任何节点的锚点,统一采用默认的锚点位置,如果修改了锚点,则需要修改计算方式,不推荐这么做。
我们希望限制节点只能在屏幕可见范围里,如果拖到屏幕外面应该阻止它继续移动,以节点移动到左侧为例,x 坐标应该为屏幕宽度的一半减去节点的宽度:
在 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!