Phaser实现水果忍者

先来看一个效果吧!

怎么样?和真正的水果忍者有点像吧?再给一个体验地址,自己先体验一下:http://game.webxinxin.com/fruit/

好了,才实现基本功能,还有很多可以扩展的功能,如果你有兴趣可以在我的这个版本基础上继续开发。下面简单讲解一下代码设计思路,具体的还是自己撸吧。

先看下主菜单页面。

主菜单上面的部分还是比较简单的,主要是几个开场动画,把元素定好位后,做一个带次序的tween动画就可以了。可以用tweenchain来做,也可以在tween结束时onComplete回调启动下一个动画。下面的几个水果和炸弹要想一想,尤其是炸弹。炸弹是会冒火星的,这里我简单用方块来代替了,而且使用了粒子系统。

左边桃子和DOJO的圆圈,可以组成一个group,这两个元素都是旋转,但是他们的旋转方向和速度是不一样的。中间的西瓜和NEWGAME的圆圈也一样,还有右边的炸弹和QUIT圆圈。但是炸弹应该怎么来做呢,其实它本身也是三部分组成,一个炸弹图片,一个烟雾图片,一个火花的粒子发射器。一开始,我也想把它们三组成一个group,但是后来想一想,其实整个炸弹应该是一个sprite。在phaser中,groupsprite都可以去addChild。至于是一个group还是一个sprite,主要看内部元素的关系,如果这些内部元素自己是一个个体,可以有不用的速度,位置,旋转方向等,那么它们应该组成一个group,如果内部元素位置相互固定,速度,旋转都一致,而且本来就是某个物体的组成部分,那么它们就组成一个sprite。组成一个sprite的好处就是,物理属性共享。

sprite = game.add.sprite(env.x || 0, env.y || 0);
var bombImage = game.add.sprite(0, 0, 'bomb');
bombImage.anchor.setTo(0.5, 0.5);
// 烟雾
var bombSmoke = game.add.sprite(-55, -55, 'smoke');
// 粒子发射器
var bombEmit = game.add.emitter(-30, -30, 20);
// 设置粒子,使用我们自定义的粒子
bombEmit.particleClass = FlameParticle;
bombEmit.makeParticles();
// 设置属性
bombEmit.setScale(1, 0.8, 1, 0.8, 1500);
bombEmit.setAlpha(1, 0.1, 1500);
// 发射
bombEmit.start(false, 500, 50);
// 什么时候用Group,什么时候用sprite,一个炸弹,是一个sprite,刚体,速度,旋转都一致。group里面的东西可以速度不一致。
sprite.addChild(bombImage);
sprite.addChild(bombEmit);
sprite.addChild(bombSmoke);
// 物理属性
game.physics.enable(sprite, Phaser.Physics.ARCADE);
sprite.enableBody = true;

接下来的一个难点就是鼠标划过屏幕时的刀光怎么来做。这块确实想了很久,后来决定用图形来画,graphics,把鼠标划过的点连成折线,往周围延伸,形成一个刀光的模样。这里面涉及到一些数学的计算,具体的逻辑都封装在了mathTool里面。算法最后得到的就是组成刀光轮廓的一系列点,而输入就是鼠标滑过的点。再记录一下鼠标点的时间,设置一个超时,超时后移除,这个刀光效果就完成了。

// 形成刀光点
var res = [];
if(points.length <= 0) {
  return;
} else if(points.length == 1) {
  var oneLength = 6;
  res.push(new Phaser.Point(points[0].x - oneLength, points[0].y));
  res.push(new Phaser.Point(points[0].x, points[0].y - oneLength));
  res.push(new Phaser.Point(points[0].x + oneLength, points[0].y));
  res.push(new Phaser.Point(points[0].x, points[0].y + oneLength));
} else {
  var tailLength = 10;
  var headLength = 20;
  var tailWidth = 1;
  var headWidth = 6;
  res.push(this.calcParallel(points[0], points[1], tailLength));
  for(var i=0; i<points.length-1; i++) {
    res.push(this.calcVertical(points[i+1], points[i], Math.round((headWidth - tailWidth) * i / (points.length - 1) + tailWidth), true));
  }
  res.push(this.calcVertical(points[points.length-2], points[points.length-1], headWidth, false));
  res.push(this.calcParallel(points[points.length-1], points[points.length-2], headLength));
  res.push(this.calcVertical(points[points.length-2], points[points.length-1], headWidth, true));
  for(var i=points.length-1; i>0; i--) {
    res.push(this.calcVertical(points[i], points[i-1], Math.round((headWidth - tailWidth) * (i - 1) / (points.length - 1) + tailWidth), false));
  }
}
return res;

