啊哈 phaser制作小游戏flappy bird(2)

下面就只剩最后一个也是最重要的一个场景就是play场景啦

先上create代码

game.States.play = function(){
    this.create = function(){
        this.bg = game.add.tileSprite(0,0,game.width,game.height,'background');//背景图,这里先不用移动,游戏开始后再动
        this.pipeGroup = game.add.group();//用于存放管道的组,后面会讲到
        this.pipeGroup.enableBody = true;
        this.ground = game.add.tileSprite(0,game.height-112,game.width,112,'ground'); //地板,这里先不用移动,游戏开始后再动
        this.bird = game.add.sprite(50,150,'bird'); //鸟
        this.bird.animations.add('fly');//添加动画
        this.bird.animations.play('fly',12,true);//播放动画
        this.bird.anchor.setTo(0.5, 0.5); //设置中心点
        game.physics.enable(this.bird,Phaser.Physics.ARCADE); //开启鸟的物理系统
        this.bird.body.gravity.y = 0; //鸟的重力,未开始游戏,先让重力为0,不然鸟会掉下来
        game.physics.enable(this.ground,Phaser.Physics.ARCADE);//开启地面的物理系统
        this.ground.body.immovable = true; //让地面在物理环境中固定不动

        this.readyText = game.add.image(game.width/2, 40, 'ready_text'); //get ready 文字
        this.playTip = game.add.image(game.width/2,300,'play_tip'); //提示点击屏幕的图片
        this.readyText.anchor.setTo(0.5, 0);
        this.playTip.anchor.setTo(0.5, 0);

        this.hasStarted = false; //游戏是否已开始
        game.time.events.loop(900, this.generatePipes, this); //利用时钟事件来循环产生管道
        game.time.events.stop(false); //先不要启动时钟
        game.input.onDown.addOnce(this.statrGame, this); //点击屏幕后正式开始游戏
    };
}

首先,把背景加进来

 this.bg = game.add.tileSprite(0,0,game.width,game.height,'background');//背景图,这里先不用移动,游戏开始后再动

有新建了一个组,这组啥也没放

this.pipeGroup = game.add.group();//用于存放管道的组,后面会讲到
        this.pipeGroup.enableBody = true;

地板

  this.ground = game.add.tileSprite(0,game.height-112,game.width,112,'ground'); //地板,这里先不用移动,游戏开始后再动

  this.bird = game.add.sprite(50,150,'bird'); //鸟
        this.bird.animations.add('fly');//添加动画
        this.bird.animations.play('fly',12,true);//播放动画
        this.bird.anchor.setTo(0.5, 0.5); //设置中心点
        game.physics.enable(this.bird,Phaser.Physics.ARCADE); //开启鸟的物理系统
        this.bird.body.gravity.y = 0; //鸟的重力,未开始游戏,先让重力为0,不然鸟会掉下来

值得注意的是,给鸟添加了一个物理系统哦
默认的游戏中的每个对象的物理系统是关闭的,要启用一个对象的物理系统,可以使用 game.physics.enable() 方法

enable(object, system, debug)

object : 要开启物理系统的对象,可以是单个对象,也可以是一个包含多个对象的数组

system : 要启用的物理系统,默认为 Phaser.Physics.ARCADE,Phaser目前支持三种物理引擎,分别是Arcade ,P2 以及 Ninja。

debug : 是否开启调试

只有开启了对象的物理系统,该对象才具有物理特性,开启了物理系统后,对象的body属性指向该对象拥有的物理系统,所有与物理相关的属性或方法都必须在body上进行操作。

如注释,后来又添加了地面的物理系统,加入了提示文字和图片。添加了定时器,但是定时器还没有开启。时间监听器,onDown就是鼠标按下时触发startgame函数。

关于定时器:

loop(delay, callback, callbackContext, arguments); //以指定的时间间隔无限重复执行某一个函数,直到调用了Timer对象的stop()方法才停止

repeat(delay, repeatCount, callback, callbackContext, arguments); //让某个函数重复执行,可以指定重复的次数

关于事件监听

var input = game.input; //当前游戏的input对象
var signal = input.onDown; //鼠标按下时的 Signal对象
signal.add(function(){}); //给Signal 绑定事件处理函数
signal.add(function(){}); //再绑定一个
signal.addOnce(function(){}); //绑定一个只会执行一次的事件函数

