基于CocosCreator的切水果小游戏(三)

        在前面完成了基本的各种功能逻辑编写后,剩下的就主要是游戏得分的相关控制,以及新加了切水果时的刀划过的效果,炸弹爆炸时的光线、白屏效果等等。另外在文章最后面,放上了游戏所有代码以及下载链接。

        首先这是整体水果切开的效果gif图。(mac下用腾讯的jietu来制作的gif,感觉还是蛮舒适好用的。)

        主要实现也比较简单,就是在切开的同时,判断一下划过的角度,再放上图片,让其旋转就可以了。水果被切开时的相关代码如下:

    

// start.js

onTouchMove(event){
    //...
    // 刀光以及相关audio
        let lenV = this.curent_pos.sub(this.prev_pos).mag(); 
        let roateV = 0;
        let falsh_angle = this.curent_pos.sub(this.prev_pos).signAngle(cc.v2(1,0)) / Math.PI * 180;

        if(lenV > this.knife_height){
            let tempVec = cc.v2(0,10)
            roateV = this.curent_pos.sub(this.prev_pos).signAngle(tempVec) / Math.PI * 180
            // 
            let end_pooledKnife = this.getPooledKnife();
            if(end_pooledKnife != null){
                this.node.addChild(end_pooledKnife);
                end_pooledKnife.height = lenV;
                end_pooledKnife.setPosition((this.sx+this.ex)/2-this.node_w/2,(this.sy+this.ey)/2-this.node_h/2);
                end_pooledKnife.angle = -roateV;
            }
            
            
            this.prev_pos = this.curent_pos;
            this.sx = this.ex;
            this.sy = this.ey;

            if(!this.throw_audio_id || this.throw_audio_id && cc.audioEngine.getState(this.throw_audio_id)!=1)
                this.throw_audio_id = cc.audioEngine.playEffect(this.throw_audio,false);
            
        }

    //...
    // 对水果和炸弹被切开时的处理
        this.pooled_fruits.forEach(e => {
            if(e && !e.isAvailable
                && e.fruit_all!=null 
                && (( e.fruit_all.getComponent('cc.PolygonCollider') && cc.Intersection.pointInPolygon(this.curent_pos, e.fruit_all.getComponent('cc.PolygonCollider').world.points) )
                     || ( e.fruit_all.getComponent('cc.CircleCollider') && cc.Intersection.polygonCircle([this.curent_pos], e.fruit_all.getComponent('cc.CircleCollider').world) ))){
                        // cc.log("hit");
                        if(e.fruit_type != "boom"){
                            cc.audioEngine.playEffect(this.splatter_audio,false);

                            this.game_score++;
                            this.game_start_need[this.game_start_need_name.indexOf('score_label')].getComponent('cc.Label').string = this.game_score;
                        
                            var flash = cc.instantiate(this.flash_prefab);
                            flash.position = e.fruit_all.position;
                            flash.rotation = falsh_angle;
                            this.node.addChild(flash);
                            setTimeout(function(){
                                this.node.removeChild(flash);
                            }.bind(this),100);

                            e.apart();
                        }
                        else{
                            e.apart();
                            this.boomIsBoom(e,e.fruit_all.x + this.node_w/2,e.fruit_all.y + this.node_h/2);
                        }
                        
                        
                     }
        });

//... 
}

        首先是先计算下切开时刀划过的角度(方向),同之前所说的刀光制作的原理一样,通过位置向量相减的方式,再求出与水平向量的夹角角度即可。

        接着是处理切开碰撞,这里面之所以有两个判断碰撞的条件,是因为我对sandia和banana水果是加了多边形的碰撞组件,其它的是用圆形碰撞组件,所以写了两个,不过我看cocos源码中的圆形碰撞组件的碰撞处理其实也是通过pointInPolygon函数来处理的,所以也可以自己整理写一个。还有就是cocos中的angle和rotation好像是相反的,写的时候要注意一下。

        然后就是炸弹爆炸时的特效了,也就是上面代码中的this.boomIsBoom(),做出来大概长这样:

        

        主要实现是通过setTimeout函数,先生成炸弹的光线,然后再生成一片透明度逐渐降低的白色遮罩。相关代码如下:

//start.js 

