基于HTML5/Javascrip的游戏开发框架Phaser

Phaser是一个开源的HTML5游戏框架,也就是传说中100行代码之内搞定Flappy Bird的神器,通过这个框架我们可以很容易地开发桌面和移动的小游戏。目前Phaser的最新版本是2.0.7,它同时支持WebGL和Canvas。像其它游戏框架一样,Phaser封装了很多游戏开发的特性。在这篇文章中我们将会通过Phaser创建一个简单的游戏。为了专注于对框架学习,在下文中我们不会用到Phaser的任何对象,如精灵和组。

搭建Phaser

Phaser是一个通过Yeoman构建的开源的框架,我们可以在Github上下载到最新的Phaser,下载之后通过以下命令搭建:

1. npm install -g generator-phaser-official
2. yo phaser-official


Phaser框架的文件结构

一个Phaser工程将会有如下的文件结构:

|-- app.js 
|-- main.js
|-- prefabs
`-- states
    |-- boot.js
    |-- level_intro.js
    |-- level_master.js
    |-- level_round.js
    |-- main_intro.js
    |-- main_menu.js
    `-- preload.js

Main.js作为程序入口,开始游戏。app.js用来定义一个Phaser应用,prefabs文件夹存放游戏对象,states文件夹用来保存游戏状态。 

如果你想要在这些文件中引入纯脚本,我推荐你用RequireJS或是Browserify。

Phaser.state

Phaser state将会封装游戏中不同的状态,比如游戏的加载、主菜单、级别、帮助、暂停等等……当一个state开始的时候,你可以创建与这个state相关的对象,同时你也可以在不同的state之间切换,Phaser将会为你清理过时的游戏对象,你可以在新的state中继续创建游戏对象并显示它们。

你可以通过实现预设方法来创建状态,下面我举一些重要的例子:

  • init – 当state开始的时候执行的方法,它传递一个参数使state之间共享数据。
  • preload – 当state开始的时候执行的方法,它的作用是在游戏开始前加载所有的资源文件。
  • create – preload方法执行后开始执行的方法,用来创建游戏对象。
  • shutdown – 当一个state结束的时候执行的方法,用来清理过时的游戏对象。 

State执行流程

下图是state的执行流程。Boot和Preload状态用来配置和加载游戏资源,MainMenu状态用来显示主菜单。其他的level状态是为了解决游戏过程中不同的级别和关数,每一个级别可以分成很多关,通过后进入下一关。

Game State

Boot state在preload的hook方法中加载预加载资源,并构建游戏的配置,如窗口缩放和输入内容。

File: states/boot.js

1. function Boot() {};
2. 
3. Boot.prototype = {
4.   preload: function() {
5.     // load preloader assets
6.   },
7.   create: function() {
8.     // setup game environment
9.     // scale, input etc..
10. 
11.     this.game.state.start('preload');
12.   }
13. };

 Preload state加载所有的游戏资源,然后切换到main-intro状态。

File: states/preload.js

1. Preload.prototype = {
2.   preload: function() {
3.     // load all game assets
4.     // images, spritesheets, atlases, audio etc..
5.   },
6.   create: function() {
7.     this.game.state.start('main-intro');
8.   }
9. };

MainIntro state 用来显示游戏的介绍、logo、用户信息等等……它不需要preload方法,执行完之后就会转换到main-menu状态。

File: states/main_intro.js

1. function MainIntroState() {};
2. 
3. MainIntroState.prototype = {
4.   create: function() {
5.     // add main intro assets into the world
6.     this.tweenFadeState();
7.   },
8. 
9.   tweenFadeState: function() {
10.     this.game.add.tween({})
11.       .to({alpha: 1}, 2000)
12.       .onComplete.add(function() {
13.         this.game.state.start('main-menu');
14.       }, this);
15.   }
16. };

 MainMenu state显示游戏的主界面,用户可以通过主菜单和程序进行交互。为了简单的演示,我在以下代码中添加了一个键盘触发事件,事件触发后转换到level-master状态。

File: states/main_menu.js

1. MainMenuState.prototype = {
2.   create: function() {
3.     this.enterKey = this.game.input.keyboard
4.         .addKey(Phaser.Keyboard.ENTER);
5. 
6.     this.enterKey.onDown.add(this.tweenPlayState, this);
7.   },
8.   tweenPlayState: function() {
9.     var tweenMenuShrink = this.game.add.tween({})
10.           .to({x: 0, y: 0}, 200);
11. 
12.     var tweenFadeIn = this.game.add.tween({})
13.           .to({alpha: 1}, 2000);
14. 
15.     tweenFadeIn.onComplete.add(function() {
16.       this.game.state.start('level-master');
17.     }, this);
18. 
19.     tweenMenuShrink.chain(tweenFadeIn);
20.     tweenMenuShrink.start();
21.   }
22. };


这里为了演示简化了tween操作,在正常的开发中,你应该在create方法中创建游戏对象,具体的创建方法可以参见Phaser的Demo和文档。

LevelMaster state在游戏界面上不显示任何的内容,它要做的只是决定选择转换到level-round state还是leverl-intro state,同时,它也会在不同的state之间更新和传递游戏数据。

File: state/level_master.js

1. LevelMasterState.prototype = {
2.   init: function(levelData) {
3.     if (!levelData) {
4.       levelData = {
5.         level: 0,
6.         round: 1,
7.         players: [
8.           { score: 0, skill: 1 },
9.           { score: 0, skill: 1 }
10.         ]
11.       };
12.     }
13. 
14.     this.levelData = levelData;
15.     this.winScore = 2;
16.   },
17. 
18.   create: function() {
19.     this.decideLevelState();
20.   }
21. };