这里面的定时器如果开启调用的是重复产生管道的函数,后面会提到。

第一个提到的是startgame函数,上代码。

this.startGame = function(){
    this.gameSpeed = 200; //游戏速度
    this.gameIsOver = false; //游戏是否已结束的标志
    this.hasHitGround = false; //是否已碰撞到地面的标志
    this.hasStarted = true; //游戏是否已经开始的标志
    this.score = 0; //初始得分
    this.bg.autoScroll(-(this.gameSpeed/10),0); //让背景开始移动
    this.ground.autoScroll(-this.gameSpeed,0); //让地面开始移动
    this.bird.body.gravity.y = 1150; //给鸟设一个重力
    this.readyText.destroy(); //去除 'get ready' 图片
    this.playTip.destroy(); //去除 '玩法提示 图片
    game.input.onDown.add(this.fly, this); //给鼠标按下事件绑定鸟的飞翔动作
    game.time.events.start(); //启动时钟事件,开始制造管道
}

开始后各项状态数据初始化啦,然后把两个提示图片destroy掉,事件监听器是小鸟的飞,定时器是制造管子。。

下面是小鸟的飞函数:

this.fly = function(){
    this.bird.body.velocity.y = -350; //飞翔,实质上就是给鸟设一个向上的速度
    game.add.tween(this.bird).to({angle:-30}, 100, null, true, 0, 0, false); //上升时头朝上的动画
    this.soundFly.play(); //播放飞翔的音效
}

sprite.body 所指向的对象,拥有很多跟物理相关的属性和方法,然后这个body它还有一个 velocity 对象,表示物体的速度,跟重力一样,都分水平和垂直两个方向,也可以用setTo(x,y)方法来设置。一旦给物体设置了合适的速度,它便能动了。通过给小鸟设置向上的速度不就上去了嘛。
但是重力是向下的。可以想象,小鸟有了向上的速度但是加速度是向下的,还是会掉下来。

噢,提了两次的管道制造函数:

this.generatePipes = function(gap){ //制造一组上下的管道
    gap = gap || 100; //上下管道之间的间隙宽度
    var position = (505 - 320 - gap) + Math.floor((505 - 112 - 30 - gap - 505 + 320 + gap) * Math.random());//计算出一个上下管道之间的间隙的随机位置
    var topPipeY = position-360; //上方管道的位置
    var bottomPipeY = position+gap; //下方管道的位置

    if(this.resetPipe(topPipeY,bottomPipeY)) return; //如果有出了边界的管道,则重置他们,不再制造新的管道了,达到循环利用的目的

    var topPipe = game.add.sprite(game.width, topPipeY, 'pipe', 0, this.pipeGroup); //上方的管道
    var bottomPipe = game.add.sprite(game.width, bottomPipeY, 'pipe', 1, this.pipeGroup); //下方的管道
    this.pipeGroup.setAll('checkWorldBounds',true); //边界检测
    this.pipeGroup.setAll('outOfBoundsKill',true); //出边界后自动kill
    this.pipeGroup.setAll('body.velocity.x', -this.gameSpeed); //设置管道运动的速度
}

看到 if(this.resetPipe(topPipeY,bottomPipeY)) return; 是不是就不懂啦。实际上呢,reset是phaser为了节省资源的一个函数,就是重置位置。用旧的管子改变一下位置再用一次,而不是用新的管子还要渲染,浪费。

this.resetPipe = function(topPipeY,bottomPipeY){//重置出了边界的管道,做到回收利用
    var i = 0;
    this.pipeGroup.forEachDead(function(pipe){ //对组调用forEachDead方法来获取那些已经出了边界,也就是“死亡”了的对象
        if(pipe.y<=0){ //是上方的管道
            pipe.reset(game.width, topPipeY); //重置到初始位置
            pipe.hasScored = false; //重置为未得分
        }else{//是下方的管道
            pipe.reset(game.width, bottomPipeY); //重置到初始位置
        }
        pipe.body.velocity.x = -this.gameSpeed; //设置管道速度
        i++;
    }, this);
    return i == 2; //如果 i==2 代表有一组管道已经出了边界,可以回收这组管道了
}
reset(x, y, health)

这个方法能重置sprite对象的位置,更重要的是,如果在一个已经被杀死了(kill)的sprite对象上执行该方法,那么该sprite的 alive, exists, visible and renderable 等属性都会变回为true。在需要重复利用已经存在的sprite对象时,经常要使用该方法。

