[Cocos Creator] 踩坑日记(三)-路径移动问题及为其添加Promise

时间:2024-03-16 07:15:26

背景:一个人物模型Actor与一张地图map,在地图上人物随意移动,比如能从A点到B点,或A点到B点过程中用户点击C点,那么改变移动方向到C点,在移动结束后执行其它人物动作Action

[Cocos Creator] 踩坑日记(三)-路径移动问题及为其添加Promise

路径移动

首先我们能联想到的是cc.moveBy或cc.moveTo实现一格的移动,通过给出的路径path[ point1 , point2, ..., B]到达B点。

1. 生成路径path: generatePath(A, B, map?)(函数实现略)

2. 添加路径并移动: Actor.move(path)

    move(path){
        if(path == null || path == undefined || path.length == 0 )
            return;
        else{
            this.path = path;
            this._move(path);
        }
}

3. 一格移动: Actor._move(point)

    _move(path){
        // path = [ point1 , point2, ..., B]
        if(this.moveActions!=null && !this.moveActions.isDone()) {
            // 上个动作没有完成,记录下path等之后完成
            this.path = path;
            return;
        }else{
            this.moveActions = new cc.ActionInterval();
            let nextSite = this.path.splice(0, 1)[0];
            if(nextSite){
                // 换算成网格移动所需要的x, y
                let x = (nextSite.x - this.actor.x) * this.node.width,
                y = - (nextSite.y - this.actor.y) * this.node.height;
                // cc.moveBy移动一格,通过cc.sequence与cc.callFunc组合,实现移动一格结束后的回调
                this.moveActions = cc.sequence(cc.moveBy(MOVE_SPEED, x, y), this.moveActions);
                this.moveActions = cc.sequence(this.moveActions, cc.callFunc(this._moveFinished, this, nextSite));
                this.node.runAction(this.moveActions);
            }
        }
    }

    _moveFinished(targetNode, nextSite){
        //移动一格,更新自己的位置
        this.actor.x = nextSite.x;
        this.actor.y = nextSite.y;
        //目标路径没有移动完,继续移动
        if(this.path && this.path.length){
            this._move(this.path);
        }else{
            cc.log('Promise: move is done');
        }
}


路径1:用户点击B点,生成path(A->B),调用Actor.move(path),_move移动到B点结束

路径2:用户点击B点,生成path(A->B),调用Actor.move(path(A->B)),_move向着B点移动;中途用户再次点击C点,生成当前位置到C点的路径path(_point->C),调用Actor.move(path(_point->C)),_move改变方向向着C点移动。

添加Promise

关于在人物移动结束后还要执行的Action动作,则需要为整个移动添加一个promise了:

    move(path){
        let promise = new Promise((resolve, reject)=>{
            if(path == null || path == undefined || path.length == 0 )
                reject('path is null');
            else{
                this.path = path;
                this._move(path, resolve);
            }
        });
        return promise;
    }
    _move(path, resolve){
        if(this.moveActions!=null && !this.moveActions.isDone()) {
            // 上个动作没有完成,记录下path等之后完成
            this.path = path;
            return;
        }else{
            this.moveActions = new cc.ActionInterval();
            let nextSite = this.path.splice(0, 1)[0];
            if(nextSite){
                let x = (nextSite.x - this.actor.x) * this.node.width,
                y = - (nextSite.y - this.actor.y) * this.node.height;
                // cc.moveBy移动一格,通过cc.sequence与cc.callFunc组合,实现移动一格结束后的回调
                this.moveActions = cc.sequence(cc.moveBy(MOVE_SPEED, x, y), this.moveActions);
                // cc.callFunc回调里加入resolve作为参数
         this.moveActions = cc.sequence(this.moveActions, cc.callFunc(this._moveFinished, this, {nextSite, resolve}));
                this.node.runAction(this.moveActions);
            }
        }
    }
    _moveFinished(targetNode, {nextSite, resolve}){
        this.actor.x = nextSite.x;
        this.actor.y = nextSite.y;

        if(this.path && this.path.length){
            this._move(this.path, resolve);
        }else{
            cc.log('Promise: move is done');
            resolve('');
        }
    }

以上加入了promise的丑陋的代码看似完成了完成动作后的回调,你只需要

Actor.move(path).then(()=>{

//这里写下一个动作

})

就能在move结束后执行其它相关操作了。

但但但但但但 是:

对于路径二,在用户第二次点击改变path的同时,_move方法会执行第一个if判断:

_move(path, resolve){
    if(this.moveActions!=null && !this.moveActions.isDone()) {
        // 上个动作没有完成,记录下path等之后完成
        this.path = path;
        return;
    }

也就是说,路径二的promise几乎永远不会被resolve,而
cc.callFunc(this._moveFinished, this, {nextSite, resolve})

中的resolve还是从A->B时的promise的resolve,因为它被闭包了。

调整代码,加入this.movePromise记录当前promise,使resolve从闭包中脱离:

    move(path){
        let promise = new Promise((resolve, reject)=>{
            if(this.movePromise){
                // 上个未完成动作会被直接reject
                this.movePromise.reject('');
                cc.log('Move Promise reject');
            }
            this.movePromise = {resolve, reject};
            if(path == null || path == undefined || path.length == 0 )
                reject('path is null');
            else{
                this.path = path;
                this._move(path);
            }
        });
        return promise;
    }
    _move(path){
        if(this.moveActions!=null && !this.moveActions.isDone()) {
            this.path = path;
            return;
        }else{
            this.moveActions = new cc.ActionInterval();
            let nextSite = this.path.splice(0, 1)[0];
            if(nextSite){
                let x = (nextSite.x - this.actor.x) * this.node.width,
                y = - (nextSite.y - this.actor.y) * this.node.height;
                this.moveActions = cc.sequence(cc.moveBy(MOVE_SPEED, x, y), this.moveActions);
                this.moveActions = cc.sequence(this.moveActions, cc.callFunc(this._moveFinished, this, {nextSite}));
                this.node.runAction(this.moveActions);
            }
        }
    }
    _moveFinished(targetNode, {nextSite}){
        this.actor.x = nextSite.x;
        this.actor.y = nextSite.y;
        if(this.path && this.path.length){
            this._move(this.path);
        }else{
            cc.log('Promise: move is done');
            this.movePromise.resolve('');
            this.movePromise = null;
        }
    }

这下就能开开心心的使用move了

Actor.move(path).then(()=>{

//这里写下一个动作

})