CocosCreator: 实现斗地主滑动选择多张牌

如题,要实现这个功能,首先我们需要先了解下的CocosCreator中的触摸事件传递的机制。

触摸事件冒泡

触摸事件支持节点树的事件冒泡,以下图为例:

在图中的场景里,假设 A 节点拥有一个子节点 B,B 拥有一个子节点 C。开发者对 A、B、C 都监听了触摸事件(以下的举例都默认节点监听了触摸事件)。

当鼠标或手指在 C 节点区域内按下时,事件将首先在 C 节点触发,C 节点监听器接收到事件。接着 C 节点会将事件向其父节点传递这个事件,B 节点的监听器将会接收到事件。同理 B 节点会将事件传递给 A 父节点。这就是最基本的事件冒泡过程。需要强调的是,在触摸事件冒泡的过程中不会有触摸检测,这意味着即使触点不在 A B 节点区域内,A B 节点也会通过触摸事件冒泡的机制接收到这个事件。

触摸事件的冒泡过程与普通事件的冒泡过程并没有区别。所以,调用 event.stopPropagation() 可以主动停止冒泡过程。

同级节点间的触点归属问题

假设上图中 B、C 为同级节点,C 节点部分覆盖在 B 节点之上。这时候如果 C 节点接收到触摸事件后,就宣布了触点归属于 C 节点,这意味着同级节点的 B 就不会再接收到触摸事件了,即使触点同时也在 B 节点内。同级节点间,触点归属于处于顶层的节点。

此时如果 C 节点还存在父节点,则还可以通过事件冒泡的机制传递触摸事件给父节点。

这一点就是我们所需要注意的,由于牌组中的牌都处于同一层,因此不能直接监听每一张牌的触摸事件,这样触摸点归属一张牌之后,其他牌就无法收到事件传递,因此不能监听每张牌的触摸事件,需要一个触摸层,用来接受触摸事件,通过判断触摸点是否落在卡牌上。

原理

如上图,由于每张牌都要按照一个固定位移差排列着,因此检查触摸范围时,只有最后一张卡牌的触摸范围是整张牌范围,其他牌的触摸范围只要检查显示的部分。

不管触摸点起始位置时在触摸终点的左边还是右边,只要判断卡牌的显示部分处于起始点和终点之间就是被选中的状态

实现

1,创建一个卡牌视图类,用来控制卡牌被选中,挂起和复位效果,代码如下:

typescript 复制代码
export default class CardView {    
    ...
    /** 原始位置 */
    private _orginPos: cc.Vec3 = cc.v3();

    /** 是否被选中 */
    private _isSelect: boolean = false;
    public get isSelect (): boolean {
        return this._isSelect;
    }

    public set isSelect (isSelect: boolean) {
        this._isSelect = isSelect;
        this.selectStateImg.active = isSelect;
    }

    /** 是否挂起 */
    private _isUp: boolean = false;
    public isUp (): boolean {
        return this._isUp;
    }
    
    /** 牌之间的间隔 */
    private _interval: number = 0;
    public set interval (interval: number) {
        this._interval = interval;
    }

    public get interval (): number {
        return this._interval;
    }
    
    /** 被选中后,挂起的牌复位,原位的牌挂起 */
    public checkUp () {
        if (this._isUp) {
            this.unselectAction();
        } else {
            this.selectAction();
        }
    }

    /** 被选中后挂起动画 */
    public selectAction () {
        if (this._isUp) {
            return;
        }
        cc.tween(this.node)
            .to(0.1, { position: cc.v3(this.node.x, this._orginPos.y + 20, 0) }, { easing: 'sineIn'})
            .call(() => {
                this._isUp = true;
            })
            .start();
    }

    /** 复位动画 */
    public unselectAction () {
        if (!this._isUp) {
            return;
        }
        cc.tween(this.node)
            .to(0.1, { position: cc.v3(this.node.x, this._orginPos.y, 0) }, { easing: 'sineIn'})
            .call(() => {
                this._isUp = false;
            })
            .start();
    }
}

2, 创建触摸层, 用来监听触摸事件

