安装官方的最佳实践
下载npm
安装Yeoman
npm install -g yo
安装Yeoman生成器
npm install -g generator-phaser-official
初始化生成器
yo phaser-official
创建项目目录
mkdir flappy-bird-reborn
cd flappy-bird-reborn
yo phaser-official
按提示一一回答:
You're using the fantastic Phaser generator.
[?] What is the name of your project? flappy bird reborn ---项目名称
[?] Which Phaser version would you like to use? 2.0.1 ---使用Phaser的版本号
[?] Game Display Width: 288 --游戏的宽度
[?] Game Display Height: 505 --游戏的高度
最终的结果如下:
├── Gruntfile.js
├── assets
│ ├── preloader.gif
│ └── yeoman-logo.png
│──bower_components
│──node_modules
├── bower.json
├── config.json
├── css
│ └── styles.css
├── game
│ ├── main.js
│ └── states
│ ├── boot.js
│ ├── gameover.js
│ ├── menu.js
│ ├── play.js
│ └── preload.js
├── package.json
└── templates
└── _main.js.tpl
进入flappy-bird-reborn目录下,执行命令
grunt
该命令将编译资源,启动服务器,打开浏览器,当编写的代码发生变化时,自动刷新浏览器。
每一个游戏都需要一个菜单,我们的也不例外。首先,我们加载菜单所需要的资源。打开game/states/preload.js.代码如下:
'use strict';
function Preload() {
this.asset = null;
this.ready = false;
}
Preload.prototype = {
preload: function() {
this.asset = this.add.sprite(this.width/2,this.height/2, 'preloader');
this.asset.anchor.setTo(0.5, 0.5);
this.load.onLoadComplete.addOnce(this.onLoadComplete, this);
this.load.setPreloadSprite(this.asset);
this.load.image('yeoman', 'assets/yeoman-logo.png');
},
create: function() {
this.asset.cropEnabled = false;
},
update: function() {
if(!!this.ready) {
this.game.state.start('menu');
}
},
onLoadComplete: function() {
this.ready = true;
}
};
module.exports = Preload;
原创的Flappy Bird游戏,在菜单界面上包含有背景,地面,游戏标题,小鸟,一个开始按钮,和一个共享按钮。因此我们加载这些图片资源:
preload: function() {
this.load.onLoadComplete.addOnce(this.onLoadComplete, this);
this.asset = this.add.sprite(this.width/2, this.height/2, 'preloader');
this.asset.anchor.setTo(0.5, 0.5);
this.load.setPreloadSprite(this.asset);
this.load.image('background', 'assets/background.png');
this.load.image('ground', 'assets/ground.png');
this.load.image('title', 'assets/title.png');
this.load.image('startButton', 'assets/start-button.png');
this.load.spritesheet('bird', 'assets/bird.png', 34, 24, 3);
}
开始的四行代码是样本代码,用于在加载我们需要的资源时,显示加载进度条。紧跟着的四行用于加载单个的图片文件。加载图片的API如下
this.load.image(key,url);
简单的来说,就是告诉Phaser的缓存系统通过指定的url加载图片并保存起来,以便通过key参数指定过的唯一名字进行引用。
最好一行,稍微有些不同。其主要目的是用于加载具有动画帧的小鸟图片。小鸟动画图片如下。
你可以看到图片有三帧,用行话说就是每帧宽34像素,高24像素。API如下:
this.load.spritesheet(key, url, frameWidth, frameHeight, numberOfFrames);
保存代码,刷新浏览器。应当看到在屏幕上有一个绿色的立方体。这是因为我们删除了以前在该区域显示的资源。这是正确的,我们通过一行代码进行改观。
打开game/states/menu.js 原始代码如下
'use strict';
function Menu() {}
Menu.prototype = {
preload: function() {
},
create: function() {
var style = { font: '65px Arial', fill: '#ffffff', align: 'center'};
this.sprite = this.game.add.sprite(this.game.world.centerX, 138, 'yeoman');
this.sprite.anchor.setTo(0.5, 0.5);
this.titleText = this.game.add.text(this.game.world.centerX, 300, '\'Allo, \'Allo!', style);
this.titleText.anchor.setTo(0.5, 0.5);
this.instructionsText = this.game.add.text(this.game.world.centerX, 400, 'Click anywhere to play "Click The Yeoman Logo"', { font: '16px Arial', fill: '#ffffff', align: 'center'});
this.instructionsText.anchor.setTo(0.5, 0.5);
this.sprite.angle = -20;
this.game.add.tween(this.sprite).to({angle: 20}, 1000, Phaser.Easing.Linear.NONE, true, 0, 1000, true);
},
update: function() {
if(this.game.input.activePointer.justPressed()) {
this.game.state.start('play');
}
}
};
module.exports = Menu;
删除在create和update函数中的内容,我们从头开始。
'use strict';
function Menu() {}
Menu.prototype = {
preload: function() {
},
create: function() {
},
update: function() {
}
};
module.exports = Menu;
第一件事就是为我们的游戏设置背景图片。我们在menu.js中的create方法中增加如下代码:
create: function() {
// 增加背景
this.background = this.game.add.sprite(0, 0, 'background');
},
保存代码,在浏览器中你将看到背景被正确的加载。
加载一个精灵是相当容易的。
var sprite = this.game.add.sprite(x, y, key);
x和y是指定加载的坐标,key是在预加载过程中指定的资源名字。
在原始版本中,在菜单界面,地面也是移动的。为了实现这一点,我们使用一个称为TileSprite的对象,这样就能够在不需要实例化一组精灵的情况下,当它们离开屏幕的时候,通过切换纹理,实现让地面自动滑动。在menu.js中的create方法中增加如下代码:
// 增加地面,并沿着x轴的反方向滚动
// and start scrolling in the negative x direction
this.ground = this.game.add.tileSprite(0, 400, 335, 112, 'ground');
this.ground.autoScroll(-200, 0);
保存,效果如下。
API如下:
var tileSprite = this.game.add.tileSprite(x, y, width, height, key);
tileSprite.autoScroll(xSpeed, ySpeed)
这行代码告诉tileSprite按照指定的速度进行滑动 xSpeed:X轴速度。负值代表沿着x轴的负方向移动。 ySpeed:Y轴速度。负值代表沿着y轴的负方向移动。
这是一个较大的主题。我们先讨论一下我们想要做成的效果。在原创的游戏中,当小鸟扇动翅膀时,标题和鸟都上下震动。为了模仿这种效果,我们需要编写如下代码:
1.创建一个组,用于保存标题和小鸟 2.创建一个标题精灵,增加到标题组 3.创建一个小鸟精灵,增加到标题组 4.使用动画精灵表创建一个会动的小鸟,并开始动画 5.设置组的在屏幕中的最初位置 6.为组增加动画,以便它们在震动时能一起移动。 7.谈论它
我们先列出全部代码,然后在逐一讲解。
如下代码仍旧添加在menu.js的create方法中:
/** 步骤 1 **/
// 创建一个组,用于保存标题和小鸟
this.titleGroup = this.game.add.group();
/** 步骤 2 **/
// 创建一个标题精灵,增加到标题组
this.title = this.game.add.sprite(0,0,'title');
this.titleGroup.add(this.title);
/** 步骤 3 **/
// 创建一个小鸟精灵,增加到标题组
this.bird = this.game.add.sprite(200,5,'bird');
this.titleGroup.add(this.bird);
/** 步骤 4 **/
// 使用动画精灵表创建一个会动的小鸟,并开始动画
this.bird.animations.add('flap');
this.bird.animations.play('flap', 12, true);
/** 步骤 5 **/
// 设置组的在屏幕中的最初位置
this.titleGroup.x = 30;
this.titleGroup.y = 0;
/** 步骤 6 **/
// 为组增加动画,以便它们在震动时能一起移动
this.game.add.tween(this.titleGroup).to({y:15}, 350, Phaser.Easing.Linear.NONE, true, 0, 1000, true);
步骤1:创建一个组,用于保存标题和小鸟
this.titleGroup = this.game.add.group();
用于创建一个简单的游戏组。
*什么是游戏组 组一般用于保存一组能够拉动的精灵,如子弹或者敌人。但是,它更强大。在组中的每一个精灵能被很容易的枚举,并同时有一个方法能调用它们,所有的属性能够被同时设置,包括事件。如我们设置的变形动画。
步骤 2:创建一个小鸟精灵,增加到标题组
this.title = this.game.add.sprite(0,0,'title');
this.titleGroup.add(this.title);
步骤 3:创建一个小鸟精灵,增加到标题组
this.bird = this.game.add.sprite(200,5,'bird');
this.titleGroup.add(this.bird);
步骤 4:使用动画精灵表创建一个会动的小鸟,并开始动画
this.bird.animations.add('flap');
this.bird.animations.play('flap', 12, true);
相当炫,我们引入一些新内容。
把某些相关的小图组成的图形叫做spriteshee,你可以很容易的通过AnimationManager让图形动起来。AnimationManager可以通过具有动画效果的sprite对象的获取到。我们在这里创建了一个简单的动画。假如我们的spritesheet有更多的帧和不同的动画(如:跳,向左走,向右走,溜达,或者射击),我们可以创建更逼真的动画,这是另外一个不同的教程。我们的代码非常简单,让我们逐行解释
sprite.animations.add(animationKey);
告诉sprite的AnimationManager使用在spritesheet中的所有帧创建一个被命名为animationKey的动画。
sprite.animations.play(animationKey, frameRate, loop);
让sprite的AnimationManager对象开始以给定的frameRate速度执行animationKey命名的动画。假如loop为true,将不停的执行动画,直到sprite.animations.stop()方法被调用。我们再次回顾一下我们的代码:
1. this.bird.animations.add('flap');
2. this.bird.animations.play('flap', 12, true);
1.我们告诉bird的AnimationManager使用在spritesheet中所有的帧创建一个名为flap的动画。
2.我们告诉sprite的AnimationManager以每秒12帧的速度开始flap的动画,并且一直执行。
步骤 5:设置组的在屏幕中的最初位置
this.titleGroup.x = 30;
this.titleGroup.y = 100;
我们分别在坐标(0,0) 和 (200,5)创建了我们的sprite,如果我们没有设置组的位置,屏幕的显示如下:
然而,当你改变一个组的位置,它变换在组中的所有sprite的位置的偏移量。因此,通过将titleGroup的坐标设置为(30,100),我们实际上将组中包含的sprite右移30个像素,下移100个像素,最终的效果如下:
正如瓦片说的,组使用起来超级方便。我们在以后的教程中进一步介绍它的高级功能。
步骤 6:为组增加动画,以便它们在震动时能一起移动
this.game.add.tween(this.titleGroup).to({y:115}, 350, Phaser.Easing.Linear.NONE, true, 0, 1000, true);
这一行代码看起来有点多。让我们一点点的看到底实际上干了那些事情。
this.game.add.tween(object).to(properties, duration, ease, autoStart, delay, repeat, yoyo);
关于讨论渐变有很多文档,建议查看官方文档。 Phaser.Tween
回过头来看看我们的代码:
this.game.add.tween(this.titleGroup).to({y:115}, 350, Phaser.Easing.Linear.NONE, true, 0, 1000, true);
属性列表如下:
用白话描述如下: 350毫秒内从屏幕的上方开始,沿y轴方向下移115个像素,并且不带渐变。立马开始执行,不需等待,重复1000次后重复从头开始重复执行。
菜单屏幕看起来相当不错,但是不能开始游戏,我们的菜单是完全无用的。
再一次我们在mene.js的create方法中增加如下代码:
//增加一个代回调函数的开始按钮
this.startButton = this.game.add.button(this.game.width/2, 300, 'startButton', this.startClick, this);
this.startButton.anchor.setTo(0.5,0.5);
除了最后的两个参数,代码看起来很眼熟。
var button = this.game.add.button(x, y, key, callback, callbackContext);
如调用game.add.sprite一样,我们增加一个按钮,指定X,Y坐标,以及一个我们以及预加载的key代表的图片。然而我们必须指定一个callback 方法来执行我们按钮的交互。callbackContext 是callback方法执行的上下文。
Callbacks 和CallbackContexts简介
在Phaser中具有回调函数的每一个函数都有一个callbackcontext参数。假如你忘记传递callbackcontext参数,Phaser将设置其为null。一般情况下,都将这个参数设置为this,因为我们想让我们的回调函数的上下文能够取到游戏中的所有对象。
细心的读者注意到,我们通过非常熟悉的小技巧来设置按钮的坐标,而不是具体的数值。
var centerX = this.game.width/2;
var centerY = this.game.height/2;
这将坐标设置游戏的屏幕中央。
紧跟这代码如下:
this.startButton.anchor.setTo(0.5,0.5);
设置按钮的锚位为高度的1/2,宽度的1/2.或者更简洁的说是其中心。
锚位
一个sprite的锚位是在sprite的内部,被Phaser当做圆点。意味着当你定位一个sprite,锚位将移动到这个位置,并且sprite的图像将围绕该点进行变换。当一个sprite被第一次创建时,锚位被设置为(0,0)。意味着sprite将其定位为左上脚。设置为(0.5,0.5)意味着sprite将其设置为显示区域的中心。
默认锚位的显示如下:
修改为(0.5,0.5)锚位的显示如下:
锚位也是sprite的旋转中心。也就是说,默认的旋转中心是左上脚。
在create方法下,增加新的方法startClick,代码如下:
create: function() {
/* ....... */
},
startClick: function() {
// 开始按钮的回调函数,开始play场景
this.game.state.start('play');
}
该函数只做了一件事。调用了game.state.start()。该方法只有一个参数:一个代表开始状态的字符串。在该实例中,我们开始‘play’状态,它才是游戏的主角。
状态(state)
一个状态,对于游戏来说通俗的讲就是一个不同的屏幕,或展示。在我们这个游戏中,我们有5个状态:boot状态,加载屏幕,菜单状态,play状态,和游戏结束状态。虽然我们只是讨论了menu界面(稍微的提到了加载pigment),但是生成器以及给我们提供了所有的5个状态。
打开main.js.你能看到如下代码:
'use strict';
var BootState = require('./states/boot');
var GameoverState = require('./states/gameover');
var MenuState = require('./states/menu');
var PlayState = require('./states/play');
var PreloadState = require('./states/preload');
var game = new Phaser.Game(288, 505, Phaser.AUTO, 'flappy-bird-reborn');
// 游戏状态
game.state.add('boot', BootState);
game.state.add('gameover', GameoverState);
game.state.add('menu', MenuState);
game.state.add('play', PlayState);
game.state.add('preload', PreloadState);
game.state.start('boot');
生成器自动给我们生成了这个文件,每增加一个状态,修改这个文件一次。了解这段代码最重要的是:
game.state.add(key, Phaser.State);
和加载器一样,游戏的StateManager 允许你增加一个游戏状态,并用一个key定义,以便以后检索它。Phaser.State代表状态本身。每一个状态继承了能被Phaser 自动识别并知道在游戏的生命周期中在合适时机调用的方法。你已经看到了它们的一部分(preload,craete,update)
在menu.js的底部有这么一行代码
module.exports = Menu;
因为我们使用 Browserify定义模块,并且通过require()管理依赖,module.exports告诉Browserify当这个文件被要求时,应当返回的内容。在本例中,我们告诉我们的模块输出Menu对象,它包含了我们真的这个状态写的全部代码。
因为生成器在main.js中为我们创建了以下几行代码:
var MenuState = require('./states/menu');
...
game.state.add('menu', MenuState);
游戏的StateManager将知道当‘menu'的状态开启时,menu.js代码将被执行。