当一个新的level开始,level-intro state就开始了,level-intro state会显示对这个新level的介绍,比如当前是哪种级别,在level-intro之后,会转换到level-round,也就是游戏开始的地方。

当一个round结束之后,要么是新的level,要么就是新的round。这个逻辑在decideLevelState方法中实现,如果我们通过了一个level,游戏就会进入下一个level,当已经进入最高的level之后,会切换到下一个round。

this.levelData保存游戏的数据,像当前的level、round和游戏分数。我们在这些值发生逻辑变化和切换state的时候更新它们。

File: states/level_master.js

1. LevelMasterState.prototype = {
2.   decideLevelState: function() {
3.     if (this.isFirstLevel() || this.getWinningPlayer() !== -1) {
4.       this.nextLevel();
5.     } else {
6.       this.nextRound();
7.     }
8.   },
9.   nextLevel: function() {
10.     this.levelData.level++;
11. 
12.     this.levelData.players.forEach(function(p) {
13.       p.score = 0;
14.     }, this);
15. 
16.     this.levelData.round = 1;
17. 
18.     this.game.state.start('level-intro', true, false, this.levelData);
19.   },
20.   nextRound: function() {
21.       this.levelData.round++;
22.       this.game.state.start('level-round', true, false, this.levelData);
23.   }
24. };

LevelIntro state显示了level的介绍信息,如你正在哪个level中。我们可以通过传递levelDate参数来保存游戏数据。在create方法中,如果用户进入第一个level,我们可以通过一个技能菜单来获取levelData,技能菜单指的是用户选择的的难度级别,这个值由你来设定。结束的时候会切换到level-round state。

File: states/level_intro.js

1. LevelIntroState.prototype = {
2.   init: function(levelData) {
3.     this.levelData = levelData;
4.   },
5.   create: function() {
6.     var tweenIntro = this.tweenIntro();
7. 
8.     if (this.levelData.level === 1) {
9.       var tweenSkillMenuPop = this.tweenSkillMenuPop();
10. 
11.       tweenIntro.chain(tweenSkillMenuPop);
12.       tweenSkillMenuPop.onComplete.add(this.levelStart, this);
13.     } else {
14.       tweenIntro.onComplete.add(this.levelStart, this);
15.     }
16.   },
17.   levelStart: function() {
18.     this.game.state.start('level-round', true, false, this.levelData);
19.   },
20.   tweenIntro: function() {
21.     var tween = this.game.add.tween({})
22.       .to({alpha: 0}, 1000, Phaser.Easing.Linear.None, true);
23. 
24.     return tween;
25.   },
26.   tweenSkillMenuPop: function() {
27.     var tween = this.game.add.tween({})
28.       .to({x: 1, y: 1}, 500, Phaser.Easing.Linear.None, true);
29. 
30.     return tween;
31.   }
32. };


最后LevelRound state是游戏当前所发生的位置,你可以通过update方法进行更新。为了简化演示,我在state结束的时候添加了一个点击回车键的交互事件,它会返回到lever-master,在level-master的初始位置得到传递的levelData数据。

File: states/level_round.js

1. LevelRoundState.prototype = {
2.   init: function(levelData) {
3.     this.levelData = levelData;
4.   },
5.   create: function() {
6.     this.enterKey = this.game.input.keyboard
7.       .addKey(Phaser.Keyboard.ENTER);
8. 
9.     this.enterKey.onDown.add(this.roundEnd, this);
10.   },
11.   roundEnd: function() {
12.     this.nextRound();
13.   },
14.   nextRound: function() {
15.     this.game.state.start('level-master', true, false, this.levelData);
16.   }
17. };

 我们已经完成了整个state的流程环,它们看起来像下面这样:

1. Boot -> Preload ->
2. main-intro -> main-menu ->
3. level-master -> Level1 ->
4. level-master -> L1 Round1 ->
5. level-master -> L1 Round2 ->
6. level-master -> Level2 ->
7. level-master -> L2 Round1 ->

你可以通过在level-round state中设置一个事件转换到main-menu state来结束游戏的流程。 

开始游戏

现在我们将会运行这个Phaser游戏,将这个div放到你的页面中,Phaser将会把它的canvas放在这里。

File: index.html

1. <div id="game-area"></div>

我们需要创建一个Phaser.Game,把我们所有的state加入到StateManager中并开始Boot state。

File: app.js

1. function Game() {}
2. 
3. Game.prototype = {
4.   start: function() {
5.     var game = new Phaser.Game(640, 480, Phaser.AUTO, 'game-area');
6. 
7.     game.state.add('boot', BootState);
8.     game.state.add('preload', PreloadState);
9.     game.state.add('main-intro', MainIntroState);
10.     game.state.add('main-menu', MainMenuState);
11.     game.state.add('level-master', LevelMasterState);
12.     game.state.add('level-intro', MainIntroState);
13.     game.state.add('level-round', LevelRoundState);
14.     game.state.start('boot');
15.   }
16. };

 最后,都过以下代码触发这个游戏:

File: main.js

1. var game = new Game();
2. game.start();

 

总结

这只是利用Phaser框架开发了一个简单的游戏架子,你可以在文章上方点击下载这个Demo。同时,Phaser官方为你的开发游戏提供了很多资源,像精灵、动画、声音、物理、缩放等等,你可以访问Phaser的官网进行深入的学习。