还有很多人会好奇,一刀把水果切成两半是怎么实现的?其实你看一下图片资源就知道了。图片中每一个水果都配有两个半片的水果,只要计算一下切过去的角度,然后把原来水果消失,把两半的水果贴上去,再把速度和加速度调整一下,就实现了水果切成两半的效果。大概像这样:

// 两半
halfOne = game.add.sprite(sprite.body.x + sprite.width/2, sprite.body.y + sprite.height/2, env.key + '-1');
halfOne.anchor.setTo(0.5, 0.5);
halfOne.rotation = deg + 45;
game.physics.enable(halfOne, Phaser.Physics.ARCADE);
halfOne.body.velocity.x = 100 + sprite.body.velocity.x;
halfOne.body.velocity.y = sprite.body.velocity.y;
halfOne.body.gravity.y = 2000;
halfOne.checkWorldBounds = true;
halfOne.outOfBoundsKill = true;
halfTwo = game.add.sprite(sprite.body.x + sprite.width/2, sprite.body.y + sprite.height/2, env.key + '-2');
halfTwo.anchor.setTo(0.5, 0.5);
halfTwo.rotation = deg + 45;
game.physics.enable(halfTwo, Phaser.Physics.ARCADE);
halfTwo.body.velocity.x = -100 + sprite.body.velocity.x;
halfTwo.body.velocity.y = sprite.body.velocity.y;
halfTwo.body.gravity.y = 2000;
halfTwo.checkWorldBounds = true;
halfTwo.outOfBoundsKill = true;
sprite.kill();

其实到这里,都还没有意识到,需要做一点封装了,知道做到下一个场景。

在这个场景中,很多东西其实和主菜单场景类似。比如刀光,水果被切成两半,炸弹。所以这个时候,我开始去封装一些东西,我把水果,炸弹,刀光代码都抽出来,封装成一些类。这样用起来确实方便很多了。

其实在这个场景中,基本动画就不说了,最关键的就是随机产生水果和炸弹,还要注意产生的范围,上抛的速度和方向,这些东西,就要大量用到随机数,熟练了之后也很简单,具体可看代码。

最后一个点就是炸弹爆炸时的动画,这个动画是用tweenchain功能来实现的。先随机一个初始角度,然后每隔45度增加一个光线,先把graphic画出来,但是设置透明度为0,通过tween动画将透明度设为1,就实现了光线一道一道出现的效果。当然光线数组设置好了之后,还要shuffle一下。

var explode = function(onWhite, onComplete) {
  var lights = [];
  var startDeg = Math.floor(Math.random() * 360);
  for(var i=0; i<8; i++) {
    var light = game.add.graphics(sprite.body.x, sprite.body.y);
    var points = [];
    points[0] = new Phaser.Point(0, 0);
    points[1] = new Phaser.Point(Math.floor(800*mathTool.degCos(startDeg + i*45)), Math.floor(800*mathTool.degSin(startDeg + i*45)));
    points[2] = new Phaser.Point(Math.floor(800*mathTool.degCos(startDeg + i*45 + 10)), Math.floor(800*mathTool.degSin(startDeg + i*45 + 10)));
    light.beginFill(0xffffff);
    light.drawPolygon(points);
    light.endFill();
    light.alpha = 0;
    lights.push(light);
  }
  lights = mathTool.shuffle(lights);
  var firstTween;
  var lastTween;
  for(var i=0; i<8; i++) {
    var light = lights[i];
    var tween = game.add.tween(light).to({alpha: 1}, 100, "Linear", false);
    if(i == 0) {
      firstTween = tween;
    }
    if(lastTween) {
      lastTween.chain(tween);
    }
    lastTween = tween;
    if(i == 7) {
      tween.onComplete.add(function() {
        var whiteScreen = game.add.graphics(0, 0);
        whiteScreen.beginFill(0xffffff);
        whiteScreen.drawRect(0, 0, game.width, game.height);
        whiteScreen.endFill();
        whiteScreen.alpha = 0;
        var tween = game.add.tween(whiteScreen).to({alpha: 1}, 100, "Linear", true);
        // 开始和结束的回调
        tween.onComplete.add(function() {
          onWhite();
          for(var i=0; i<8; i++) {
            var light = lights[i];
            light.kill();
          }
          var tweenBack = game.add.tween(whiteScreen).to({alpha: 0}, 100, "Linear", true);
          tweenBack.onComplete.add(function() {
            onComplete();
          });
        });
      });
    }
  }
  firstTween.start();
};

