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;
    }
}

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

相关推荐
VaJoy17 天前
Cocos Creator Shader 入门 ⑺ —— 图层混合样式的实现与 Render Texture
cocos creator
VaJoy19 天前
Cocos Creator Shader 入门 ⑹ —— 灰阶、反色等滤镜的实现
cocos creator
VaJoy21 天前
Cocos Creator Shader 入门 ⑸ —— 代码复用与绿幕抠图技术
cocos creator
VaJoy23 天前
Cocos Creator Shader 入门 ⑷ —— 纹理采样与受击闪白的实现
cocos creator
VaJoy24 天前
Cocos Creator Shader 入门 ⑶ —— 给节点设置透明度
cocos creator
VaJoy1 个月前
Cocos Creator Shader 入门 (2) —— 给节点染色
cocos creator
VaJoy1 个月前
Cocos Creator Shader —— 附录
cocos creator
成长ing121381 个月前
多层背景视差滚动Parallax Scrolling
cocos creator
似水流年wxk1 个月前
cocos creator使用jenkins打包微信小游戏,自动上传资源到cdn,windows版运行jenkins
运维·jenkins·cocos creator
成长ing121383 个月前
点击音效系统
前端·cocos creator