typescript 复制代码
xport default class CardPanelView extends EventComponent {

    @inject("playerPanel1", cc.Node)
    playerPanel1: cc.Node = null;

    /** 根视图 */
    private _gameView: UIView = null;
    public set gameView (view: UIView) {
        this._gameView = view;
    }

    /** 创建的牌组 */
    private _cardViews: cc.Node[] = [];

    /** 触摸点起始位置 */
    private _startPos: cc.Vec2 = null;
    /** 触摸到牌外之前的坐标 */
    private _moveEndPos: cc.Vec2 = null;

    /** 触摸起始位置是否触摸到牌 */
    private _isTouchedInCard: boolean = false;

    onLoad(): void {
        super.onLoad();
    }

    /** 监听触摸事件 */
    addEvents(): void {
        this.onN(this.node, cc.Node.EventType.TOUCH_START, this._touchStart);
        this.onN(this.node, cc.Node.EventType.TOUCH_MOVE, this._touchMove);
        this.onN(this.node, cc.Node.EventType.TOUCH_END, this._touchEnd);
    }

    init() {
        this.initCards();
    }

    /** 初始化牌组 */
    initCards () {
        const cards = [0x1A, 0x1A, 0x1B, 0x1B, 0x1E, 0x1E, 0x1E, 0x1F, 0x1F, 0x01, 0x02];
        createPrefab({
            url: DDZ_PREFAB_URL[0],
            view: this._gameView,
            complete: (node) => {
                for (let i = 0; i < cards.length; i++) {
                    const node1 = cc.instantiate(node);
                    this.node.addChild(node1);
                    node1.x = setCardPositionX(cards.length-1, i, node1.width, 60);
                    node1.y = this.playerPanel1.y;
                    const cardView1 = node1.addComponent(CardView); 
                    cardView1.gameView = this._gameView;
                    cardView1.interval = 60;
                    cardView1.value = cards[i];

                    this._cardViews.push(node1);
                }
            }
        });
    }

    private _touchStart (ev : cc.Event.EventTouch) {
        this._startPos = ev.getLocation();
        this._moveEndPos = this._startPos;
        this._isTouchedInCard = this._isTouchedCard(this._startPos);
    }

    private _touchMove (ev : cc.Event.EventTouch) {
        /** 起始触摸点在牌组范围外,不进行触摸检测 */
        if (!this._isTouchedInCard) return;

        /** 触摸点移动到牌组范围外,就不继续进行新的碰撞检测 */
        const currentPos = ev.getLocation();
        const isCurTouchedCard = this._isTouchedCard(currentPos);
        if (!isCurTouchedCard) {
            return;
        }

        this._moveEndPos = currentPos;


        /** 碰撞检测,检测到牌显示部分在起始和终点范围之间,则为选中状态,否则为选中 */
        this._cardViews.forEach((card, index, arr) => {
            const cardRect = card.getBoundingBoxToWorld();
            const cardView = card.getComponent(CardView);
            cardRect.width = index === arr.length - 1 ? cardRect.width : cardView.interval;
            if (this._isCardInSelectionRange(cardRect, this._startPos, this._moveEndPos)) {
                cardView.isSelect = true;
            } else {
                cardView.isSelect = false;
            }
        });
    }

    private _touchEnd (ev : cc.Event.EventTouch) {
        const currentPos = ev.getLocation();

        // 选中牌意外的区域,所有牌复位
        const _isEndTouchedInCard = this._isTouchedCard(currentPos);
        if (!this._isTouchedInCard && !_isEndTouchedInCard) {
            this._cardViews.forEach((card, index, arr) => {
                const cardView = card.getComponent(CardView);
                if (cardView.isSelect)
                    cardView.isSelect = false;
                if (cardView.isUp) {
                    cardView.unselectAction();
                }
            });
            return;
        }
        if (!this._isTouchedInCard) return;

        // 选中牌区域,挂起的牌复位,原始位置的牌挂起
        this._cardViews.forEach((card, index, arr) => {
            const cardRect = card.getBoundingBoxToWorld();
            const cardView = card.getComponent(CardView);
            cardRect.width = index === arr.length - 1 ? cardRect.width : cardView.interval;
            
            if (this._isCardInSelectionRange(cardRect, this._startPos, this._moveEndPos)) {
                cardView.checkUp();
            }

            if (cardView.isSelect)
                cardView.isSelect = false;
        });
    }

    /** 判断牌显示区域是否在两个点范围内 */
    private _isCardInSelectionRange(cardRect: cc.Rect, startPos: cc.Vec2, currentPos: cc.Vec2): boolean {
        const minX = Math.min(startPos.x, currentPos.x);
        const maxX = Math.max(startPos.x, currentPos.x);
        const minY = Math.min(startPos.y, currentPos.y);
        const maxY = Math.max(startPos.y, currentPos.y);

        if (cardRect.x + cardRect.width < minX || cardRect.x > maxX || cardRect.y + cardRect.height < minY || cardRect.y > maxY) {
            return false;
        }

        return true;
    }

    /** 判断是否在牌组内 */
    private _isTouchedCard (targetPos: cc.Vec2) {
        let isTouchedCard = false;
        this._cardViews.forEach((card, index, arr) => {
            const cardRect = card.getBoundingBoxToWorld();
            const cardView = card.getComponent(CardView);
            cardRect.width = index === arr.length - 1 ? cardRect.width : cardView.interval;
            
            if (this._isCardInSelectionRange(cardRect, targetPos, targetPos)) {
                isTouchedCard = true;
            }
        });
        return isTouchedCard;
    }
}

代码就不多解释了,看注释。 这样就基本可以实现多选卡牌的功能,效果如下:

相关推荐
布鲁克零三四四18 天前
Cocos Creator导出obj文件用于后端寻路
cocos creator
烧仙草奶茶22 天前
【cocos creator】输入框滑动条联动小组建
cocos creator·cocos-creator
烧仙草奶茶3 个月前
【cocos creator】2.x里,使用3D射线碰撞检测
3d·cocos creator·cocos-creator·2.x
仅此而已7293 个月前
Cocos Creator倒计时
游戏引擎·cocos creator
仅此而已7293 个月前
cocosUI多分辨率适配
游戏引擎·cocos creator·多分辩率适配
SilenceJude3 个月前
cocos creator 3学习记录01——如何替换图片
前端·cocos creator
GrimRaider4 个月前
[Cocos Creator] v3.8开发知识点记录(持续更新)
开发·cocos creator
S_clifftop5 个月前
cocos creator如何使用cryptojs加解密(及引入方法)
cocos creator·加密解密·cryptojs
平淡风云5 个月前
cocosCreator获取手机剪切板内容
java·智能手机·typescript·android studio·cocos creator
zhenmu5 个月前
【cocos creator 3.x】 修改builtin-unlit 加了一个类似流光显示的mask参数
cocos creator·shader·effect