最后还有一个坑要注意,在GAMEOVER之后,我们点一下鼠标又能回到主菜单场景,但是这时候并不是开了一个新的对象,而是用的原来内存里的对象,也就是主菜单场景里面的一些属性,还会是跳转到play场景时的值,所以在跳转前,需要reset一下。否则可能会有意想不到的结果。

好了,整个水果忍者游戏大概就介绍到这里,有兴趣的朋友可以去翻看源码。当然,这个游戏和原版的还是有一些区别的,有的功能还不完善,期待你来改吧。

/* ** *** 游戏元素使用条款及注意事项 **** 游戏中的所有元素全部由iFIERO所原创(除引用之外),包括人物、音乐、场景等,* 创作的初衷就是让更多的游戏爱好者可以在开发游戏中获得自豪感 -- 让手机游戏开发变得简单。* 秉着开源分享的原则,iFIERO发布的游戏都尽可能的易懂实用,并开放所有源码,* 任何使用者都可以使用游戏中的代码块,也可以进行拷贝、修改、更新、升级,无须再经过iFIERO的同意。* 但这并不表示可以任意复制、拆分其中的游戏元素:* 用于[商业目的]而不注明出处,* 用于[任何教学]而不注明出处,* 用于[游戏上架]而不注明出处;* 另外,iFIERO有商用授权游戏元素,获得iFIERO官方授权后,即无任何限制!* 请尊重帮助过你的iFIERO的知识产权,非常感谢!* Created by VANGO杨 && ANDREW陈* Copyright :copyright: 2018 iFiero. All rights reserved.* iFIERO -- 让手机游戏开发变得简单* [www.iFIERO.com](http://www.ifiero.com/)* NinjaPiggy 忍者小猪 在此游戏中您将获得如下技能:* 1、LaunchScreen 学习如何设置游戏启动画面* 2、Scene 学习如何切换游戏游戏场景* 3、Scene Edit 学习直接使用可见即所得操作编辑游戏场景* 4、Scene Coding 学习纯代码编写一个场景、建立节点、设置音乐* 5、Random 利用可复用的随机函数生成Enemy* 6、Music 如何添加背景音乐、碰撞时的音效* 7、Particle 学习如何制造粒子爆炸特效* 8、Collision 学习有节点与节点之间的碰撞的原理及处理方法* 9、Animation&Atlas; 学习如何导入动画帧及何为Atlas* 10、SKEmitter 学习如何使用SKEmitter产生特效*/```let background = SKSpriteNode(imageNamed: Category.backgroundName) var isFinger // 手指是否在Ninja里let maxAspectRatio:CGFloat = CGFloat(16 / 9)let monsterScoreLabelNode:SKLabelNode = SKLabelNode()var monsterScore:Int = 0 // monster score 分数let ninjaLiveLabelNode :SKLabelNode = SKLabelNode()var ninjaLive:Int = 5 // ninja live 生命var ninjaNode = SKSpriteNode() // 加入ninja player// Ninja Atlasvar ninjaAtlas = SKTextureAtlas() // atlas 文件夹名称var ninjaTextureArray = [SKTexture]() var ninjaActi // Ninja手挥动的SKAction touchesBegan调用// Monsters Atlas var m // 加入 monster var mvar mvar hitActivar invincible = false // 无敌时刻```源码传送门:https://github.com/apiapia/NinjaPiggyGameTutorial
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值