//cc Class 类里面
cc.Class({
    // onload update onTouchMove ... 
    // ...

    //boom light 用于下面的第一种方法
    createLight(x,y,every_angle,index){
        let light_len = 1074;
        let light_left_angle = Math.PI * (index * every_angle - 2.5) / 180;
        let light_right_angle = Math.PI * (index * every_angle + 2.5) / 180;
        let light_left_x = x + light_len * Math.cos(light_left_angle);
        let light_left_y = y + light_len * Math.sin(light_left_angle);
        let light_right_x = x + light_len * Math.cos(light_right_angle);
        let light_right_y = y + light_len * Math.sin(light_right_angle);

        // cc.log(x,y);
        let paint = cc.instantiate(this.paint)
        this.node.addChild(paint);
        let g = paint.getComponent(cc.Graphics);
        cc.log(index);
        // cc.log(g);
        // cc.log(light_left_x,light_left_y);
        g.moveTo(x,y);
        g.lineTo(light_left_x,light_left_y);
        g.lineTo(light_right_x,light_right_y);
        g.close();
        g.stroke();
        g.fillColor = new cc.Color().fromHEX("ffffff");
        g.fill();
 
    },

    boomIsBoom(fruit,x,y){
        this.node.off(cc.Node.EventType.TOUCH_START, this.onTouchStart, this, true);
        this.node.off(cc.Node.EventType.TOUCH_MOVE, this.onTouchMove, this, true);
        this.unscheduleAllCallbacks();
        let num_light = 10;
        let light_array = [];
        for(var i=0;i

        在这里面,首先是建立一个数组通过随机排序存放这10条光线的生成顺序,接着通过定时器setTimeout来逐条画出来。这里涉及到一些javascript的知识,像是bind的使用,给回调函数传参,变量作用域等等,这里我给了两种方法参考(上面代码注释部分)。

        第一种方法是将setTimeout函数写进一个新的函数set_time_func()中,根据你要使用到的变量给这个新的函数的设置形参,同时要在外面设置start_this 存放外面的this对象(脚本start.js的this,类似于在furit类里面存放start.js的引用),接着用bind方法来指定。


        // 第一种方法:
        

        let start_this = this;
        function set_time_func(x,y,light_array,num){
            setTimeout(function(){
                cc.log(x,y)
                this.createLight(x,y,360/num_light,light_array[num]);
            }.bind(start_this),time+=100);
        }

        while(num--){  
            // 第一种方法:
            set_time_func(x,y,light_array,num);

        }

        

        之所以要将setTimeout函数写进另一个函数中,不仅仅是为了参数的传递,同时涉及到一些变量作用域的问题,比如如果像下面这样写:

// wrong
while(num--){
            setTimeout(function(){
                this.createLight(x,y,360/num_light,light_array[ num ]);
            }.bind(this),time+=110);
        }

 那么实际上,真正传进this.createLight里的num实际上都等于-1,因为此时的num对setTimout里的函数来讲,相当于是全局变量,由于while肯定会先一步全部跑完,这时候再传进this.createLight里面的num就只会是跑完后的-1了。

        因此也有第二种处理方法,将num作为this,来绑定到setTimeout的回调函数中。这样,每个num值(从10到0)都能被正确传进去。

// 第二种方法 
       let paint = cc.instantiate(this.paint);
       this.node.addChild(paint);

        while(num--){
            cc.log(num);
            setTimeout(function(){
                createLight(x,y,360/num_light,light_array[ this ],paint);
            }.bind(num),time+=110);
        }

        注意这里面的createLight前面没有this.,是因为它是在cc class类外面写的,因为bind了num的缘故,只能在外面作为全局函数来做。

        这两种方法的creatLight方法实现逻辑是完全一样的,都是先计算每条光线边的位置、角度,再通过Graphics组件来完成绘制。

        绘制光线之后,再处理整个屏幕的特效,在update中逐步降低其透明度来实现。

//start.js

update(dt){
// ...
// 当切到炸弹时的特效
        if(this.boom_is_broken && this.blank_paint && this.blank_opacity>0){
            var g = this.blank_paint.getComponent(cc.Graphics);
            g.clear();
            g.fillColor = cc.Color.WHITE.setA(this.blank_opacity-=255 * (dt/3));
            g.fillRect(0,0,1000,1000);
        }
//...

}

        最后,就是游戏得分控制了,这里设置当水果漏掉3个或切到炸弹时就game over,因此,只需在start.js中设置一个变量存放当前漏掉的水果个数,以及设置变量代表是否切到炸弹即可,同时添加一个label组件用来存放当前得分。逻辑比较简单,这里不再介绍,结合代码看就行了。

        那么整个水果忍者小游戏的制作流程就介绍完毕了,剩下的就是构建发布,以及一些细节优化等等。如果要在手机上调试运行的话,可能需要解决这些问题:

  • 节点适配问题。虽然cocos已经在canvas编辑器中有fixHeight和fixWidth这两个选项,但实际上对于下面的其它子节点可能效果并不是很好,最好还是通过代码去设置调节其中的子节点大小、位置、缩放比例等等。
  • 多点触控问题。不同于电脑,像手机这种的Touch事件是存在多点触摸问题的,像是会导致滑动“刀光”的显示问题等等。我之前有试了一下,似乎可以通过event的getId()来判断是否为同一触摸事件来解决,相关代码我也有写出,不过注释掉了。
  • 性能问题。因为主要在电脑端调试,所以我不清楚发布成手机端会不会卡顿。(发布成web mobile方式的话,手机运行应该是没问题的)。

        另外,mac上用safari调试或者运行的话,相关声音好像是出不来的,用chrome的话,要滑动一下才会开始有声音。

       最后,附上游戏代码的github地址和下载链接:

github:https://github.com/1998y12/FruitNinja.git

下载地址:https://download.csdn.net/download/qq_41508508/12454969

你可能感兴趣的:(基于CocosCreator的切水果小游戏(三))