关于 drag & drop 操作的实现

cc.Node emit 的Touch 事件有 Begin,Move,End,Cancel。
拖拽 Drag 操作可以使用 Begin 和 Move 来触发。

我想实现 Drop 操作(类似背包中拖拽起一个件物品,放置到空物品槽)。

根据测试,在Node_A 上TouchBegin,手指不松,Move 到 Node_B 区域内 再松手,无法触发Node_B 的 TouchEnd 和 TouchCancel 事件。
所以背包拖拽,换位置的操作无法实现。

有没有其他实现方案?

你用的是什么版本?我这边是没有问题的,你还是先检查一下,或者把代码贴出来看看

使用的 2.0.5

// Learn cc.Class:
// - [Chinese] http://docs.cocos.com/creator/manual/zh/scripting/class.html
// - [English] http://www.cocos2d-x.org/docs/creator/en/scripting/class.html
// Learn Attribute:
// - [Chinese] http://docs.cocos.com/creator/manual/zh/scripting/reference/attributes.html
// - [English] http://www.cocos2d-x.org/docs/creator/en/scripting/reference/attributes.html
// Learn life-cycle callbacks:
// - [Chinese] http://docs.cocos.com/creator/manual/zh/scripting/life-cycle-callbacks.html
// - [English] http://www.cocos2d-x.org/docs/creator/en/scripting/life-cycle-callbacks.html

cc.Class({
extends: cc.Component,

ctor: function () {
    this._dragTesting = false;
    this._inDrag = false;
},

properties: {
    dragable: {
        default: true,
    },

    dropable: {
        default: true,
    },

    dragStartEvents: {
        default: [],
        type: cc.Component.EventHandler,
    },


    dragStopEvents: {
        default: [],
        type: cc.Component.EventHandler,
    },

    dropEvents: {
        default: [],
        type: cc.Component.EventHandler,
    },

    interactable: {
        default: true,
    }
},

start() {

},

onEnable() {
    this.node.on(cc.Node.EventType.TOUCH_START, this._onTouchBegan, this);
    this.node.on(cc.Node.EventType.TOUCH_MOVE, this._onTouchMove, this);
    this.node.on(cc.Node.EventType.TOUCH_END, this._onTouchEnded, this);
    this.node.on(cc.Node.EventType.TOUCH_CANCEL, this._onTouchCancel, this);
},

onDisable() {
    this.node.off(cc.Node.EventType.TOUCH_START, this._onTouchBegan, this);
    this.node.off(cc.Node.EventType.TOUCH_MOVE, this._onTouchMove, this);
    this.node.off(cc.Node.EventType.TOUCH_END, this._onTouchEnded, this);
    this.node.off(cc.Node.EventType.TOUCH_CANCEL, this._onTouchCancel, this);
},

_onTouchBegan(event) {
    if (!this.interactable || !this.enabledInHierarchy) return;
    if(this.dragable) {
        this._dragTesting = true;
    }
    // cc.log(`[drag] start testing`);
    event.stopPropagation();
},

_onTouchMove(event) {
    if (!this.interactable || !this.enabledInHierarchy) return;

    if(this._dragTesting && !this._inDrag) {
        if(this.dragable){
            let start = event.getStartLocation();
            let cur = event.getLocation();
            cur.subSelf(start);
            if (cur.magSqr() > 20) {
                cc.Component.EventHandler.emitEvents(this.dragStartEvents, event);
                this.node.emit('drag', this);
                this._inDrag = true;
                cc.log(`[drag] start drag`);
            }
        }
    }
    event.stopPropagation();
},

_onTouchEnded(event) {
    cc.log(`touch end`);
    if (!this.interactable || !this.enabledInHierarchy) return;

    this._dragTesting = false;
    if (this._inDrag) {
        cc.Component.EventHandler.emitEvents(this.dragStopEvents, event);
        this.node.emit('stop', this);
        cc.log(`[drag] stop drag`);
    }

    if(this.dropable) {
        cc.Component.EventHandler.emitEvents(this.dropEvents, event);
        this.node.emit('drop', this);
        cc.log(`[drop]`);
    }

    this._inDrag = false;

    event.stopPropagation();
},

_onTouchCancel(event) {
    if (!this.interactable || !this.enabledInHierarchy) return;

    if (this._inDrag) {
        cc.Component.EventHandler.emitEvents(this.dragStopEvents, event);
        this.node.emit('stop', this);
        cc.log(`[drag] stop drag`);
    }
    this._inDrag = false;
    event.stopPropagation();
}

});

这是我的脚本, 挂载到两个 Node 上,从 A 拖到 B,松开,touch end 是打印不出来的

没有实际写过这部分代码 touchEnd是在Node范围内离开才会触发的 touchCancel是Node范围外离开触发 你可以用touchCancel监听,判断手指离开位置是否存在其它节点。或者用碰撞检测来判断是否碰撞到其它节点。

你的层级是什么样的?A在上面还是B在上面

没有层级关系,我新建的New Project 两个Node都放在 canvas 下。

所有你是可以 Touch Begin 从 A触发,到 B 触发 TouchEnded 吗?

这是 engine 里 CCEventManager.js 代码,
只有在监听的 Node 上触发 begin,才会在 line 499 行 listener._claimedTouches.push(selTouch);
这样这个 Node 之后的 touchEnd 和 Cancel 才会触发。 (line 501, 502) 分支

因为 Touch Begin 从 NodeA 触发, NodeB 的touch End 在 同一 Touch 移动到 NodeB 上松手, NodeB 不会触发 TouchEnd 事件。

没说nodea事件可以在nodeb触发,你用了event.stopPropagation();所以如果你没有设置zindex,正好B是在A之后创建的,有可能是接受不到的

所以要么你把A的zindex设置大一点,要不A在B之后创建

用Cancel事件不行吗

找到了解决办法
如果有人也需要这个功能,可以参考下。

NodeA 触发拖拽后 当 TouchEnd 和 TouchCancel,触发 NodeA的drop事件,用全局消息 cc.systemEvent.emit(“drop”, event) 往外分发事件。

NodeB 监听全局的 drop 事件,然后 this.node._hitTest() 检测手指松开的点是否在自己的区域内,如果成功就知道有东西drop了。

你有一个例子吗?

因为触发的是a的toucancle事件!

这样的事情可以做到

https://shopify.github.io/draggable/examples/multiple-containers.html

._hitTest() ;这个方法怎么找不到啊?

hitTest 这是 CCNode.js 里面没有开放API的方法

请问咋调用呢,刚刚接触creator