该讲的都讲完啦,下面是碰撞检测

this.update = function(){ //每一帧中都要执行的代码可以写在update方法中
    if(!this.hasStarted) return; //游戏未开始,先不执行任何东西
    game.physics.arcade.collide(this.bird,this.ground, this.hitGround, null, this); //检测与地面的碰撞
    game.physics.arcade.overlap(this.bird, this.pipeGroup, this.hitPipe, null, this); //检测与管道的碰撞
    if(this.bird.angle < 90) this.bird.angle += 2.5; //下降时鸟的头朝下的动画
    this.pipeGroup.forEachExists(this.checkScore,this); //分数检测和更新
}

基本上所有的碰撞检测都是出自update的。这就不多说了,在Arcade物理引擎中,碰撞检测主要用到两个函数,一个是collide,还有一个是overlap。

collide方法与overlap的区别在于collide会影响两个要检测的对象之间的物理状态,比如使用collide函数去检测两个物体,如果物体碰撞了,那么这两个物体之间就会有力的相互作用,可能其中一个会被另一个弹开,或者两个之间相互弹开。但如果使用overlap方法的话,则只会检测两个物体是否已经碰撞了,或者说已经重叠了,并不会产生物理作用,显然,如果只需要知道两个物体是否已经重叠了的话,overlap性能会更好。

碰撞检测可以单个对象与单个对象进行检测、单个对象与组进行检测、组与组进行检测。collide方法必须在每一帧中都进行调用,才能产生碰撞后的物理作用。

最后是分数管理

this.checkScore = function(pipe){//负责分数的检测和更新,pipe表示待检测的管道
    //pipe.hasScored 属性用来标识该管道是否已经得过分
    //pipe.y<0是指一组管道中的上面那个管道,一组管道中我们只需要检测一个就行了
    //当管道的x坐标 加上管道的宽度小于鸟的x坐标的时候,就表示已经飞过了管道,可以得分了
    if(!pipe.hasScored && pipe.y<=0 && pipe.x<=this.bird.x-17-54){
        pipe.hasScored = true; //标识为已经得过分
        this.scoreText.text = ++this.score; //更新分数的显示
        this.soundScore.play(); //得分的音效
        return true; 
    }
    return false;
}

剩下的代码

 this.stopGame = function(){
        this.bg.stopScroll();
        this.ground.stopScroll();
        this.pipeGroup.forEachExists(function(pipe){
            pipe.body.velocity.x = 0;
        }, this);
        this.bird.animations.stop('fly', 0);
        game.input.onDown.remove(this.fly,this);
        game.time.events.stop(true);
    }
    this.hitPipe = function(){
        if(this.gameIsOver) return;
        this.soundHitPipe.play();
        this.gameOver();
    }
    this.hitGround = function(){
        if(this.hasHitGround) return; //已经撞击过地面
        this.hasHitGround = true;
        this.soundHitGround.play();
        this.gameOver(true);
    }
    this.gameOver = function(show_text){
        this.gameIsOver = true;
        this.stopGame();
        if(show_text) this.showGameOverText();
    };//这里把最后的得分加上啦

    this.showGameOverText = function(){
        this.scoreText.destroy();
        game.bestScore = game.bestScore || 0;
        if(this.score > game.bestScore) game.bestScore = this.score; //最好分数
        this.gameOverGroup = game.add.group(); //添加一个组
        var gameOverText = this.gameOverGroup.create(game.width/2,0,'game_over'); //game over 文字图片
        var scoreboard = this.gameOverGroup.create(game.width/2,70,'score_board'); //分数板
        var currentScoreText = game.add.bitmapText(game.width/2 + 60, 105, 'flappy_font', this.score+'', 20, this.gameOverGroup); //当前分数
        var bestScoreText = game.add.bitmapText(game.width/2 + 60, 153, 'flappy_font', game.bestScore+'', 20, this.gameOverGroup); //最好分数
        var replayBtn = game.add.button(game.width/2, 210, 'btn', function(){//重玩按钮
            game.state.start('play');
        }, this, null, null, null, null, this.gameOverGroup);
        gameOverText.anchor.setTo(0.5, 0);
        scoreboard.anchor.setTo(0.5, 0);
        replayBtn.anchor.setTo(0.5, 0);
        this.gameOverGroup.y = 30;
    }

完了。

你可能感兴趣的:(游戏,play)